utils.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import base64
  2. import colorsys
  3. import copy
  4. import re
  5. import socket
  6. import traceback
  7. import urllib
  8. from enum import Enum
  9. from functools import reduce
  10. import requests
  11. from PIL import ImageDraw, ImageFont, Image
  12. from keras import backend as K
  13. import numpy as np
  14. import cv2
  15. def compose(*funcs):
  16. """Compose arbitrarily many functions, evaluated left to right.
  17. Reference: https://mathieularose.com/function-composition-in-python/
  18. """
  19. if funcs:
  20. return reduce(lambda f, g: lambda *a, **kw: g(f(*a, **kw)), funcs)
  21. else:
  22. raise ValueError('Composition of empty sequence not supported.')
  23. def box_iou(b1, b2):
  24. """Return iou tensor
  25. Parameters
  26. ----------
  27. b1: tensor, shape=(i1,...,iN, 4), xywh
  28. b2: tensor, shape=(j, 4), xywh
  29. Returns
  30. -------
  31. iou: tensor, shape=(i1,...,iN, j)
  32. """
  33. # Expand dim to apply broadcasting.
  34. b1 = K.expand_dims(b1, -2)
  35. b1_xy = b1[..., :2]
  36. b1_wh = b1[..., 2:4]
  37. b1_wh_half = b1_wh/2.
  38. b1_mins = b1_xy - b1_wh_half
  39. b1_maxes = b1_xy + b1_wh_half
  40. # Expand dim to apply broadcasting.
  41. b2 = K.expand_dims(b2, 0)
  42. b2_xy = b2[..., :2]
  43. b2_wh = b2[..., 2:4]
  44. b2_wh_half = b2_wh/2.
  45. b2_mins = b2_xy - b2_wh_half
  46. b2_maxes = b2_xy + b2_wh_half
  47. intersect_mins = K.maximum(b1_mins, b2_mins)
  48. intersect_maxes = K.minimum(b1_maxes, b2_maxes)
  49. intersect_wh = K.maximum(intersect_maxes - intersect_mins, 0.)
  50. intersect_area = intersect_wh[..., 0] * intersect_wh[..., 1]
  51. b1_area = b1_wh[..., 0] * b1_wh[..., 1]
  52. b2_area = b2_wh[..., 0] * b2_wh[..., 1]
  53. iou = intersect_area / (b1_area + b2_area - intersect_area)
  54. return iou
  55. def get_classes(classes_path):
  56. """loads the classes"""
  57. with open(classes_path) as f:
  58. class_names = f.readlines()
  59. class_names = [c.strip() for c in class_names]
  60. return class_names
  61. def get_anchors(anchors_path):
  62. """loads the anchors from a file"""
  63. with open(anchors_path) as f:
  64. anchors = f.readline()
  65. anchors = [float(x) for x in anchors.split(',')]
  66. return np.array(anchors).reshape(-1, 2)
  67. def get_colors(number, bright=True):
  68. """
  69. Generate random colors for drawing bounding boxes.
  70. To get visually distinct colors, generate them in HSV space then
  71. convert to RGB.
  72. """
  73. if number <= 0:
  74. return []
  75. brightness = 1.0 if bright else 0.7
  76. hsv_tuples = [(x / number, 1., brightness)
  77. for x in range(number)]
  78. colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples))
  79. colors = list(
  80. map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)),
  81. colors))
  82. np.random.seed(10101) # Fixed seed for consistent colors across runs.
  83. np.random.shuffle(colors) # Shuffle colors to decorrelate adjacent classes.
  84. np.random.seed(None) # Reset seed to default.
  85. return colors
  86. labelType = Enum('labelType', ('LABEL_TOP_OUTSIDE',
  87. 'LABEL_BOTTOM_OUTSIDE',
  88. 'LABEL_TOP_INSIDE',
  89. 'LABEL_BOTTOM_INSIDE',))
  90. def draw_boxes(image, out_boxes, out_classes, out_scores, class_names, colors):
  91. font = ImageFont.truetype(font='yolo_data/FiraMono-Medium.otf',
  92. size=np.floor(3e-2 * image.size[1] + 0.5).astype('int32'))
  93. thickness = (image.size[0] + image.size[1]) // 300
  94. box_list = []
  95. for i, c in reversed(list(enumerate(out_classes))):
  96. predicted_class = class_names[c]
  97. box = out_boxes[i]
  98. score = out_scores[i]
  99. label = '{} {:.2f}'.format(predicted_class, score)
  100. draw = ImageDraw.Draw(image)
  101. label_size = draw.textsize(label, font)
  102. top, left, bottom, right = box
  103. top = max(0, np.floor(top + 0.5).astype('int32'))
  104. left = max(0, np.floor(left + 0.5).astype('int32'))
  105. bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32'))
  106. right = min(image.size[0], np.floor(right + 0.5).astype('int32'))
  107. # print(label, (left, top), (right, bottom))
  108. box_list.append([(left, top), (right, bottom)])
  109. if top - label_size[1] >= 0:
  110. text_origin = np.array([left, top - label_size[1]])
  111. else:
  112. text_origin = np.array([left, top + 1])
  113. # My kingdom for a good redistributable image drawing library.
  114. for i in range(thickness):
  115. draw.rectangle(
  116. [left + i, top + i, right - i, bottom - i],
  117. outline=colors[c])
  118. draw.rectangle(
  119. [tuple(text_origin), tuple(text_origin + label_size)],
  120. fill=colors[c])
  121. draw.text(text_origin, label, fill=(0, 0, 0), font=font)
  122. del draw
  123. return image, box_list
  124. def draw_label(image, text, color, coords, label_type=labelType.LABEL_TOP_OUTSIDE):
  125. font = cv2.FONT_HERSHEY_PLAIN
  126. font_scale = 1.
  127. (text_width, text_height) = cv2.getTextSize(text, font, fontScale=font_scale, thickness=1)[0]
  128. padding = 5
  129. rect_height = text_height + padding * 2
  130. rect_width = text_width + padding * 2
  131. (x, y) = coords
  132. if label_type == labelType.LABEL_TOP_OUTSIDE or label_type == labelType.LABEL_BOTTOM_INSIDE:
  133. cv2.rectangle(image, (x, y), (x + rect_width, y - rect_height), color, cv2.FILLED)
  134. cv2.putText(image, text, (x + padding, y - text_height + padding), font,
  135. fontScale=font_scale,
  136. color=(255, 255, 255),
  137. lineType=cv2.LINE_AA)
  138. else:
  139. # LABEL_BOTTOM_OUTSIDE or LABEL_TOP_INSIDE
  140. cv2.rectangle(image, (x, y), (x + rect_width, y + rect_height), color, cv2.FILLED)
  141. cv2.putText(image, text, (x + padding, y + text_height + padding), font,
  142. fontScale=font_scale,
  143. color=(255, 255, 255),
  144. lineType=cv2.LINE_AA)
  145. return image
  146. def pil_resize(image_np, height, width):
  147. image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB))
  148. image_pil = image_pil.resize((int(width), int(height)), Image.BICUBIC)
  149. image_np = cv2.cvtColor(np.asarray(image_pil), cv2.COLOR_RGB2BGR)
  150. return image_np
  151. def pil_resize_a(image_np, height, width):
  152. image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGRA2RGBA))
  153. image_pil = image_pil.resize((int(width), int(height)), Image.BICUBIC)
  154. image_np = cv2.cvtColor(np.asarray(image_pil), cv2.COLOR_RGBA2BGRA)
  155. return image_np
  156. def np2pil(image_np):
  157. image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGR2RGB))
  158. return image_pil
  159. def pil2np(image_pil):
  160. image_np = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
  161. return image_np
  162. def np2pil_a(image_np):
  163. image_pil = Image.fromarray(cv2.cvtColor(image_np, cv2.COLOR_BGRA2RGBA))
  164. return image_pil
  165. def pil2np_a(image_pil):
  166. image_np = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGBA2BGRA)
  167. return image_np
  168. def pil_rotate(image_np, angle, fill_color=(255, 255, 255)):
  169. image_pil = np2pil(image_np)
  170. image_rotate = image_pil.rotate(angle, expand=False, fillcolor=fill_color)
  171. image_np = pil2np(image_rotate)
  172. return image_np
  173. def pil_rotate_a(image_np, angle):
  174. image_pil = np2pil_a(image_np)
  175. image_rotate = image_pil.rotate(angle, expand=False, fillcolor=(255, 255, 255))
  176. image_np = pil2np_a(image_rotate)
  177. return image_np
  178. def request_post(url, param, time_out=1000, use_zlib=False):
  179. fails = 0
  180. text = None
  181. while True:
  182. try:
  183. if fails >= 1:
  184. break
  185. headers = {'content-type': 'application/json'}
  186. # result = requests.post(url, data=param, timeout=time_out)
  187. session = requests.Session()
  188. result = session.post(url, data=param, timeout=time_out)
  189. if result.status_code == 200:
  190. text = result.text
  191. break
  192. else:
  193. print('result.status_code', result.status_code)
  194. print('result.text', result.text)
  195. fails += 1
  196. continue
  197. except socket.timeout:
  198. fails += 1
  199. print('timeout! fail times:', fails)
  200. except:
  201. fails += 1
  202. print('fail! fail times:', fails)
  203. traceback.print_exc()
  204. return text
  205. def np2bytes(image_np):
  206. # numpy转为可序列化的string
  207. success, img_encode = cv2.imencode(".jpg", image_np)
  208. # numpy -> bytes
  209. img_bytes = img_encode.tobytes()
  210. return img_bytes
  211. def bytes2np(_b, unchanged=False):
  212. try:
  213. # 二进制数据流转np.ndarray [np.uint8: 8位像素]
  214. if unchanged:
  215. # unchanged能读取透明通道
  216. image_np = cv2.imdecode(np.frombuffer(_b, np.uint8), cv2.IMREAD_UNCHANGED)
  217. else:
  218. image_np = cv2.imdecode(np.frombuffer(_b, np.uint8), cv2.IMREAD_COLOR)
  219. # 将rgb转为bgr
  220. # image_np = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
  221. return image_np
  222. except cv2.error as e:
  223. if "src.empty()" in str(e):
  224. print("bytes2np image is empty!")
  225. return None
  226. except:
  227. traceback.print_exc()
  228. return None
  229. def base64_decode(b64, is_test=False):
  230. # b64 = str(b64)
  231. # missing_padding = 4 - len(b64) % 4
  232. # if missing_padding:
  233. # print('base64 missing_padding', missing_padding)
  234. # b64 += '=' * missing_padding
  235. # print('len(b64)', len(b64))
  236. if '%' in str(b64):
  237. b64 = urllib.parse.unquote(str(b64))
  238. print(b64)
  239. if type(b64) == bytes:
  240. return base64.b64decode(b64)
  241. b64 = re.sub(r"\r\n", "\n", b64)
  242. missing_padding = 4 - len(b64) % 4
  243. if missing_padding:
  244. b64 += '=' * missing_padding
  245. if is_test:
  246. return base64.b64decode(b64), b64
  247. return base64.b64decode(b64)
  248. def limit_image_size(image_np, threshold=2000):
  249. h, w = image_np.shape[:2]
  250. if h <= threshold and w <= threshold:
  251. return image_np
  252. scale = threshold / max(h, w)
  253. h = int(h * scale)
  254. w = int(w * scale)
  255. image_np = pil_resize(image_np, h, w)
  256. return image_np
  257. def get_best_predict_size(image_np, times=32):
  258. sizes = []
  259. for i in range(1, 100):
  260. if i*times <= 1300:
  261. sizes.append(i*times)
  262. sizes.sort(key=lambda x: x, reverse=True)
  263. min_len = 10000
  264. best_height = sizes[0]
  265. for height in sizes:
  266. if abs(image_np.shape[0] - height) < min_len:
  267. min_len = abs(image_np.shape[0] - height)
  268. best_height = height
  269. min_len = 10000
  270. best_width = sizes[0]
  271. for width in sizes:
  272. if abs(image_np.shape[1] - width) < min_len:
  273. min_len = abs(image_np.shape[1] - width)
  274. best_width = width
  275. return best_height, best_width
  276. def add_contrast(image_np):
  277. # cv2.imshow("image_np", image_np)
  278. img = image_np.astype(np.float32)
  279. bri_mean = np.mean(img)
  280. # a = np.arange(5, 16, 5) / 10
  281. # b = np.arange(-30, 31, 30)
  282. #
  283. # a_len = len(a)
  284. # b_len = len(b)
  285. # print(a_len, b_len)
  286. #
  287. # for i in range(a_len):
  288. # for j in range(b_len):
  289. # aa = a[i]
  290. # bb = b[j]
  291. # img_a = aa * (img-bri_mean) + bb + bri_mean
  292. # print(i, j, aa, bb)
  293. # img_a = np.clip(img_a, 0, 255).astype(np.uint8)
  294. # cv2.imshow("img_a", img_a)
  295. # cv2.waitKey(0)
  296. aa = 3
  297. bb = -50
  298. img_a = aa * (img-bri_mean) + bb + bri_mean
  299. img_a = np.clip(img_a, 0, 255).astype(np.uint8)
  300. # cv2.imshow("img_a", img_a)
  301. # cv2.waitKey(0)
  302. return img_a
  303. def image_to_str(image_np):
  304. file_bytes = np2bytes(image_np)
  305. file_base64 = base64.b64encode(file_bytes)
  306. file_str = file_base64.decode("utf-8")
  307. return file_str
  308. def str_to_image(_str):
  309. b64 = _str.encode("utf-8")
  310. image_np = bytes2np(base64_decode(b64))
  311. return image_np
  312. def rgba_to_rgb(image_np, return_position=False):
  313. # 提取 RGB 通道和透明通道
  314. rgb_channels = image_np[:, :, :3]
  315. alpha_channel = image_np[:, :, 3]
  316. # 找到透明通道小于250的部分
  317. transparent_pixels = alpha_channel < 250
  318. # 将透明通道小于250的部分的RGB值置为黑色
  319. rgb_channels[transparent_pixels] = [100, 100, 100]
  320. image_np = rgb_channels
  321. # 创建图像的副本,并将所有像素值置为白色
  322. # white_background = np.ones_like(image_np) * 255
  323. white_background = copy.copy(image_np)
  324. # 在图像副本上找到黑色部分的轮廓
  325. contours, hierarchy = cv2.findContours(transparent_pixels.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  326. # 输出黑色部分的位置(边界框坐标)
  327. position_list = []
  328. for contour in contours:
  329. x, y, w, h = cv2.boundingRect(contour)
  330. position_list.append([int(x), int(y), int(x+w), int(y+h)])
  331. # 将黑色部分的轮廓描绘为白色
  332. cv2.drawContours(white_background, contours, -1, (255, 255, 255), thickness=1)
  333. # 合并原始图像和处理后的图像
  334. # image_np = cv2.addWeighted(image_np, 0, white_background, 1, 0)
  335. image_np = white_background
  336. if return_position:
  337. return image_np, position_list
  338. return image_np