deal_one_image.py 22 KB

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