grenerate_main_image_test.py 43 KB

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