grenerate_main_image_test.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  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.paste(cut_image, (0, 0), mask=cut_image)
  736. # _new_im_shadow.show()
  737. # ===========处理其他====================
  738. # 保存带有阴影的底图,没有logo
  739. if out_process_path_1:
  740. out_image_1 = im_shadow.copy()
  741. if image_deal_mode == 1:
  742. out_image_1 = out_image_1.transpose(Image.FLIP_LEFT_RIGHT)
  743. self.saver.save_image(
  744. image=out_image_1, file_path=out_process_path_1, quality=100, dpi=(350, 350), _format="PNG"
  745. )
  746. # save_image_by_thread(image=out_image_1, out_path=out_process_path_1)
  747. # out_image_1.save(out_process_path_1)
  748. # 保存抠图结果,没有底图,没有logo
  749. if out_process_path_2:
  750. out_image_2 = cut_image.copy()
  751. if image_deal_mode == 1:
  752. out_image_2 = out_image_2.transpose(Image.FLIP_LEFT_RIGHT)
  753. self.saver.save_image(
  754. image=out_image_2, file_path=out_process_path_2, quality=100, dpi=(350, 350), _format="PNG"
  755. )
  756. # save_image_by_thread(image=out_image_2, out_path=out_process_path_2, save_mode="png")
  757. # out_image_2.save(out_process_path_2)
  758. # 不生成主图时直接退出
  759. if not out_path:
  760. return True
  761. if image_deal_mode == 1:
  762. # 翻转
  763. im_shadow = im_shadow.transpose(Image.FLIP_LEFT_RIGHT)
  764. cut_image = cut_image.transpose(Image.FLIP_LEFT_RIGHT)
  765. image_margin = int(padding_800image)
  766. bg_size = (1600, 1600)
  767. _offset_x, _offset_y = 0, 0
  768. scale_rate = 1
  769. # im_shadow.show()
  770. # =====================主图物体的缩放依据大小
  771. if image_margin is not None:
  772. _bbox = cut_image.getbbox()
  773. _x, _y = _bbox[0], _bbox[1]
  774. _w, _h = _bbox[2] - _bbox[0], _bbox[3] - _bbox[1]
  775. # 中心偏移量
  776. offset_x, offset_y = _x - (cut_image.width - _w) / 2, _y - (cut_image.height - _h) / 2,
  777. # print("中心偏移量:", offset_x, offset_y)
  778. # 透明底最小矩形
  779. scale_rate = self.get_scale(base_by_box=(bg_size[0] - image_margin * 2, bg_size[1] - image_margin * 2),
  780. image_size=(_w, _h))
  781. # 计算缩放比例,以及顶点相对位置
  782. # print("缩放比例:", scale_rate)
  783. # 偏移量
  784. _offset_x, _offset_y = offset_x * scale_rate, offset_y * scale_rate
  785. # print("偏移量:", _offset_x, _offset_y)
  786. # 阴影图缩放尺寸
  787. cut_image = to_resize(_im=cut_image, width=cut_image.width * scale_rate)
  788. im_shadow = to_resize(_im=im_shadow, width=im_shadow.width * scale_rate)
  789. else:
  790. if max_box:
  791. im_shadow = to_resize(_im=im_shadow, width=max_box[0], high=max_box[1])
  792. cut_image = to_resize(_im=cut_image, width=max_box[0], high=max_box[1])
  793. else:
  794. size_defind = 1400
  795. if resize_mode is None:
  796. im_shadow = to_resize(_im=im_shadow, width=size_defind, high=size_defind)
  797. cut_image = to_resize(_im=cut_image, width=size_defind, high=size_defind)
  798. elif resize_mode == 1:
  799. im_shadow = to_resize(_im=im_shadow, width=size_defind, high=size_defind)
  800. cut_image = to_resize(_im=cut_image, width=size_defind, high=size_defind)
  801. elif resize_mode == 2:
  802. # todo 兼容长筒靴等,将图片大小限制在一个指定的box内
  803. im_shadow = to_resize(_im=im_shadow, width=650)
  804. cut_image = to_resize(_im=cut_image, width=650)
  805. # 再次检查需要约束缩小到一定高度,适应长筒靴
  806. _im_x, _im_y = cut_image.size
  807. if _im_y > 1400:
  808. im_shadow = to_resize(_im=im_shadow, high=1400)
  809. cut_image = to_resize(_im=cut_image, high=1400)
  810. # 创建底层背景
  811. # 用户可设置的颜色值参数
  812. # image_bg = Image.new("RGB", bg_size, rgb_color)
  813. # image_bg = self.paste_img(image=image_bg, top_img=im_shadow, base="cc", value=(_offset_x * -1, _offset_y * -1))
  814. # image_bg = self.paste_img(image=image_bg, top_img=cut_image, base="cc", value=(_offset_x * -1, _offset_y * -1))
  815. image_bg = PictureProcessing("RGB", bg_size, rgb_color)
  816. image_bg = image_bg.to_overlay_pic_advance(mode="pixel",
  817. top_img=PictureProcessing(im=im_shadow),
  818. base="cc",
  819. value=(_offset_x * -1, _offset_y * -1),
  820. top_png_img=PictureProcessing(im=cut_image), )
  821. image_bg = image_bg.im
  822. image_bg_x, image_bg_y = image_bg.size
  823. image_x, image_y = im_shadow.size
  824. _x = int((image_bg_x - image_x) / 2)
  825. _y = int((image_bg_y - image_y) / 2)
  826. # image_bg.paste(im_shadow, (_x, _y))
  827. # image_bg.paste(cut_image, (_x, _y), cut_image) # 再叠加原图避免色差
  828. if "小苏" in settings.Company:
  829. # 所有主图加logo
  830. is_logo = True
  831. if is_logo:
  832. if not logo_path:
  833. logo_im = Image.new("RGBA", (1600, 1600), (0, 0, 0, 0))
  834. else:
  835. if os.path.exists(logo_path):
  836. logo_im = Image.open(logo_path)
  837. if logo_im.mode != 'RGBA':
  838. logo_im = logo_im.convert('RGBA')
  839. else:
  840. logo_im = Image.new("RGBA", (1600, 1600), (0, 0, 0, 0))
  841. try:
  842. image_bg.paste(logo_im, (0, 0), logo_im)
  843. except Exception as e:
  844. alpha_mask = logo_im.split()[3]
  845. image_bg.paste(logo_im, (0, 0), alpha_mask)
  846. out_pci_factor = float(
  847. 1
  848. if settings.getSysConfigs("basic_configs", "image_sharpening", "1") == ""
  849. else settings.getSysConfigs("basic_configs", "image_sharpening", "1")
  850. )
  851. if out_pci_factor > 1.0:
  852. print("图片锐化处理")
  853. image_bg = sharpen_image(image_bg, factor=out_pci_factor)
  854. out_pci_mode = "." + settings.getSysConfigs(
  855. "basic_configs", "image_out_format", "png"
  856. )
  857. for imageSize in out_pic_size:
  858. dot_index = out_path.rfind(".")
  859. if dot_index != -1:
  860. # 拆分文件路径和后缀
  861. file_without_suffix = out_path[:dot_index]
  862. suffix = out_path[dot_index + 1:]
  863. else:
  864. file_without_suffix = out_path
  865. suffix = ""
  866. # 单独拼接字符串示例
  867. image_size_int = int(imageSize)
  868. image_size_str = str(imageSize)
  869. new_file_path = f"{file_without_suffix}_{image_size_str}.{suffix}"
  870. image_bg = image_bg.resize(
  871. (image_size_int, image_size_int), resample=settings.RESIZE_IMAGE_MODE
  872. )
  873. if image_size_int < 3000:
  874. if out_pci_mode == ".jpg":
  875. self.saver.save_image(
  876. image=image_bg,
  877. file_path=new_file_path,
  878. save_mode="jpg",
  879. quality=100,
  880. dpi=(350, 350),
  881. _format="JPEG",
  882. )
  883. elif out_pci_mode == ".png":
  884. self.saver.save_image(
  885. image=image_bg,
  886. file_path=new_file_path,
  887. quality=100,
  888. dpi=(350, 350),
  889. _format="PNG",
  890. )
  891. else:
  892. new_format = out_pci_mode.split(".")[-1]
  893. self.saver.save_image(
  894. image=image_bg,
  895. file_path=new_file_path,
  896. save_mode=new_format,
  897. quality=100,
  898. dpi=(350, 350),
  899. _format=new_format,
  900. )
  901. else:
  902. new_format = out_pci_mode.split(".")[-1]
  903. self.saver.save_image(
  904. image=image_bg,
  905. file_path=new_file_path,
  906. save_mode=new_format,
  907. quality=100,
  908. dpi=(350, 350),
  909. _format=new_format,
  910. )
  911. # image_bg.save(out_path)
  912. # 在函数结束时使用更安全的关闭方式
  913. # 清理所有可能打开的图片对象
  914. for img_var in ['orign_im', 'cut_image', 'logo_im', 'out_image_1', 'out_image_2']:
  915. if img_var in locals():
  916. img = locals()[img_var]
  917. if hasattr(img, 'close'):
  918. try:
  919. img.close()
  920. except Exception as e:
  921. logger.warning(f"关闭图片对象 {img_var} 时出错: {e}")
  922. if output_queue is not None:
  923. output_queue.put(True)
  924. return True
  925. def get_scale(self, base_by_box, image_size):
  926. box_width, box_height = int(base_by_box[0]), int(base_by_box[1])
  927. width, height = image_size[0], image_size[1]
  928. if box_width / box_height < width / height:
  929. scale = box_width / width
  930. else:
  931. scale = box_height / height
  932. return scale