grenerate_main_image_test.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. import os
  2. import copy
  3. import time
  4. from .image_deal_base_func import *
  5. from PIL import Image, ImageDraw
  6. from blend_modes import multiply
  7. import os
  8. import settings
  9. from functools import wraps
  10. from .multi_threaded_image_saving import ImageSaver
  11. from .get_mask_by_green import GetMask
  12. from middleware import UnicornException
  13. from logger import logger
  14. from custom_plugins.plugins_mode.pic_deal import PictureProcessing
  15. from service.remove_bg_ali import Segment
  16. def time_it(func):
  17. @wraps(func) # 使用wraps来保留原始函数的元数据信息
  18. def wrapper(*args, **kwargs):
  19. start_time = time.time() # 记录开始时间
  20. result = func(*args, **kwargs) # 调用原始函数
  21. end_time = time.time() # 记录结束时间
  22. print(
  23. f"Executing {func.__name__} took {end_time - start_time:.4f} seconds."
  24. ) # 打印耗时
  25. return result
  26. return wrapper
  27. class GeneratePic(object):
  28. def __init__(self, is_test=False):
  29. # self.logger = MyLogger()
  30. self.is_test = is_test
  31. self.saver = ImageSaver()
  32. pass
  33. @time_it
  34. def get_mask_and_config_v3(self, im_jpg: Image, im_png: Image, curve_mask: bool,
  35. grenerate_main_pic_brightness: int):
  36. """
  37. 步骤:
  38. 1、尺寸进行对应缩小
  39. 2、查找并设定鞋底阴影蒙版
  40. 3、自动色阶检查亮度
  41. 4、输出自动色阶参数、以及放大的尺寸蒙版
  42. """
  43. # ===================尺寸进行对应缩小(提升处理速度)
  44. im_jpg = to_resize(im_jpg, width=600)
  45. im_png = to_resize(im_png, width=600)
  46. # =========================两个蒙版叠加,删除上半部分的图
  47. # 获取透明图的左右点
  48. result = get_extremes_from_transparent(im_png)
  49. # 创建多边形mask(并进行左右偏移)
  50. left_point = (result["leftmost"][0], result["leftmost"][1] - 50)
  51. right_point = (result["rightmost"][0], result["rightmost"][1] - 50)
  52. mask_other_2 = create_polygon_mask_from_points(img=im_png, left_point=left_point, right_point=right_point)
  53. # 透明图转mask 将原图扩边一些,并填充白色
  54. mask_other_1 = transparent_to_mask_pil(im_png, is_invert=False)
  55. mask_other_1 = expand_or_shrink_mask(pil_image=mask_other_1, expansion_radius=40, blur_radius=0)
  56. new_image_1 = Image.new("RGBA", im_png.size, (255, 255, 255, 0))
  57. im_grey_jpg = im_jpg.convert("L").convert("RGB")
  58. inverted_mask_other_1 = ImageChops.invert(mask_other_1)
  59. # 两个mask 取交集
  60. mask_other_2 = mask_other_2.convert("L")
  61. # 返回的蒙版区域
  62. return_mask = mask_other_2
  63. new_mask = mask_intersection(inverted_mask_other_1, mask_other_2)
  64. # new_mask.show()
  65. # return_mask.show()
  66. # TODO 待移除
  67. # ====================生成新的图片
  68. print("84 生成新的图片")
  69. bg = Image.new(mode="RGB", size=im_png.size, color=(255, 255, 255))
  70. bg.paste(im=im_jpg, mask=new_mask) # 只粘贴有阴影的地方
  71. # bg.show()
  72. # ==================自动色阶处理======================
  73. # 对上述拼接后的图片进行自动色阶处理
  74. _im = cv2.cvtColor(np.asarray(bg), cv2.COLOR_RGB2BGR)
  75. # 背景阴影
  76. im_shadow = cv2.cvtColor(_im, cv2.COLOR_BGR2GRAY)
  77. print("copy.copy(im_shadow)")
  78. _im_shadow = copy.copy(im_shadow)
  79. Midtones = 0.7
  80. Highlight = 235
  81. k = copy.copy(settings.COLOR_GRADATION_CYCLES)
  82. print("开始循环识别")
  83. xunhuan = 0
  84. while k:
  85. xunhuan += 1
  86. k -= 1
  87. Midtones += 0.035
  88. if Midtones > 1.7:
  89. Midtones = 1.7
  90. Highlight -= 3
  91. _im_shadow = levels_adjust(img=im_shadow,
  92. Shadow=0,
  93. Midtones=Midtones,
  94. Highlight=Highlight,
  95. OutShadow=0,
  96. OutHighlight=255, Dim=3)
  97. brightness_value = brightness_check(img_gray=_im_shadow, mask=new_mask)
  98. print("循环识别:{},Midtones:{},Highlight:{},brightness_value:{}".format(xunhuan,
  99. Midtones,
  100. Highlight,
  101. brightness_value))
  102. if brightness_value >= grenerate_main_pic_brightness:
  103. # //GRENERATE_MAIN_PIC_BRIGHTNESS 亮度校验
  104. break
  105. im_shadow = cv2_to_pil(_im_shadow)
  106. # if self.is_test:
  107. # im_shadow.show()
  108. # ========================================================
  109. # 计算阴影的亮度,用于确保阴影不要太黑
  110. # 1、图片预处理,只保留阴影
  111. only_shadow_img = im_shadow.copy()
  112. only_shadow_img.paste(Image.new(mode="RGBA", size=only_shadow_img.size, color=(255, 255, 255, 255)),
  113. mask=im_png)
  114. # only_shadow_img.show()
  115. average_brightness = calculated_shadow_brightness(only_shadow_img)
  116. print("average_brightness:", average_brightness)
  117. config = {
  118. "Midtones": Midtones,
  119. "Highlight": Highlight,
  120. "average_brightness": average_brightness,
  121. }
  122. return return_mask, config
  123. @time_it
  124. def fast_remove_black_dots(self, mask_img, max_area=50):
  125. """
  126. 极速去除黑白蒙版中面积 <= max_area 的黑色噪点
  127. """
  128. # 1. 确保是纯黑白二值图 (0 和 255)
  129. # 如果原图有灰度,先做二值化,否则连通域计算会出错
  130. if mask_img.mode != 'L':
  131. mask_img = mask_img.convert('L')
  132. img_array = np.array(mask_img)
  133. _, binary = cv2.threshold(img_array, 127, 255, cv2.THRESH_BINARY)
  134. # 2. 连通域分析 (C++底层,极快)
  135. # 注意:OpenCV 默认白色(255)是前景,黑色(0)是背景
  136. # 我们要找的是“黑色的噪点”,所以先取反
  137. inv_binary = cv2.bitwise_not(binary)
  138. num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(inv_binary, connectivity=8)
  139. # 3. 提取需要去除的黑色区域
  140. # stats 包含每个连通域的面积 (cv2.CC_STAT_AREA)
  141. # 第 0 个标签是背景(原图的大面积白色),从 1 开始是黑色噪点
  142. areas = stats[1:, cv2.CC_STAT_AREA]
  143. small_dots_indices = np.where(areas <= max_area)[0] + 1 # 索引要加1,对应回 labels
  144. # 4. 批量修改原图(向量化操作,无需 for 循环)
  145. mask_to_remove = np.isin(labels, small_dots_indices)
  146. binary[mask_to_remove] = 255 # 将小噪点涂白
  147. return Image.fromarray(binary, mode='L')
  148. @time_it
  149. def get_mask_and_config_v4_online(self, ori_im_jpg: Image, ori_im_png: Image, im_jpg: Image, im_png: Image):
  150. print("179------当前计算函数:get_mask_and_config_v4_online")
  151. """
  152. 步骤:
  153. 1、尺寸进行对应缩小
  154. 2、查找并设定鞋底阴影蒙版
  155. 3、自动色阶检查亮度
  156. 4、输出自动色阶参数、以及放大的尺寸蒙版
  157. """
  158. # ===================尺寸进行对应缩小(提升处理速度)
  159. ori_im_jpg = to_resize(ori_im_jpg, width=1200)
  160. ori_im_png = to_resize(ori_im_png, width=1200)
  161. im_jpg = to_resize(im_jpg, width=600)
  162. im_png = to_resize(im_png, width=600)
  163. segment = Segment()
  164. api_url = f"{settings.DOMAIN}/api/ai_image/segment_shadow/platform_shadow"
  165. bg_mask_image_url = segment.get_platform_shadow(ori_im_jpg, api_url=api_url)
  166. if bg_mask_image_url:
  167. response = requests.get(bg_mask_image_url)
  168. pic = response.content
  169. bg_mask = Image.open(BytesIO(pic)) # 阿里返回的抠图结果 已转PIL对象
  170. bg_mask = bg_mask.convert("L")
  171. bg_mask = ImageChops.invert(bg_mask)
  172. _, new_box = get_mini_crop_img(img=ori_im_png)
  173. bg_mask = bg_mask.crop(new_box) # 切图
  174. bg_mask = bg_mask.resize(im_png.size)
  175. bg_mask = self.fast_remove_black_dots(bg_mask, max_area=50)
  176. # bg_mask = expand_or_shrink_mask(pil_image=bg_mask, expansion_radius=6, blur_radius=0)
  177. else:
  178. bg_mask = False
  179. bg_mask = Image.new("RGB", im_png.size, (255, 255, 255))
  180. bg_mask = bg_mask.convert("L")
  181. # 透明图转mask 将原图扩边一些,并填充白色
  182. shoe_png_mask = transparent_to_mask_pil(im_png, is_invert=False)
  183. shoe_png_mask = expand_or_shrink_mask(pil_image=shoe_png_mask, expansion_radius=40, blur_radius=0)
  184. shoe_png_mask = ImageChops.invert(shoe_png_mask)
  185. # 两个mask 取交集
  186. if bg_mask is not False:
  187. # new_mask 背景+鞋子+鞋子阴影的mask
  188. new_mask = mask_intersection(shoe_png_mask, bg_mask)
  189. else:
  190. new_mask = shoe_png_mask
  191. # 黑色表示鞋子+背景
  192. # new_mask.show()
  193. # ====================生成图片(一张图减去背景、减去阴影、减去鞋子,即只有底盘的图片,其他区域为白色)
  194. bg = Image.new(mode="RGB", size=im_png.size, color=(255, 255, 255))
  195. bg.paste(im=im_jpg, mask=new_mask) # 只粘贴有阴影的地方
  196. # bg.show()
  197. # ==================自动色阶处理======================
  198. # 对上述拼接后的图片进行自动色阶处理
  199. _im = cv2.cvtColor(np.asarray(bg), cv2.COLOR_RGB2BGR)
  200. # 背景阴影
  201. im_shadow = cv2.cvtColor(_im, cv2.COLOR_BGR2GRAY)
  202. _im_shadow = copy.copy(im_shadow)
  203. Midtones = 0.8
  204. Highlight = 235
  205. k = copy.copy(settings.COLOR_GRADATION_CYCLES)
  206. print("开始循环识别")
  207. xunhuan = 0
  208. while k:
  209. xunhuan += 1
  210. k -= 1
  211. Midtones += 0.035
  212. if Midtones > 1.7:
  213. Midtones = 1.7
  214. Highlight -= 3
  215. _im_shadow = levels_adjust(img=im_shadow,
  216. Shadow=0,
  217. Midtones=Midtones,
  218. Highlight=Highlight,
  219. OutShadow=0,
  220. OutHighlight=255, Dim=3)
  221. brightness_value = get_png_brightness(img_gray=_im_shadow, mask=new_mask)
  222. print("128----循环识别:{},Midtones:{},Highlight:{},brightness_value:{},阀值:{}".format(xunhuan,
  223. Midtones,
  224. Highlight,
  225. brightness_value,
  226. settings.GRENERATE_MAIN_PIC_BRIGHTNESS
  227. ))
  228. if brightness_value >= settings.GRENERATE_MAIN_PIC_BRIGHTNESS:
  229. break
  230. im_shadow = cv2_to_pil(_im_shadow)
  231. # ========================================================
  232. # 计算阴影的亮度,用于确保阴影不要太黑
  233. # 1、图片预处理,只保留阴影
  234. only_shadow_img = im_shadow.copy()
  235. only_shadow_img.paste(Image.new(mode="RGBA", size=only_shadow_img.size, color=(255, 255, 255, 255)),
  236. mask=im_png)
  237. # only_shadow_img.show()
  238. average_brightness = calculated_shadow_brightness(only_shadow_img)
  239. print("average_brightness:", average_brightness)
  240. config = {
  241. "Midtones": Midtones,
  242. "Highlight": Highlight,
  243. "average_brightness": average_brightness,
  244. }
  245. return bg_mask, config
  246. @time_it
  247. def get_mask_and_config(self, im_jpg: Image, im_png: Image, curve_mask: bool):
  248. """
  249. 步骤:
  250. 1、尺寸进行对应缩小
  251. 2、查找并设定鞋底阴影蒙版
  252. 3、自动色阶检查亮度
  253. 4、输出自动色阶参数、以及放大的尺寸蒙版
  254. """
  255. # ===================尺寸进行对应缩小(提升处理速度)
  256. im_jpg = to_resize(im_jpg, width=800)
  257. im_png = to_resize(im_png, width=800)
  258. x1, y1, x2, y2 = im_png.getbbox()
  259. cv2_png = pil_to_cv2(im_png)
  260. # =====================设定鞋底阴影图的蒙版
  261. # 查找每列的最低非透明点
  262. min_y_values = find_lowest_non_transparent_points(cv2_png)
  263. # 在鞋底最低处增加一条直线蒙版,蒙版宽度为有效区域大小
  264. image_high = im_jpg.height
  265. print("图片高度:", image_high)
  266. cv2_jpg = pil_to_cv2(im_jpg)
  267. # 返回线条图片,以及最低位置
  268. print("返回线条图片,以及最低位置")
  269. # crop_image_box=(x1, y1, x2, y2),
  270. if curve_mask:
  271. crop_image_box = None
  272. else:
  273. # 不需要曲线部分的蒙版
  274. crop_image_box = (x1, y1, x2, y2)
  275. img_with_shifted_line, lowest_y = draw_shifted_line(
  276. image=cv2_jpg,
  277. min_y_values=min_y_values,
  278. shift_amount=15,
  279. one_line_pos=(x1, x2),
  280. line_color=(0, 0, 0),
  281. line_thickness=20,
  282. app=None,
  283. crop_image_box=crop_image_box,
  284. )
  285. print("66 制作蒙版")
  286. # 制作蒙版
  287. mask_line = cv2_to_pil(img_with_shifted_line)
  288. mask = mask_line.convert("L") # 转换为灰度图
  289. mask = ImageOps.invert(mask)
  290. # 蒙版扩边
  291. print("72 蒙版扩边")
  292. # 默认expansion_radius 65 blur_radius 45
  293. mask = expand_or_shrink_mask(
  294. pil_image=mask, expansion_radius=50, blur_radius=35
  295. )
  296. # =============使用绿色蒙版进行处理
  297. if settings.IS_GET_GREEN_MASK:
  298. print("============使用绿色蒙版进行处理")
  299. mask = mask.convert("RGB")
  300. white_bg = Image.new(mode="RGB", size=im_png.size, color=(0, 0, 0))
  301. green_areas_mask_pil = GetMask().find_green_areas(cv2_jpg)
  302. green_areas_mask_pil = expand_or_shrink_mask(
  303. pil_image=green_areas_mask_pil, expansion_radius=15, blur_radius=5
  304. )
  305. mask.paste(white_bg, mask=green_areas_mask_pil.convert("L"))
  306. mask = mask.convert("L")
  307. # ====================生成新的图片
  308. print("84 生成新的图片")
  309. bg = Image.new(mode="RGBA", size=im_png.size, color=(255, 255, 255, 255))
  310. bg.paste(im_png, mask=im_png)
  311. bg.paste(im_jpg, mask=mask) # 粘贴有阴影的地方
  312. if image_high > y2 + 20:
  313. lowest_y = y2 + 20
  314. if self.is_test:
  315. _bg = bg.copy()
  316. draw = ImageDraw.Draw(_bg)
  317. # 定义直线的起点和终点坐标
  318. start_point = (0, lowest_y) # 直线的起始点
  319. end_point = (_bg.width, lowest_y) # 直线的结束点
  320. # 定义直线的颜色(R, G, B)
  321. line_color = (255, 0, 0) # 红色
  322. _r = Image.new(mode="RGBA", size=im_png.size, color=(246, 147, 100, 255))
  323. # mask_line = mask_line.convert('L') # 转换为灰度图
  324. # mask_line = ImageOps.invert(mask_line)
  325. # _bg.paste(_r, mask=mask)
  326. # 绘制直线
  327. draw.line([start_point, end_point], fill=line_color, width=1)
  328. _bg.show()
  329. # bg.save(r"C:\Users\gymmc\Desktop\data\bg.png")
  330. # bg.show()
  331. # ==================自动色阶处理======================
  332. # 对上述拼接后的图片进行自动色阶处理
  333. bg = bg.convert("RGB")
  334. _im = cv2.cvtColor(np.asarray(bg), cv2.COLOR_RGB2BGR)
  335. # 背景阴影
  336. im_shadow = cv2.cvtColor(_im, cv2.COLOR_BGR2GRAY)
  337. print("image_high lowest_y", image_high, lowest_y)
  338. if lowest_y < 0 or lowest_y >= image_high:
  339. lowest_y = image_high - 1
  340. print("image_high lowest_y", image_high, lowest_y)
  341. rows = [lowest_y] # 需要检查的像素行
  342. print("copy.copy(im_shadow)")
  343. _im_shadow = copy.copy(im_shadow)
  344. Midtones = 0.7
  345. Highlight = 235
  346. k = copy.copy(settings.COLOR_GRADATION_CYCLES)
  347. print("循环识别")
  348. xunhuan = 0
  349. while k:
  350. xunhuan += 1
  351. # if settings.app:
  352. # settings.app.processEvents()
  353. k -= 1
  354. Midtones += 0.035
  355. if Midtones > 1.7:
  356. Midtones = 1.7
  357. Highlight -= 3
  358. _im_shadow = levels_adjust(
  359. img=im_shadow,
  360. Shadow=0,
  361. Midtones=Midtones,
  362. Highlight=Highlight,
  363. OutShadow=0,
  364. OutHighlight=255,
  365. Dim=3,
  366. )
  367. brightness_list = calculate_average_brightness_opencv(
  368. img_gray=_im_shadow, rows_to_check=rows
  369. )
  370. print(
  371. "循环识别:{},Midtones:{},Highlight:{},brightness_list:{}".format(
  372. xunhuan, Midtones, Highlight, brightness_list
  373. )
  374. )
  375. if brightness_list[0] >= settings.GRENERATE_MAIN_PIC_BRIGHTNESS:
  376. break
  377. im_shadow = cv2_to_pil(_im_shadow)
  378. # ========================================================
  379. # 计算阴影的亮度,用于确保阴影不要太黑
  380. # 1、图片预处理,只保留阴影
  381. only_shadow_img = im_shadow.copy()
  382. only_shadow_img.paste(
  383. Image.new(
  384. mode="RGBA", size=only_shadow_img.size, color=(255, 255, 255, 255)
  385. ),
  386. mask=im_png,
  387. )
  388. average_brightness = calculated_shadow_brightness(only_shadow_img)
  389. print("average_brightness:", average_brightness)
  390. config = {
  391. "Midtones": Midtones,
  392. "Highlight": Highlight,
  393. "average_brightness": average_brightness,
  394. }
  395. return mask, config
  396. def get_mask_and_config_1_2025_05_18(self, im_jpg: Image, im_png: Image):
  397. """
  398. 步骤:
  399. 1、尺寸进行对应缩小
  400. 2、查找并设定鞋底阴影蒙版
  401. 3、自动色阶检查亮度
  402. 4、输出自动色阶参数、以及放大的尺寸蒙版
  403. """
  404. # ===================尺寸进行对应缩小(提升处理速度)
  405. im_jpg = to_resize(im_jpg, width=800)
  406. im_png = to_resize(im_png, width=800)
  407. x1, y1, x2, y2 = im_png.getbbox()
  408. cv2_png = pil_to_cv2(im_png)
  409. # =====================设定鞋底阴影图的蒙版
  410. # 查找每列的最低非透明点
  411. min_y_values = find_lowest_non_transparent_points(cv2_png)
  412. # 在鞋底最低处增加一条直线蒙版,蒙版宽度为有效区域大小
  413. image_high = im_jpg.height
  414. print("图片高度:", image_high)
  415. cv2_jpg = pil_to_cv2(im_jpg)
  416. # 返回线条图片,以及最低位置
  417. print("返回线条图片,以及最低位置")
  418. img_with_shifted_line, lowest_y = draw_shifted_line(
  419. image=cv2_jpg,
  420. min_y_values=min_y_values,
  421. shift_amount=15,
  422. one_line_pos=(x1, x2),
  423. line_color=(0, 0, 0),
  424. line_thickness=20,
  425. app=None,
  426. crop_image_box=(x1, y1, x2, y2),
  427. )
  428. print("66 制作蒙版")
  429. # 制作蒙版
  430. mask_line = cv2_to_pil(img_with_shifted_line)
  431. mask = mask_line.convert("L") # 转换为灰度图
  432. mask = ImageOps.invert(mask)
  433. # 蒙版扩边
  434. print("72 蒙版扩边")
  435. # 默认expansion_radius 65 blur_radius 45
  436. mask = expand_or_shrink_mask(
  437. pil_image=mask, expansion_radius=50, blur_radius=35
  438. )
  439. # mask1 = expand_mask(mask, expansion_radius=30, blur_radius=10)
  440. # mask1.save("mask1.png")
  441. # mask2 = expand_or_shrink_mask(pil_image=mask, expansion_radius=60, blur_radius=30)
  442. # mask2.save("mask2.png")
  443. # raise 11
  444. # ====================生成新的图片
  445. print("84 生成新的图片")
  446. bg = Image.new(mode="RGBA", size=im_png.size, color=(255, 255, 255, 255))
  447. bg.paste(im_png, mask=im_png)
  448. bg.paste(im_jpg, mask=mask) # 粘贴有阴影的地方
  449. if self.is_test:
  450. _bg = bg.copy()
  451. draw = ImageDraw.Draw(_bg)
  452. # 定义直线的起点和终点坐标
  453. start_point = (0, lowest_y) # 直线的起始点
  454. end_point = (_bg.width, lowest_y) # 直线的结束点
  455. # 定义直线的颜色(R, G, B)
  456. line_color = (255, 0, 0) # 红色
  457. # 绘制直线
  458. draw.line([start_point, end_point], fill=line_color, width=1)
  459. # mask.show()
  460. # bg = pil_to_cv2(bg)
  461. # cv2.line(bg, (x1, lowest_y + 5), (x2, lowest_y + 5), color=(0, 0, 0),thickness=2)
  462. # bg = cv2_to_pil(bg)
  463. _r = Image.new(mode="RGBA", size=im_png.size, color=(246, 147, 100, 255))
  464. mask_line = mask_line.convert("L") # 转换为灰度图
  465. mask_line = ImageOps.invert(mask_line)
  466. _bg.paste(_r, mask=mask)
  467. _bg.show()
  468. # bg.save(r"C:\Users\gymmc\Desktop\data\bg.png")
  469. # bg.show()
  470. # ==================自动色阶处理======================
  471. # 对上述拼接后的图片进行自动色阶处理
  472. bg = bg.convert("RGB")
  473. _im = cv2.cvtColor(np.asarray(bg), cv2.COLOR_RGB2BGR)
  474. # 背景阴影
  475. im_shadow = cv2.cvtColor(_im, cv2.COLOR_BGR2GRAY)
  476. print("image_high lowest_y", image_high, lowest_y)
  477. if lowest_y < 0 or lowest_y >= image_high:
  478. lowest_y = image_high - 1
  479. print("image_high lowest_y", image_high, lowest_y)
  480. rows = [lowest_y] # 需要检查的像素行
  481. print("copy.copy(im_shadow)")
  482. _im_shadow = copy.copy(im_shadow)
  483. Midtones = 0.7
  484. Highlight = 235
  485. k = 12
  486. print("循环识别")
  487. while k:
  488. print("循环识别:{}".format(k))
  489. # if settings.app:
  490. # settings.app.processEvents()
  491. k -= 1
  492. Midtones += 0.1
  493. if Midtones > 1:
  494. Midtones = 1
  495. Highlight -= 3
  496. _im_shadow = levels_adjust(
  497. img=im_shadow,
  498. Shadow=0,
  499. Midtones=Midtones,
  500. Highlight=Highlight,
  501. OutShadow=0,
  502. OutHighlight=255,
  503. Dim=3,
  504. )
  505. brightness_list = calculate_average_brightness_opencv(
  506. img_gray=_im_shadow, rows_to_check=rows
  507. )
  508. print(brightness_list)
  509. if brightness_list[0] >= settings.GRENERATE_MAIN_PIC_BRIGHTNESS:
  510. break
  511. print("Midtones,Highlight:", Midtones, Highlight)
  512. im_shadow = cv2_to_pil(_im_shadow)
  513. # ========================================================
  514. # 计算阴影的亮度,用于确保阴影不要太黑
  515. # 1、图片预处理,只保留阴影
  516. only_shadow_img = im_shadow.copy()
  517. only_shadow_img.paste(
  518. Image.new(
  519. mode="RGBA", size=only_shadow_img.size, color=(255, 255, 255, 255)
  520. ),
  521. mask=im_png,
  522. )
  523. average_brightness = calculated_shadow_brightness(only_shadow_img)
  524. print("average_brightness:", average_brightness)
  525. config = {
  526. "Midtones": Midtones,
  527. "Highlight": Highlight,
  528. "average_brightness": average_brightness,
  529. }
  530. return mask, config
  531. def my_test(self, **kwargs):
  532. if "output_queue" in kwargs:
  533. output_queue = kwargs["output_queue"]
  534. else:
  535. output_queue = None
  536. time.sleep(3)
  537. if output_queue is not None:
  538. output_queue.put(True)
  539. def paste_img(self, image, top_img, base="nw", value=(0, 0), ):
  540. """
  541. {
  542. "command": "paste_img",
  543. "im": 需要粘贴的图片
  544. "pos": {"plugins_mode": "relative", # pixel
  545. "base": "center", # nw,nc,ne,ec ... 各个方向参考点
  546. "value": (100, 100),
  547. "percentage": (0.5, 0.5),
  548. },
  549. "margins": (0, 0, 0, 0), # 上下左右边距
  550. }
  551. """
  552. value = (int(value[0]), int(value[1]))
  553. # 处理默认值
  554. base = "nw" if not base else base
  555. top, down, left, right = 0, 0, 0, 0
  556. # 基于右边,上下居中
  557. if base == "ec" or base == "ce":
  558. p_x = int(image.width - (top_img.width + value[0]))
  559. p_y = int((image.height - top_img.height) / 2) + value[1]
  560. # 基于顶部,左右居中
  561. if base == "nc" or base == "cn":
  562. # 顶部对齐
  563. deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
  564. (image.height - top_img.height) / 2
  565. )
  566. p_x = deviation_x + value[0] + left
  567. p_y = value[1]
  568. # 基于右上角
  569. if base == "en" or base == "ne":
  570. p_x = int(image.width - (top_img.width + value[0])) + left
  571. p_y = value[1]
  572. # 基于左上角
  573. if base == "nw" or base == "wn":
  574. deviation_x, deviation_y = 0, 0
  575. p_x, p_y = value
  576. # 基于底部,左右居中
  577. if base == "cs" or base == "sc":
  578. deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
  579. (image.height - top_img.height) / 2
  580. )
  581. p_y = image.height - (top_img.height + value[1] + down)
  582. p_x = deviation_x + value[0] + left
  583. # 上下左右居中
  584. if base == "center" or base == "cc":
  585. deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
  586. (image.height - top_img.height) / 2
  587. )
  588. p_x = deviation_x + value[0] + left
  589. p_y = deviation_y + value[1] + top
  590. # 基于左下角
  591. if base == "sw" or base == "ws":
  592. # deviation_x, deviation_y = 0, int((img.height - img_1.height))
  593. p_x = value[0] + left
  594. p_y = image.height - (top_img.height + value[1] + down)
  595. # 基于左边,上下居中
  596. if base == "wc" or base == "cw":
  597. p_x = value[0] + left
  598. p_y = int((image.height - top_img.height) / 2) + value[1] + top
  599. # 基于右下角
  600. if base == "es" or base == "se":
  601. p_x = int(image.width - (top_img.width + value[0])) + left
  602. p_y = image.height - (top_img.height + value[1] + down) + top
  603. try:
  604. image.paste(top_img, box=(p_x, p_y), mask=top_img)
  605. except:
  606. image.paste(top_img, box=(p_x, p_y), mask=top_img.convert("RGBA"))
  607. return image
  608. @time_it
  609. def run(
  610. self,
  611. image_path,
  612. cut_image_path,
  613. out_path,
  614. image_deal_mode=0,
  615. image_index=99,
  616. out_pic_size=1024,
  617. is_logo=True,
  618. out_process_path_1=None,
  619. out_process_path_2=None,
  620. resize_mode=None,
  621. max_box=None,
  622. logo_path="",
  623. curve_mask=False,
  624. **kwargs,
  625. ): # im 为cv对象
  626. """
  627. image_path:原始图
  628. cut_image_path:抠图结果 与原始图尺寸相同
  629. out_path:输出主图路径
  630. image_deal_mode:图片处理模式,1表示需要镜像处理
  631. image_index:图片顺序索引
  632. out_pic_size:输出图片宽度大小
  633. is_logo=True 是否要添加logo水印
  634. out_process_path_1=None, 有阴影的图片,白底非透明
  635. out_process_path_2=None, 已抠图的图片
  636. resize_mode=0,1,2 主体缩小尺寸
  637. curve_mask 为True时,表示为对鞋曲线部分的mask,不做剪裁
  638. """
  639. if "output_queue" in kwargs:
  640. output_queue = kwargs["output_queue"]
  641. else:
  642. output_queue = None
  643. # image_deal_mode = 0#不翻转图像
  644. padding_800image = settings.getSysConfigs(
  645. "basic_configs", "padding_800image", 100
  646. )
  647. color_800image = settings.getSysConfigs(
  648. "basic_configs", "color_800image", "#FFFFFF"
  649. )
  650. rgb_color = settings.hex_to_rgb(color_800image)
  651. # ==========先进行剪切原图
  652. _s = time.time()
  653. orign_im = Image.open(image_path)
  654. print("242 need_time_1:{}".format(time.time() - _s))
  655. orign_x, orign_y = orign_im.size
  656. orign_im_cut = Image.open(cut_image_path) # 原始图的已扣图
  657. cut_image, new_box = get_mini_crop_img(img=orign_im_cut)
  658. im_shadow = orign_im.crop(new_box) # 切图
  659. new_x, new_y = im_shadow.size
  660. # ================自动色阶处理
  661. _s = time.time()
  662. image_mask_config = settings.getSysConfigs("basic_configs", "image_mask_config",
  663. {"mode": 0, "opacity": 0.5, "grenerate_main_pic_brightness": 254})
  664. print("阴影图处理参数===>>>", image_mask_config)
  665. image_mask_mode = image_mask_config.get("mode", 0)
  666. image_mask_opacity = float(image_mask_config.get("opacity", 0.5))
  667. image_mask_grenerate_main_pic_brightness = int(image_mask_config.get("grenerate_main_pic_brightness", 254))
  668. if image_mask_mode == 0:
  669. shadow_mask, config = self.get_mask_and_config(
  670. im_jpg=im_shadow, im_png=cut_image, curve_mask=curve_mask
  671. )
  672. elif image_mask_mode == 1:
  673. shadow_mask, config = self.get_mask_and_config_v3(im_jpg=im_shadow, im_png=cut_image, curve_mask=curve_mask,
  674. grenerate_main_pic_brightness=image_mask_grenerate_main_pic_brightness)
  675. elif image_mask_mode == 2:
  676. shadow_mask, config = self.get_mask_and_config_v4_online(ori_im_jpg=orign_im,
  677. ori_im_png=orign_im_cut,
  678. im_jpg=im_shadow,
  679. im_png=cut_image)
  680. else:
  681. shadow_mask, config = self.get_mask_and_config_v3(im_jpg=im_shadow, im_png=cut_image, curve_mask=curve_mask,
  682. grenerate_main_pic_brightness=image_mask_grenerate_main_pic_brightness)
  683. print("242 need_time_2:{}".format(time.time() - _s))
  684. shadow_mask = shadow_mask.resize(im_shadow.size)
  685. # =====抠图,形成新的阴影背景图=====
  686. _new_im_shadow = Image.new(
  687. mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255)
  688. )
  689. _new_im_shadow.paste(im_shadow, mask=shadow_mask) # 粘贴有阴影的地方
  690. # _new_im_shadow.show()
  691. _new_im_shadow = pil_to_cv2(_new_im_shadow)
  692. _new_im_shadow = cv2.cvtColor(_new_im_shadow, cv2.COLOR_BGR2GRAY)
  693. _new_im_shadow = levels_adjust(
  694. img=_new_im_shadow,
  695. Shadow=0,
  696. Midtones=config["Midtones"],
  697. Highlight=config["Highlight"],
  698. OutShadow=0,
  699. OutHighlight=255,
  700. Dim=3,
  701. )
  702. im_shadow = cv2_to_pil(_new_im_shadow)
  703. # ================处理阴影的亮度==================
  704. average_brightness = config["average_brightness"]
  705. if image_mask_mode == 0:
  706. if config["average_brightness"] < 180:
  707. # 调整阴影亮度
  708. backdrop_prepped = np.asfarray(
  709. Image.new(mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255))
  710. )
  711. im_shadow = im_shadow.convert("RGBA")
  712. source_prepped = np.asfarray(im_shadow)
  713. # im_shadow.show()
  714. opacity = (average_brightness - 30) / 160
  715. opacity = max(0.5, min(opacity, 1))
  716. print("阴影透明度:{}%".format(int(opacity * 100)))
  717. blended_np = multiply(
  718. backdrop_prepped, source_prepped, opacity=int(opacity * 100) / 100
  719. )
  720. im_shadow = Image.fromarray(np.uint8(blended_np)).convert("RGB")
  721. # im_shadow.show()
  722. else:
  723. backdrop_prepped = np.asfarray(
  724. Image.new(mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255))
  725. )
  726. im_shadow = im_shadow.convert("RGBA")
  727. source_prepped = np.asfarray(im_shadow)
  728. opacity_params = int(image_mask_opacity * 100)
  729. print("阴影透明度:{}%".format(opacity_params))
  730. blended_np = multiply(
  731. backdrop_prepped, source_prepped, opacity=opacity_params / 100
  732. )
  733. im_shadow = Image.fromarray(np.uint8(blended_np)).convert("RGB")
  734. # 边缘羽化处理
  735. im_shadow = self.filter_2(im_shadow=im_shadow, feather=100)
  736. # 把原图粘贴回去,避免色差
  737. im_shadow.paste(cut_image, (0, 0), mask=cut_image)
  738. # _new_im_shadow.show()
  739. # ===========处理其他====================
  740. # 保存带有阴影的底图,没有logo
  741. if out_process_path_1:
  742. out_image_1 = im_shadow.copy()
  743. if image_deal_mode == 1:
  744. out_image_1 = out_image_1.transpose(Image.FLIP_LEFT_RIGHT)
  745. self.saver.save_image(
  746. image=out_image_1, file_path=out_process_path_1, quality=100, dpi=(350, 350), _format="PNG"
  747. )
  748. # save_image_by_thread(image=out_image_1, out_path=out_process_path_1)
  749. # out_image_1.save(out_process_path_1)
  750. # 保存抠图结果,没有底图,没有logo
  751. if out_process_path_2:
  752. out_image_2 = cut_image.copy()
  753. if image_deal_mode == 1:
  754. out_image_2 = out_image_2.transpose(Image.FLIP_LEFT_RIGHT)
  755. self.saver.save_image(
  756. image=out_image_2, file_path=out_process_path_2, quality=100, dpi=(350, 350), _format="PNG"
  757. )
  758. # save_image_by_thread(image=out_image_2, out_path=out_process_path_2, save_mode="png")
  759. # out_image_2.save(out_process_path_2)
  760. # 不生成主图时直接退出
  761. if not out_path:
  762. return True
  763. if image_deal_mode == 1:
  764. # 翻转
  765. im_shadow = im_shadow.transpose(Image.FLIP_LEFT_RIGHT)
  766. cut_image = cut_image.transpose(Image.FLIP_LEFT_RIGHT)
  767. image_margin = int(padding_800image)
  768. bg_size = (1600, 1600)
  769. _offset_x, _offset_y = 0, 0
  770. scale_rate = 1
  771. # im_shadow.show()
  772. # =====================主图物体的缩放依据大小
  773. if image_margin is not None:
  774. _bbox = cut_image.getbbox()
  775. _x, _y = _bbox[0], _bbox[1]
  776. _w, _h = _bbox[2] - _bbox[0], _bbox[3] - _bbox[1]
  777. # 中心偏移量
  778. offset_x, offset_y = _x - (cut_image.width - _w) / 2, _y - (cut_image.height - _h) / 2,
  779. # print("中心偏移量:", offset_x, offset_y)
  780. # 透明底最小矩形
  781. scale_rate = self.get_scale(base_by_box=(bg_size[0] - image_margin * 2, bg_size[1] - image_margin * 2),
  782. image_size=(_w, _h))
  783. # 计算缩放比例,以及顶点相对位置
  784. # print("缩放比例:", scale_rate)
  785. # 偏移量
  786. _offset_x, _offset_y = offset_x * scale_rate, offset_y * scale_rate
  787. # print("偏移量:", _offset_x, _offset_y)
  788. # 阴影图缩放尺寸
  789. cut_image = to_resize(_im=cut_image, width=cut_image.width * scale_rate)
  790. im_shadow = to_resize(_im=im_shadow, width=im_shadow.width * scale_rate)
  791. else:
  792. if max_box:
  793. im_shadow = to_resize(_im=im_shadow, width=max_box[0], high=max_box[1])
  794. cut_image = to_resize(_im=cut_image, width=max_box[0], high=max_box[1])
  795. else:
  796. size_defind = 1400
  797. if resize_mode is None:
  798. im_shadow = to_resize(_im=im_shadow, width=size_defind, high=size_defind)
  799. cut_image = to_resize(_im=cut_image, width=size_defind, high=size_defind)
  800. elif resize_mode == 1:
  801. im_shadow = to_resize(_im=im_shadow, width=size_defind, high=size_defind)
  802. cut_image = to_resize(_im=cut_image, width=size_defind, high=size_defind)
  803. elif resize_mode == 2:
  804. # todo 兼容长筒靴等,将图片大小限制在一个指定的box内
  805. im_shadow = to_resize(_im=im_shadow, width=650)
  806. cut_image = to_resize(_im=cut_image, width=650)
  807. # 再次检查需要约束缩小到一定高度,适应长筒靴
  808. _im_x, _im_y = cut_image.size
  809. if _im_y > 1400:
  810. im_shadow = to_resize(_im=im_shadow, high=1400)
  811. cut_image = to_resize(_im=cut_image, high=1400)
  812. # 创建底层背景
  813. # 用户可设置的颜色值参数
  814. # image_bg = Image.new("RGB", bg_size, rgb_color)
  815. # image_bg = self.paste_img(image=image_bg, top_img=im_shadow, base="cc", value=(_offset_x * -1, _offset_y * -1))
  816. # image_bg = self.paste_img(image=image_bg, top_img=cut_image, base="cc", value=(_offset_x * -1, _offset_y * -1))
  817. image_bg = PictureProcessing("RGB", bg_size, rgb_color)
  818. image_bg = image_bg.to_overlay_pic_advance(mode="pixel",
  819. top_img=PictureProcessing(im=im_shadow),
  820. base="cc",
  821. value=(_offset_x * -1, _offset_y * -1),
  822. top_png_img=PictureProcessing(im=cut_image), )
  823. image_bg = image_bg.im
  824. image_bg_x, image_bg_y = image_bg.size
  825. image_x, image_y = im_shadow.size
  826. _x = int((image_bg_x - image_x) / 2)
  827. _y = int((image_bg_y - image_y) / 2)
  828. # image_bg.paste(im_shadow, (_x, _y))
  829. # image_bg.paste(cut_image, (_x, _y), cut_image) # 再叠加原图避免色差
  830. if "小苏" in settings.Company:
  831. # 所有主图加logo
  832. is_logo = True
  833. if is_logo:
  834. if not logo_path:
  835. logo_im = Image.new("RGBA", (1600, 1600), (0, 0, 0, 0))
  836. else:
  837. if os.path.exists(logo_path):
  838. logo_im = Image.open(logo_path)
  839. if logo_im.mode != 'RGBA':
  840. logo_im = logo_im.convert('RGBA')
  841. else:
  842. logo_im = Image.new("RGBA", (1600, 1600), (0, 0, 0, 0))
  843. try:
  844. image_bg.paste(logo_im, (0, 0), logo_im)
  845. except Exception as e:
  846. alpha_mask = logo_im.split()[3]
  847. image_bg.paste(logo_im, (0, 0), alpha_mask)
  848. out_pci_factor = float(
  849. 1
  850. if settings.getSysConfigs("basic_configs", "image_sharpening", "1") == ""
  851. else settings.getSysConfigs("basic_configs", "image_sharpening", "1")
  852. )
  853. if out_pci_factor > 1.0:
  854. print("图片锐化处理")
  855. image_bg = sharpen_image(image_bg, factor=out_pci_factor)
  856. out_pci_mode = "." + settings.getSysConfigs(
  857. "basic_configs", "image_out_format", "png"
  858. )
  859. for imageSize in out_pic_size:
  860. dot_index = out_path.rfind(".")
  861. if dot_index != -1:
  862. # 拆分文件路径和后缀
  863. file_without_suffix = out_path[:dot_index]
  864. suffix = out_path[dot_index + 1:]
  865. else:
  866. file_without_suffix = out_path
  867. suffix = ""
  868. # 单独拼接字符串示例
  869. image_size_int = int(imageSize)
  870. image_size_str = str(imageSize)
  871. new_file_path = f"{file_without_suffix}_{image_size_str}.{suffix}"
  872. image_bg = image_bg.resize(
  873. (image_size_int, image_size_int), resample=settings.RESIZE_IMAGE_MODE
  874. )
  875. if image_size_int < 3000:
  876. if out_pci_mode == ".jpg":
  877. self.saver.save_image(
  878. image=image_bg,
  879. file_path=new_file_path,
  880. save_mode="jpg",
  881. quality=100,
  882. dpi=(350, 350),
  883. _format="JPEG",
  884. )
  885. elif out_pci_mode == ".png":
  886. self.saver.save_image(
  887. image=image_bg,
  888. file_path=new_file_path,
  889. quality=100,
  890. dpi=(350, 350),
  891. _format="PNG",
  892. )
  893. else:
  894. new_format = out_pci_mode.split(".")[-1]
  895. self.saver.save_image(
  896. image=image_bg,
  897. file_path=new_file_path,
  898. save_mode=new_format,
  899. quality=100,
  900. dpi=(350, 350),
  901. _format=new_format,
  902. )
  903. else:
  904. new_format = out_pci_mode.split(".")[-1]
  905. self.saver.save_image(
  906. image=image_bg,
  907. file_path=new_file_path,
  908. save_mode=new_format,
  909. quality=100,
  910. dpi=(350, 350),
  911. _format=new_format,
  912. )
  913. # image_bg.save(out_path)
  914. # 在函数结束时使用更安全的关闭方式
  915. # 清理所有可能打开的图片对象
  916. for img_var in ['orign_im', 'cut_image', 'logo_im', 'out_image_1', 'out_image_2']:
  917. if img_var in locals():
  918. img = locals()[img_var]
  919. if hasattr(img, 'close'):
  920. try:
  921. img.close()
  922. except Exception as e:
  923. logger.warning(f"关闭图片对象 {img_var} 时出错: {e}")
  924. if output_queue is not None:
  925. output_queue.put(True)
  926. return True
  927. def get_scale(self, base_by_box, image_size):
  928. box_width, box_height = int(base_by_box[0]), int(base_by_box[1])
  929. width, height = image_size[0], image_size[1]
  930. if box_width / box_height < width / height:
  931. scale = box_width / width
  932. else:
  933. scale = box_height / height
  934. return scale