deal_one_image.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. import copy
  2. import os.path
  3. from .other.module_online_data import GetOnlineData
  4. import time
  5. from .other.log import MyLogger
  6. import os
  7. from .remove_bg_pixian import RemoveBgPiXian
  8. from PIL import Image
  9. from .other.pic import Picture
  10. from .other.remove_bg_ali import RemoveBgALi
  11. import cv2
  12. import numpy as np
  13. from middleware import UnicornException
  14. import math
  15. def disposeWH(image: Image, base_size=1024):
  16. width, height = image.size
  17. scale_width = int(width)
  18. scale_height = int(height)
  19. scale_rate = scale_width / scale_height
  20. if scale_width > base_size:
  21. scale_width = base_size
  22. scale_height = int(math.floor(scale_width / scale_rate))
  23. if scale_height > base_size:
  24. scale_height = base_size
  25. scale_width = int(math.floor(scale_height * scale_rate))
  26. return scale_width, scale_height
  27. class Base(object):
  28. def __init__(self, image_data, lock, windows, num, token):
  29. self.lock = lock
  30. self.image_data = image_data
  31. self.num = num
  32. self.windows = windows
  33. self.get_online_data = GetOnlineData(token)
  34. self.file_path = image_data["file_path"]
  35. self.file_name = image_data["file_name"]
  36. self.file = os.path.split(self.file_path)[1]
  37. self.is_once_data = {}
  38. self.logger = MyLogger().logger
  39. def add_log(self, text, _type="info"):
  40. self.logger.info(
  41. "第{}个,图片名称:{},内容:{}".format(self.num, self.file, text)
  42. )
  43. def show_image_info(self, data):
  44. data["file_path"] = self.file_path
  45. with self.lock:
  46. data = {
  47. "_type": "show_image_item_info",
  48. "data": data,
  49. }
  50. # self.windows.signal_data.emit(data)
  51. def send_info(
  52. self,
  53. text="",
  54. is_success=None,
  55. _type="show_text_browser",
  56. need_point_return=False,
  57. ):
  58. with self.lock:
  59. if is_success is not None:
  60. if is_success:
  61. processing_failed = 0
  62. processing_successfully = 1
  63. else:
  64. processing_failed = 1
  65. processing_successfully = 0
  66. # 分数返回
  67. if need_point_return:
  68. # 分数返回
  69. if self.is_once("add_point"):
  70. print(
  71. "第{}个,图片名称:{},内容:{}".format(
  72. self.num, self.file, "扣分返回"
  73. )
  74. )
  75. self.dispose_point(_type="add")
  76. self.refresh_times(cumulative_frequency_times_change=-1)
  77. pass
  78. # self.windows.signal_data.emit({"_type": "schedule",
  79. # "data": {"processing_failed": processing_failed,
  80. # "processing_successfully": processing_successfully,
  81. # }})
  82. if text:
  83. data = {
  84. "_type": "show_text_browser",
  85. "data": text,
  86. }
  87. # self.windows.signal_data.emit(data)
  88. def check_path(self, _path):
  89. if not os.path.exists(_path):
  90. os.mkdir(_path)
  91. return True
  92. def is_once(self, key):
  93. if key not in self.is_once_data:
  94. self.is_once_data[key] = 0
  95. return True
  96. return False
  97. def refresh_times(self, cumulative_frequency_times_change=0):
  98. data = {
  99. "_type": "refresh_times",
  100. "data": {
  101. "cumulative_frequency_times_change": cumulative_frequency_times_change
  102. },
  103. }
  104. # self.windows.signal_data.emit(data)
  105. pass
  106. def dispose_point(self, _type):
  107. n = 3
  108. while n:
  109. n -= 1
  110. try:
  111. _r = self.get_online_data.dispose_point(_type)
  112. balance = _r["data"]["balance"]
  113. return True
  114. except:
  115. time.sleep(0.5)
  116. continue
  117. return False
  118. class DealOneImage(Base):
  119. def __init__(self, image_data, lock, windows, num, token):
  120. super().__init__(image_data, lock, windows, num, token)
  121. self.r_pixian = RemoveBgPiXian()
  122. self.windows = windows
  123. def run(self, image_data, upload_pic_dict):
  124. self.file_path = image_data["file_path"]
  125. self.file = os.path.split(self.file_path)[1]
  126. self.root_path = image_data["root_path"]
  127. self.file_name = image_data["file_name"]
  128. cut_status = False
  129. success_path = ""
  130. # 直接调用抠图
  131. # 1、增加获取key,2、key需要加密、3、429报错 重试再来拿一个KEY
  132. self.add_log("开始处理")
  133. remaining_times_obj = self.get_online_data.get_cutout_image_times()
  134. if remaining_times_obj == False:
  135. raise UnicornException("获取可用次数失败,请检查是否登录")
  136. remaining_times = remaining_times_obj.get("balance")
  137. print("remaining_times", remaining_times)
  138. if remaining_times <= 0:
  139. raise UnicornException("次数不足,处理失败")
  140. # 检查图片上传是否有结束
  141. n = 60
  142. if self.file_path not in upload_pic_dict:
  143. # 不扣图
  144. raise UnicornException("处理失败")
  145. s = time.time()
  146. image_deal_info = upload_pic_dict[self.file_path]["image_deal_info"]
  147. original_im = upload_pic_dict[self.file_path]["_im"]
  148. self.add_log("抠图中")
  149. # 抠图
  150. out_root_path = "{}/已扣图".format(self.root_path)
  151. self.check_path(out_root_path)
  152. try:
  153. remaining_times_obj = self.get_online_data.get_cutout_image_times()
  154. if remaining_times_obj == False:
  155. raise UnicornException("获取可用次数失败,请检查是否登录")
  156. balance = remaining_times_obj.get("balance")
  157. self.add_log("查询balance:{}成功".format(balance))
  158. if balance <= 0:
  159. self.add_log("次数不足,处理失败")
  160. raise UnicornException("次数不足,处理失败")
  161. except:
  162. self.add_log("查询balance失败")
  163. raise UnicornException("查询balance失败")
  164. n = 0
  165. second_cut_image = self.runPiXian(n, original_im=original_im)
  166. if second_cut_image is None:
  167. raise UnicornException("抠图失败")
  168. try:
  169. out_path = "{}/{}.png".format(out_root_path, self.file_name)
  170. if image_deal_info["二次抠图是否缩放"]:
  171. self.add_log(text="图片尺寸进行还原")
  172. original_im = image_deal_info["抠图扩边后PIL对象"]
  173. second_cut_image = self.picture_resize_to_original(
  174. second_cut_image, original_im
  175. )
  176. # 创建空白图片并粘贴回去
  177. _img_im = Image.new(
  178. mode="RGBA", size=image_deal_info["原始图片大小"], color=(0, 0, 0, 0)
  179. )
  180. _img_im.paste(
  181. second_cut_image,
  182. box=(
  183. image_deal_info["抠图扩边后位置"][0],
  184. image_deal_info["抠图扩边后位置"][1],
  185. ),
  186. )
  187. if self.windows.output_type == 1:
  188. print("处理白底", "ssssss")
  189. background = Image.new("RGBA", _img_im.size, (255, 255, 255, 255))
  190. _img_im = Image.alpha_composite(background, _img_im)
  191. resize_width, resize_height = disposeWH(_img_im.convert("RGB"), 1024)
  192. _img_im = _img_im.resize((resize_width, resize_height))
  193. new_bg = Image.new("RGB", (1024, 1024), (255, 255, 255))
  194. _img_im = _img_im.convert("RGB").copy()
  195. width, height = _img_im.size
  196. offset = ((1024 - width) // 2, (1024 - height) // 2)
  197. new_bg.paste(_img_im, offset)
  198. _img_im = new_bg.copy()
  199. cut_status = True
  200. success_path = out_path
  201. _img_im.save(out_path)
  202. except BaseException as e:
  203. text = "{} 图片处理错误,代码44".format(e)
  204. self.add_log(text)
  205. self.send_info(text=text, is_success=False, need_point_return=True)
  206. cut_status = False
  207. self.add_log(text="本张耗时:{}".format(time.time() - s))
  208. self.send_info(text="抠图已完成", is_success=True)
  209. image_data["status"] = cut_status
  210. image_data["success_path"] = success_path
  211. return image_data
  212. def runPiXian(self, num, original_im):
  213. """执行pixian抠图"""
  214. num += 1
  215. data = self.get_online_data.get_key_secret()
  216. key = (data["api_info"]["api_key"], data["api_info"]["api_serect"])
  217. self.add_log("查询key成功")
  218. if not key:
  219. raise UnicornException("处理失败,请联系管理员")
  220. if self.is_once("sub_point"):
  221. # 调用扣分
  222. self.refresh_times(cumulative_frequency_times_change=1)
  223. f = self.dispose_point(_type="sub")
  224. if not f:
  225. self.add_log(text="多次获取调用余额扣减失败")
  226. raise UnicornException("多次获取调用余额扣减失败")
  227. pixian_cutout_data = self.r_pixian.run_by_image_im(original_im, key)
  228. if pixian_cutout_data["status_code"] == 200:
  229. second_cut_image = pixian_cutout_data["im"]
  230. self.add_log(text="调用抠图完成")
  231. return second_cut_image
  232. elif pixian_cutout_data["status_code"] == 402:
  233. if num >= 2:
  234. self.cutoutFail(pixian_cutout_data)
  235. raise UnicornException("多次获取调用余额扣减失败")
  236. self.add_log(
  237. text="抠图失败:{},延迟6秒".format(pixian_cutout_data["status_code"])
  238. )
  239. time.sleep(6)
  240. self.runPiXian(num, original_im=original_im)
  241. elif pixian_cutout_data["status_code"] == 429:
  242. if num >= 2:
  243. self.cutoutFail(pixian_cutout_data)
  244. return None
  245. self.add_log(
  246. text="抠图失败:{},延迟10秒".format(pixian_cutout_data["status_code"])
  247. )
  248. time.sleep(10)
  249. self.runPiXian(num, original_im=original_im)
  250. else:
  251. _data = {
  252. "text": "出错/超时",
  253. "info": "抠图异常",
  254. }
  255. self.show_image_info(_data)
  256. self.get_online_data.dispose_point("add")
  257. if "message" in pixian_cutout_data:
  258. text = "抠图异常,code:{},message:{}".format(
  259. pixian_cutout_data["status_code"], pixian_cutout_data["message"]
  260. )
  261. else:
  262. text = "抠图异常,code:{}".format(pixian_cutout_data["status_code"])
  263. self.add_log(text)
  264. return None
  265. # if self.windows.output_type == 1:
  266. # resize_width, resize_height = disposeWH(
  267. # second_cut_image.convert("RGB"), 1024
  268. # )
  269. # second_cut_image = second_cut_image.resize((resize_width, resize_height))
  270. # new_bg = Image.new("RGB", (1024, 1024), (255, 255, 255))
  271. # second_cut_image = second_cut_image.convert("RGB").copy()
  272. # width, height = second_cut_image.size
  273. # offset = ((1024 - width) // 2, (1024 - height) // 2)
  274. # new_bg.paste(second_cut_image, offset)
  275. # second_cut_image = new_bg.copy()
  276. return second_cut_image
  277. def cutoutFail(self, pixian_cutout_data):
  278. """处理失败"""
  279. _data = {
  280. "text": "出错/超时",
  281. "info": "多次抠图失败:{}".format(pixian_cutout_data["status_code"]),
  282. }
  283. self.show_image_info(_data)
  284. self.add_log(text="多次抠图失败:{}".format(pixian_cutout_data["status_code"]))
  285. self.send_info(
  286. text="处理失败,请联系管理员",
  287. is_success=False,
  288. need_point_return=True,
  289. )
  290. def picture_resize_to_original(self, _img, original_im):
  291. """
  292. Parameters
  293. ----------
  294. _img 需要还原的PIL对象
  295. original_im 原图对象
  296. Returns
  297. -------
  298. """
  299. # 将抠图结果转成mask
  300. # 将抠图结果放大到原始图大小
  301. _img = _img.resize(original_im.size)
  302. new_big_mask = Image.new("RGB", _img.size, (0, 0, 0))
  303. white = Image.new("RGB", _img.size, (255, 255, 255))
  304. new_big_mask.paste(white, mask=_img.split()[3])
  305. # ---------制作选区缩小的mask
  306. mask = cv2.cvtColor(
  307. np.asarray(new_big_mask), cv2.COLOR_BGR2GRAY
  308. ) # 将PIL 格式转换为 CV对象
  309. mask[mask != 255] = 0
  310. # 黑白反转
  311. # mask = 255 - mask
  312. # 选区缩小10
  313. kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
  314. erode_im = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
  315. # -------再进行抠图处理
  316. mask = Image.fromarray(
  317. cv2.cvtColor(erode_im, cv2.COLOR_GRAY2RGBA)
  318. ) # CV 对象转 PIL
  319. transparent_im = Image.new("RGBA", original_im.size, (0, 0, 0, 0))
  320. transparent_im.paste(original_im, (0, 0), mask.convert("L"))
  321. # 上述抠图结果进行拼接
  322. _img.paste(transparent_im, (0, 0), transparent_im)
  323. return _img
  324. class DealOneImageBeforehand(Base):
  325. def __init__(self, image_data, lock, windows, num, token):
  326. super().__init__(image_data, lock, windows, num, token)
  327. self.r_ali = RemoveBgALi()
  328. def run(self, upload_pic_dict):
  329. # 增加阿里调用报错的重试机制
  330. image_deal_info = {}
  331. try:
  332. cut_image, image_deal_info = self.get_image_cut()
  333. except BaseException as e:
  334. raise UnicornException("上传出错")
  335. upload_pic_dict[self.file_path] = {
  336. "image_deal_info": image_deal_info,
  337. "_im": cut_image,
  338. }
  339. return upload_pic_dict
  340. def get_image_cut(self):
  341. original_pic = Picture(self.file_path)
  342. original_pic.im = self.get_image_orientation(original_pic.im)
  343. original_pic.x, original_pic.y = original_pic.im.size
  344. original_pic.im = original_pic.im.convert("RGB")
  345. image_deal_info = {}
  346. image_deal_info["原始图片大小"] = (original_pic.x, original_pic.y)
  347. if original_pic.x * original_pic.y < 1000000:
  348. cut_image = original_pic.im
  349. image_deal_info["抠图扩边后图片大小"] = cut_image.size
  350. image_deal_info["二次抠图是否缩放"] = False
  351. image_deal_info["抠图扩边后位置"] = (0, 0, original_pic.x, original_pic.y)
  352. else:
  353. self.add_log("开始预抠图处理")
  354. cut_image = self.r_ali.get_image_cut(
  355. file_path=None, out_file_path=None, original_im=original_pic.im
  356. )
  357. self.add_log("预抠图处理结束")
  358. x1, y1, x2, y2 = cut_image.getbbox()
  359. image_deal_info["鞋子原始位置"] = (x1, y1, x2, y2)
  360. o_w, o_h = cut_image.size
  361. image_deal_info["鞋子原始抠图后大小"] = (o_w, o_h)
  362. # 扩边处理
  363. _w, _h = x2 - x1, y2 - y1
  364. out_px = 0.025
  365. _w, _h = int(out_px * _w), int(out_px * _h)
  366. n_x1, n_y1, n_x2, n_y2 = x1 - _w, y1 - _h, x2 + _w, y2 + _h
  367. if n_x1 < 0:
  368. n_x1 = 0
  369. if n_y1 < 0:
  370. n_y1 = 0
  371. if n_x2 > o_w:
  372. n_x2 = o_w
  373. if n_y2 > o_h:
  374. n_y2 = o_h
  375. image_deal_info["抠图扩边后位置"] = (n_x1, n_y1, n_x2, n_y2)
  376. cut_image = original_pic.im.crop(image_deal_info["抠图扩边后位置"])
  377. image_deal_info["抠图扩边后图片大小"] = cut_image.size
  378. x, y = image_deal_info["抠图扩边后图片大小"]
  379. # max_size = 32000000
  380. max_size = 12000000
  381. if x * y > max_size:
  382. r = max_size / (x * y)
  383. size = (int(x * r), int(y * r))
  384. self.add_log(
  385. text="图片进行压缩,压缩前:{},压缩后:{}".format(
  386. image_deal_info["抠图扩边后图片大小"], size
  387. )
  388. )
  389. image_deal_info["抠图扩边后PIL对象"] = copy.deepcopy(cut_image)
  390. cut_image = cut_image.resize(size=size)
  391. # print(cut_image.size)
  392. # print(image_deal_info["抠图扩边后PIL对象"].size)
  393. image_deal_info["二次抠图是否缩放"] = True
  394. else:
  395. image_deal_info["二次抠图是否缩放"] = False
  396. return cut_image, image_deal_info
  397. def get_image_cut_noraml(self, image_data):
  398. """普通模式抠图"""
  399. self.file_path = image_data["file_path"]
  400. self.file = os.path.split(self.file_path)[1]
  401. self.root_path = image_data["root_path"]
  402. self.file_name = image_data["file_name"]
  403. original_pic = Picture(self.file_path)
  404. original_pic.im = self.get_image_orientation(original_pic.im)
  405. original_pic.x, original_pic.y = original_pic.im.size
  406. original_pic.im = original_pic.im.convert("RGB")
  407. image_deal_info = {}
  408. image_deal_info["原始图片大小"] = (original_pic.x, original_pic.y)
  409. self.add_log("开始预抠图处理")
  410. remaining_times_obj = self.get_online_data.get_cutout_image_times()
  411. if remaining_times_obj == False:
  412. raise UnicornException("获取可用次数失败,请检查是否登录")
  413. remaining_times = remaining_times_obj.get("balance")
  414. if remaining_times <= 0:
  415. raise UnicornException("次数不足,处理失败")
  416. self.get_online_data.dispose_point("sub")
  417. cut_image = self.r_ali.get_image_cut(
  418. file_path=None, out_file_path=None, original_im=original_pic.im
  419. )
  420. success_path = ""
  421. if cut_image is None:
  422. cut_status = False
  423. self.get_online_data.dispose_point("add")
  424. else:
  425. self.add_log("预抠图处理结束")
  426. cut_status = True
  427. save_root_path = "{}/已扣图".format(self.root_path)
  428. self.check_path(save_root_path)
  429. if self.windows.output_type == 1:
  430. background = Image.new("RGBA", cut_image.size, (255, 255, 255, 255))
  431. cut_image = Image.alpha_composite(background, cut_image)
  432. resize_width, resize_height = disposeWH(cut_image.convert("RGB"), 1024)
  433. cut_image = cut_image.resize((resize_width, resize_height))
  434. new_bg = Image.new("RGB", (1024, 1024), (255, 255, 255))
  435. cut_image = cut_image.convert("RGB").copy()
  436. width, height = cut_image.size
  437. offset = ((1024 - width) // 2, (1024 - height) // 2)
  438. new_bg.paste(cut_image, offset)
  439. cut_image = new_bg.copy()
  440. success_path = "{}/{}.png".format(save_root_path, self.file_name)
  441. cut_image.save(success_path)
  442. image_data["status"] = cut_status
  443. image_data["success_path"] = success_path
  444. return image_data
  445. def get_image_cut_cloths(self, image_data):
  446. self.file_path = image_data["file_path"]
  447. self.file = os.path.split(self.file_path)[1]
  448. self.root_path = image_data["root_path"]
  449. self.file_name = image_data["file_name"]
  450. original_pic = Picture(self.file_path)
  451. original_pic.im = self.get_image_orientation(original_pic.im)
  452. original_pic.x, original_pic.y = original_pic.im.size
  453. original_pic.im = original_pic.im.convert("RGB")
  454. image_deal_info = {}
  455. image_deal_info["原始图片大小"] = (original_pic.x, original_pic.y)
  456. self.add_log("开始预抠图处理")
  457. remaining_times_obj = self.get_online_data.get_cutout_image_times()
  458. if remaining_times_obj == False:
  459. raise UnicornException("获取可用次数失败,请检查是否登录")
  460. remaining_times = remaining_times_obj.get("balance")
  461. if remaining_times <= 0:
  462. raise UnicornException("次数不足,处理失败")
  463. self.get_online_data.dispose_point("sub")
  464. cut_images = self.r_ali.get_image_cut_cloths(
  465. file_path=None, out_file_path=None, original_im=original_pic.im
  466. )
  467. success_path = ""
  468. if cut_images is None or len(cut_images) == 0:
  469. cut_status = False
  470. self.get_online_data.dispose_point("add")
  471. else:
  472. self.add_log("预抠图处理结束")
  473. cut_status = True
  474. save_root_path = "{}/已扣图".format(self.root_path)
  475. self.check_path(save_root_path)
  476. save_file_path = "{}/{}".format(save_root_path, self.file_name)
  477. self.check_path(save_file_path)
  478. success_path = save_file_path
  479. for idx, item in enumerate(cut_images):
  480. cn_name = item.get("cn_name")
  481. image_obj = item.get("image_obj")
  482. if self.windows.output_type == 1:
  483. background = Image.new("RGBA", image_obj.size, (255, 255, 255, 255))
  484. image_obj = Image.alpha_composite(background, image_obj)
  485. resize_width, resize_height = disposeWH(
  486. image_obj.convert("RGB"), 1024
  487. )
  488. image_obj = image_obj.resize((resize_width, resize_height))
  489. new_bg = Image.new("RGB", (1024, 1024), (255, 255, 255))
  490. image_obj = image_obj.convert("RGB").copy()
  491. width, height = image_obj.size
  492. offset = ((1024 - width) // 2, (1024 - height) // 2)
  493. new_bg.paste(image_obj, offset)
  494. image_obj = new_bg.copy()
  495. image_obj.save("{}/{}.png".format(save_file_path, cn_name))
  496. image_data["status"] = cut_status
  497. image_data["success_path"] = success_path
  498. return image_data
  499. def get_image_orientation(self, img):
  500. # 获取EXIF数据
  501. exif = img._getexif()
  502. if exif is not None:
  503. # EXIF标签274对应的是Orientation
  504. orientation = exif.get(0x0112)
  505. if orientation == 2:
  506. # 水平翻转
  507. img = img.transpose(Image.FLIP_LEFT_RIGHT)
  508. elif orientation == 3:
  509. # 旋转180度
  510. img = img.rotate(180, expand=True)
  511. elif orientation == 4:
  512. # 垂直翻转
  513. img = img.transpose(Image.FLIP_TOP_BOTTOM)
  514. elif orientation == 5:
  515. # 水平翻转后顺时针旋转90度
  516. img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270)
  517. elif orientation == 6:
  518. # 顺时针旋转90度
  519. img = img.transpose(Image.ROTATE_270)
  520. elif orientation == 7:
  521. # 水平翻转后逆时针旋转90度
  522. img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90)
  523. elif orientation == 8:
  524. # 逆时针旋转90度
  525. img = img.transpose(Image.ROTATE_90)
  526. else:
  527. print("没有EXIF数据或没有方向信息")
  528. orientation = 1
  529. return img