deal_one_image.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import asyncio
  2. import copy
  3. import os.path
  4. # from module.other.module_online_data import GetOnlineData
  5. from .online_request.module_online_data import GetOnlineDataHLM
  6. import time
  7. from logger import logger
  8. import os
  9. from PIL import Image
  10. # from module.other.remove_bg_ali import RemoveBgALi
  11. from .remove_bg_pixian import RemoveBgPiXian
  12. from .remove_bg_ali import RemoveBgALi, Picture
  13. import cv2
  14. import numpy as np
  15. import settings
  16. import math
  17. class Base(object):
  18. def __init__(self, image_data, lock, windows, num, token):
  19. self.lock = lock
  20. self.windows = windows
  21. self.image_data = image_data
  22. self.num = num
  23. self.get_online_data = GetOnlineDataHLM(token)
  24. self.file_path = image_data["file_path"]
  25. self.file_name = image_data["file_name"]
  26. self.file = os.path.split(self.file_path)[1]
  27. self.is_once_data = {}
  28. self.logger = logger
  29. def add_log(self, text, _type="info"):
  30. self.logger.info(
  31. "第{}个,图片名称:{},内容:{}".format(self.num, self.file, text)
  32. )
  33. def send_info(
  34. self, text="", is_success=None, _type="show_info", need_point_return=False
  35. ):
  36. with self.lock:
  37. if is_success is not None:
  38. if is_success:
  39. self.windows.remaining_times = self.windows.remaining_times - 1
  40. else:
  41. # 分数返回
  42. if need_point_return:
  43. # 分数返回
  44. if self.is_once("add_point"):
  45. print(
  46. "第{}个,图片名称:{},内容:{}".format(
  47. self.num, self.file, "扣分返回"
  48. )
  49. )
  50. self.dispose_point(_type="add")
  51. self.windows.remaining_times = (
  52. self.windows.remaining_times + 1
  53. )
  54. if text:
  55. data = {
  56. "_type": _type,
  57. "data": text,
  58. }
  59. # self.windows.send_sign(data)
  60. print("精细化抠图 send info ", data)
  61. def refresh_times(self, cumulative_frequency_times_change):
  62. if cumulative_frequency_times_change > 0:
  63. self.windows.remaining_times = self.windows.remaining_times - 1
  64. def check_path(self, _path):
  65. if not os.path.exists(_path):
  66. os.mkdir(_path)
  67. return True
  68. def is_once(self, key):
  69. if key not in self.is_once_data:
  70. self.is_once_data[key] = 0
  71. return True
  72. return False
  73. def dispose_point(self, _type):
  74. n = 3
  75. while n:
  76. n -= 1
  77. try:
  78. _r = self.get_online_data.dispose_point(_type)
  79. balance = _r["data"]["balance"]
  80. return True
  81. except:
  82. time.sleep(0.5)
  83. continue
  84. return False
  85. class DealOneImage(Base):
  86. def __init__(self, image_data, lock, windows, num, token):
  87. super().__init__(image_data, lock, windows, num, token)
  88. self.image_data = image_data
  89. self.lock = lock
  90. self.windows = windows
  91. self.num = num
  92. self.file_path = image_data["file_path"]
  93. self.file = os.path.split(self.file_path)[1]
  94. self.r_pixian = RemoveBgPiXian()
  95. self.file_name = image_data["file_name"]
  96. self.out_path = image_data["out_path"]
  97. def run(self):
  98. # 直接调用抠图
  99. time.sleep(0.01)
  100. # 1、增加获取key,2、key需要加密、3、429报错 重试再来拿一个KEY
  101. self.add_log("开始处理")
  102. self.send_info(text="{} 处理中".format(self.file_name))
  103. if self.windows.remaining_times <= 0:
  104. self.send_info(text="次数不足,处理失败", is_success=False)
  105. return
  106. # 检查图片上传是否有结束
  107. n = 60
  108. while 1:
  109. if self.windows.state != 1:
  110. return
  111. n -= 1
  112. if self.file_path in self.windows.upload_pic_dict:
  113. break
  114. else:
  115. time.sleep(1)
  116. if n <= 0:
  117. self.send_info(text="{} 处理超时", is_success=False)
  118. return
  119. continue
  120. print("图片已上传============》》》》》》")
  121. s = time.time()
  122. # print(self.windows.upload_pic_dict,self.windows.upload_pic_dict[self.file_path], self.file_path)
  123. _flag = self.windows.upload_pic_dict[self.file_path]["flag"]
  124. if not _flag:
  125. self.add_log("未查到上传的图片地址")
  126. self.send_info(text="{} 上传错误", is_success=False)
  127. return
  128. image_deal_info = self.windows.upload_pic_dict[self.file_path][
  129. "image_deal_info"
  130. ]
  131. original_im = self.windows.upload_pic_dict[self.file_path]["_im"]
  132. self.add_log("抠图中")
  133. # with self.lock:
  134. self.windows.is_upload_pic_num -= 1
  135. try:
  136. balance = self.get_online_data.get_cutout_image_times()["balance"]
  137. self.add_log("查询balance:{}成功".format(balance))
  138. if balance <= 0:
  139. self.add_log("次数不足,处理失败")
  140. self.send_info(text="次数不足,处理失败", is_success=False)
  141. return
  142. except:
  143. self.add_log("查询balance失败")
  144. self.send_info(text="查询balance失败", is_success=False)
  145. return
  146. n = 0
  147. while 1:
  148. # 获取key
  149. if self.windows.state != 1:
  150. return
  151. n += 1
  152. data = self.get_online_data.get_key_secret()
  153. key = (data["api_info"]["api_key"], data["api_info"]["api_serect"])
  154. self.add_log("查询key成功")
  155. if not key:
  156. _data = {
  157. "text": "出错/超时",
  158. "info": "多次获取key失败",
  159. }
  160. self.add_log(text="多次获取key失败")
  161. self.send_info(
  162. text="{} 处理失败,请联系管理员".format(self.file_name),
  163. is_success=False,
  164. )
  165. return
  166. if self.is_once("sub_point"):
  167. # 调用扣分
  168. # with self.lock:
  169. self.refresh_times(cumulative_frequency_times_change=1)
  170. f = self.dispose_point(_type="sub")
  171. if not f:
  172. self.add_log(text="多次获取调用余额扣减失败")
  173. self.send_info(text="多次获取调用余额扣减失败", is_success=False)
  174. return
  175. pixian_cutout_data = self.r_pixian.run_by_image_im(original_im, key)
  176. if pixian_cutout_data["status_code"] == 200:
  177. second_cut_image = pixian_cutout_data["im"]
  178. self.add_log(text="调用抠图完成")
  179. break
  180. elif pixian_cutout_data["status_code"] == 402:
  181. if n >= 2:
  182. self.add_log(
  183. text="多次抠图失败:{}".format(pixian_cutout_data["status_code"])
  184. )
  185. self.send_info(
  186. text="处理失败,请联系管理员",
  187. is_success=False,
  188. need_point_return=True,
  189. )
  190. if self.is_once("余额不足报错"):
  191. # 余额不足报错,钉钉消息通知
  192. self.get_online_data.send_message(
  193. "Pixian:{} 余额不足".format(key)
  194. )
  195. pass
  196. return
  197. self.add_log(
  198. text="抠图失败:{},延迟6秒".format(
  199. pixian_cutout_data["status_code"]
  200. )
  201. )
  202. time.sleep(6)
  203. continue
  204. elif pixian_cutout_data["status_code"] == 429:
  205. if n >= 2:
  206. self.add_log(
  207. text="多次抠图失败:{}".format(pixian_cutout_data["status_code"])
  208. )
  209. self.send_info(
  210. text="处理失败,请联系管理员",
  211. is_success=False,
  212. need_point_return=True,
  213. )
  214. return
  215. self.add_log(
  216. text="{}抠图失败:{},延迟10秒".format(
  217. self.file_name, pixian_cutout_data["status_code"]
  218. )
  219. )
  220. time.sleep(10)
  221. continue
  222. else:
  223. self.send_info(
  224. text="{} 处理失败,请联系管理员".format(self.file_name),
  225. is_success=False,
  226. need_point_return=True,
  227. )
  228. if "message" in pixian_cutout_data:
  229. text = "抠图异常,code:{},message:{}".format(
  230. pixian_cutout_data["status_code"], pixian_cutout_data["message"]
  231. )
  232. else:
  233. text = "抠图异常,code:{}".format(
  234. pixian_cutout_data["status_code"]
  235. )
  236. self.add_log(text)
  237. return
  238. # 拼接处理
  239. # print("耗时1:", time.time() - s)
  240. try:
  241. if image_deal_info["二次抠图是否缩放"]:
  242. # print("图片尺寸还原")
  243. self.add_log(text="图片尺寸进行还原")
  244. original_im = image_deal_info["抠图扩边后PIL对象"]
  245. second_cut_image = self.picture_resize_to_original(
  246. second_cut_image, original_im
  247. )
  248. # 创建空白图片并粘贴回去
  249. _img_im = Image.new(
  250. mode="RGBA", size=image_deal_info["原始图片大小"], color=(0, 0, 0, 0)
  251. )
  252. _img_im.paste(
  253. second_cut_image,
  254. box=(
  255. image_deal_info["抠图扩边后位置"][0],
  256. image_deal_info["抠图扩边后位置"][1],
  257. ),
  258. )
  259. _img_im.save(self.out_path)
  260. self.send_info(text="{} 抠图已完成".format(self.file_name), is_success=True)
  261. return self.file_path
  262. except BaseException as e:
  263. # print(e)
  264. text = "{} 图片处理错误,代码44".format(e)
  265. self.add_log(text)
  266. self.send_info(text=text, is_success=False, need_point_return=True)
  267. return
  268. def picture_resize_to_original(self, _img, original_im):
  269. """
  270. Parameters
  271. ----------
  272. _img 需要还原的PIL对象
  273. original_im 原图对象
  274. Returns
  275. -------
  276. """
  277. # 将抠图结果转成mask
  278. # 将抠图结果放大到原始图大小
  279. _img = _img.resize(original_im.size, resample=1)
  280. new_big_mask = Image.new("RGB", _img.size, (0, 0, 0))
  281. white = Image.new("RGB", _img.size, (255, 255, 255))
  282. new_big_mask.paste(white, mask=_img.split()[3])
  283. # ---------制作选区缩小的mask
  284. mask = cv2.cvtColor(
  285. np.asarray(new_big_mask), cv2.COLOR_BGR2GRAY
  286. ) # 将PIL 格式转换为 CV对象
  287. mask[mask != 255] = 0
  288. # 黑白反转
  289. # mask = 255 - mask
  290. # 选区缩小10
  291. kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
  292. erode_im = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
  293. # -------再进行抠图处理
  294. mask = Image.fromarray(
  295. cv2.cvtColor(erode_im, cv2.COLOR_GRAY2RGBA)
  296. ) # CV 对象转 PIL
  297. transparent_im = Image.new("RGBA", original_im.size, (0, 0, 0, 0))
  298. transparent_im.paste(original_im, (0, 0), mask.convert("L"))
  299. # 上述抠图结果进行拼接
  300. _img.paste(transparent_im, (0, 0), transparent_im)
  301. return _img
  302. class DealOneImageBeforehand(Base):
  303. def __init__(self, image_data, lock, windows, num,token):
  304. super().__init__(image_data, lock, windows, num,token)
  305. self.image_data = image_data
  306. self.lock = lock
  307. self.windows = windows
  308. self.get_online_data = GetOnlineDataHLM(token)
  309. self.num = num
  310. self.file_path = image_data["file_path"]
  311. self.file = os.path.split(self.file_path)[1]
  312. self.r_ali = RemoveBgALi()
  313. self.file_name = image_data["file_name"]
  314. def run(self):
  315. while 1:
  316. if self.windows.state != 1:
  317. return
  318. if self.windows.is_upload_pic_num >= 4:
  319. f = False
  320. else:
  321. self.windows.is_upload_pic_num += 1
  322. f = True
  323. if f:
  324. break
  325. else:
  326. time.sleep(1)
  327. continue
  328. image_deal_info = {}
  329. try:
  330. cut_image, image_deal_info = self.get_image_cut()
  331. f = True
  332. url = ""
  333. except BaseException as e:
  334. print("精细化抠图错误",e)
  335. f = False
  336. url = ""
  337. cut_image = ""
  338. self.windows.upload_pic_dict[self.file_path] = {
  339. "url": url,
  340. "image_deal_info": image_deal_info,
  341. "flag": f,
  342. "_im": cut_image,
  343. }
  344. def get_image_cut(self):
  345. original_pic = Picture(self.file_path)
  346. original_pic.im = self.get_image_orientation(original_pic.im)
  347. original_pic.x, original_pic.y = original_pic.im.size
  348. original_pic.im = original_pic.im.convert("RGB")
  349. image_deal_info = {}
  350. image_deal_info["原始图片大小"] = (original_pic.x, original_pic.y)
  351. # 原始图过小,则不需要使用阿里进行预处理
  352. if original_pic.x * original_pic.y < 1000000:
  353. cut_image = original_pic.im
  354. image_deal_info["抠图扩边后图片大小"] = cut_image.size
  355. image_deal_info["二次抠图是否缩放"] = False
  356. image_deal_info["抠图扩边后位置"] = (0, 0, original_pic.x, original_pic.y)
  357. else:
  358. self.add_log("开始预抠图处理")
  359. cut_image = self.r_ali.get_image_cut(
  360. file_path=None, out_file_path=None, original_im=original_pic.im
  361. )
  362. self.add_log("预抠图处理结束")
  363. x1, y1, x2, y2 = cut_image.getbbox()
  364. image_deal_info["鞋子原始位置"] = (x1, y1, x2, y2)
  365. o_w, o_h = cut_image.size
  366. image_deal_info["鞋子原始抠图后大小"] = (o_w, o_h)
  367. # 扩边处理
  368. _w, _h = x2 - x1, y2 - y1
  369. out_px = 0.025
  370. _w, _h = int(out_px * _w), int(out_px * _h)
  371. n_x1, n_y1, n_x2, n_y2 = x1 - _w, y1 - _h, x2 + _w, y2 + _h
  372. if n_x1 < 0:
  373. n_x1 = 0
  374. if n_y1 < 0:
  375. n_y1 = 0
  376. if n_x2 > o_w:
  377. n_x2 = o_w
  378. if n_y2 > o_h:
  379. n_y2 = o_h
  380. image_deal_info["抠图扩边后位置"] = (n_x1, n_y1, n_x2, n_y2)
  381. cut_image = original_pic.im.crop(image_deal_info["抠图扩边后位置"])
  382. image_deal_info["抠图扩边后图片大小"] = cut_image.size
  383. x, y = image_deal_info["抠图扩边后图片大小"]
  384. # 12000000
  385. max_size = settings.MAX_PIXIAN_SIZE
  386. if x * y > max_size:
  387. r = math.sqrt(max_size) / math.sqrt(x * y)
  388. r = r * 0.9
  389. size = (int(x * r), int(y * r))
  390. # print("图片:{} pixian触发二次缩放,原尺寸{}*{},新尺寸:{}".format(self.file_name, x, y, size))
  391. self.add_log(
  392. text="图片进行压缩,压缩前:{},压缩后:{}".format(
  393. image_deal_info["抠图扩边后图片大小"], size
  394. )
  395. )
  396. image_deal_info["抠图扩边后PIL对象"] = copy.deepcopy(cut_image)
  397. cut_image = cut_image.resize(size=size, resample=1)
  398. # print(cut_image.size)
  399. # print(image_deal_info["抠图扩边后PIL对象"].size)
  400. image_deal_info["二次抠图是否缩放"] = True
  401. else:
  402. image_deal_info["二次抠图是否缩放"] = False
  403. return cut_image, image_deal_info
  404. def get_image_orientation(self, img):
  405. # 获取EXIF数据
  406. exif = img._getexif()
  407. if exif is not None:
  408. # EXIF标签274对应的是Orientation
  409. orientation = exif.get(0x0112)
  410. if orientation == 2:
  411. # 水平翻转
  412. img = img.transpose(Image.FLIP_LEFT_RIGHT)
  413. elif orientation == 3:
  414. # 旋转180度
  415. img = img.rotate(180, expand=True)
  416. elif orientation == 4:
  417. # 垂直翻转
  418. img = img.transpose(Image.FLIP_TOP_BOTTOM)
  419. elif orientation == 5:
  420. # 水平翻转后顺时针旋转90度
  421. img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270)
  422. elif orientation == 6:
  423. # 顺时针旋转90度
  424. img = img.transpose(Image.ROTATE_270)
  425. elif orientation == 7:
  426. # 水平翻转后逆时针旋转90度
  427. img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90)
  428. elif orientation == 8:
  429. # 逆时针旋转90度
  430. img = img.transpose(Image.ROTATE_90)
  431. else:
  432. print("没有EXIF数据或没有方向信息")
  433. orientation = 1
  434. return img