image_deal_base_func.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. import cv2
  2. from io import BytesIO
  3. import numpy as np, requests
  4. from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageDraw, ImageChops, ImageStat
  5. import settings
  6. def uploadImage(im: Image) -> str:
  7. img_byte_io = BytesIO()
  8. # 根据图片模式选择保存格式
  9. if im.mode == 'RGBA':
  10. im.save(img_byte_io, format='PNG')
  11. else:
  12. im.save(img_byte_io, format='JPEG')
  13. img_byte_io.seek(0) # 重置指针到开头
  14. post_headers = {"Authorization": settings.USER_TOKEN}
  15. url = settings.DOMAIN + "/api/upload"
  16. # 使用字节流上传
  17. resultData = requests.post(
  18. url,
  19. files={"file": ("image.jpg", img_byte_io, "image/jpeg")},
  20. headers=post_headers
  21. ).json()
  22. return resultData["data"]["url"]
  23. # 锐化图片
  24. def sharpen_image(img, factor=1.0):
  25. # 创建一个ImageEnhance对象
  26. enhancer = ImageEnhance.Sharpness(img)
  27. # 应用增强,值为0.0给出模糊图像,1.0给出原始图像,大于1.0给出锐化效果
  28. # 调整这个值来增加或减少锐化的程度
  29. sharp_img = enhancer.enhance(factor)
  30. return sharp_img
  31. def to_resize(_im, width=None, high=None) -> Image:
  32. _im_x, _im_y = _im.size
  33. if width and high:
  34. if _im_x >= _im_y:
  35. high = None
  36. else:
  37. width = None
  38. if width:
  39. re_x = int(width)
  40. re_y = int(_im_y * re_x / _im_x)
  41. else:
  42. re_y = int(high)
  43. re_x = int(_im_x * re_y / _im_y)
  44. _im = _im.resize((re_x, re_y), resample=settings.RESIZE_IMAGE_MODE)
  45. return _im
  46. def pil_to_cv2(pil_image):
  47. # 将 PIL 图像转换为 RGB 或 RGBA 格式
  48. if pil_image.mode != 'RGBA':
  49. pil_image = pil_image.convert('RGBA')
  50. # 将 PIL 图像转换为 numpy 数组
  51. cv2_image = np.array(pil_image)
  52. # 由于 PIL 的颜色顺序是 RGB,而 OpenCV 的颜色顺序是 BGR,因此需要交换颜色通道
  53. cv2_image = cv2.cvtColor(cv2_image, cv2.COLOR_RGBA2BGRA)
  54. return cv2_image
  55. def cv2_to_pil(cv_img):
  56. return Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
  57. def get_mini_crop_img(img):
  58. old_x, old_y = img.size
  59. x1, y1, x2, y2 = img.getbbox()
  60. goods_w, goods_h = x2 - x1, y2 - y1
  61. _w, _h = int(goods_w / 10), int(goods_h / 10) # 上下左右扩展位置
  62. new_x1, new_y1, new_x2, new_y2 = x1 - _w, y1 - _h, x2 + _w, y2 + _h # 防止超限
  63. new_x1 = 0 if new_x1 < 0 else new_x1
  64. new_y1 = 0 if new_y1 < 0 else new_y1
  65. new_x2 = old_x if new_x2 > old_x else new_x2
  66. new_y2 = old_y if new_y2 > old_y else new_y2
  67. img = img.crop((new_x1, new_y1, new_x2, new_y2)) # 切图
  68. box = (new_x1, new_y1, new_x2, new_y2)
  69. return img, box
  70. def expand_or_shrink_mask(pil_image, expansion_radius=5, iterations=1, blur_radius=0):
  71. """
  72. 对输入的PIL黑白图像(掩膜)进行膨胀或腐蚀操作,以扩大或缩小前景区域。
  73. :param pil_image: 输入的PIL黑白图像对象
  74. :param expansion_radius: 结构元素大小,默认是一个3x3的小正方形;负值表示收缩
  75. :param iterations: 操作迭代次数,默认为1次
  76. :param blur_radius: 高斯模糊的半径,默认不应用模糊
  77. :return: 修改后的PIL黑白图像对象
  78. """
  79. # 将PIL图像转换为numpy数组,并确保其为8位无符号整数类型
  80. img_np = np.array(pil_image).astype(np.uint8)
  81. # 如果不是二值图像,则应用阈值处理
  82. if len(np.unique(img_np)) > 2: # 检查是否为二值图像
  83. _, img_np = cv2.threshold(img_np, 127, 255, cv2.THRESH_BINARY)
  84. # 定义结构元素(例如正方形)
  85. abs_expansion_radius = abs(expansion_radius)
  86. kernel = np.ones((abs_expansion_radius, abs_expansion_radius), np.uint8)
  87. # 根据expansion_radius的符号选择膨胀或腐蚀操作
  88. if expansion_radius >= 0:
  89. modified_img_np = cv2.dilate(img_np, kernel, iterations=iterations)
  90. else:
  91. modified_img_np = cv2.erode(img_np, kernel, iterations=iterations)
  92. # 如果提供了blur_radius,则应用高斯模糊
  93. if blur_radius > 0:
  94. modified_img_np = cv2.GaussianBlur(modified_img_np, (blur_radius * 2 + 1, blur_radius * 2 + 1), 0)
  95. # 将numpy数组转换回PIL图像
  96. modified_pil_image = Image.fromarray(modified_img_np)
  97. return modified_pil_image
  98. def expand_mask(mask, expansion_radius=5, blur_radius=0):
  99. # 对蒙版进行膨胀处理
  100. mask = mask.filter(ImageFilter.MaxFilter(expansion_radius * 2 + 1))
  101. # 应用高斯模糊滤镜
  102. if blur_radius > 0:
  103. mask = mask.filter(ImageFilter.GaussianBlur(blur_radius))
  104. return mask
  105. def find_lowest_non_transparent_points(cv2_png):
  106. # cv2_png 为cv2格式的带有alpha通道的图片
  107. alpha_channel = cv2_png[:, :, 3]
  108. """使用Numpy快速查找每列的最低非透明点"""
  109. h, w = alpha_channel.shape
  110. # 创建一个掩码,其中非透明像素为True
  111. mask = alpha_channel > 0
  112. # 使用np.argmax找到每列的第一个非透明像素的位置
  113. # 因为是从底部向上找,所以需要先翻转图像
  114. flipped_mask = np.flip(mask, axis=0)
  115. min_y_values = h - np.argmax(flipped_mask, axis=0) - 1
  116. # 将全透明列的值设置为-1
  117. min_y_values[~mask.any(axis=0)] = -1
  118. return min_y_values
  119. def draw_shifted_line(
  120. image,
  121. min_y_values,
  122. shift_amount=15,
  123. one_line_pos=(0, 100),
  124. line_color=(0, 0, 0),
  125. line_thickness=20,
  126. app=None,
  127. crop_image_box=None,
  128. ):
  129. """
  130. image:jpg cv2格式的原始图
  131. min_y_values 透明图中,不透明区域的最低那条线
  132. shift_amount:向下偏移值
  133. line_color:线颜色
  134. line_thickness:线宽
  135. """
  136. # 将最低Y值向下迁移20个像素,但确保不超过图片的高度
  137. # 创建空白图片
  138. image = np.ones((image.shape[0], image.shape[1], 3), dtype=np.uint8) * 255
  139. # 对线条取转成图片
  140. shifted_min_y_values = np.clip(min_y_values + shift_amount, 0, image.shape[0] - 1)
  141. # 使用Numpy索引批量绘制直线
  142. min_y_threshold = 50 # Y轴像素小于50的不处理
  143. valid_x = (shifted_min_y_values >= min_y_threshold) & (shifted_min_y_values != -1)
  144. # print("valid_x", len(valid_x))
  145. # 对曲线取平均值
  146. # # 对曲线取平均值
  147. # min_y = np.max(min_y_values)
  148. # min_y_values_2 = min_y_values + min_y
  149. # min_y_values_2 = min_y_values_2 / 2
  150. # min_y_values_2 = min_y_values_2.astype(int)
  151. # shifted_min_y_values = np.clip(min_y_values_2 + shift_amount, 0, image.shape[0] - 1)
  152. if settings.SHADOW_PROCESSING == 0:
  153. if crop_image_box:
  154. # 800像素宽;鞋子前后20%进行移除
  155. shoe_width = crop_image_box[2] - crop_image_box[0]
  156. _half_show_width = int(shoe_width * 0.15)
  157. valid_x[: crop_image_box[0] + _half_show_width] = False
  158. valid_x[crop_image_box[2] - _half_show_width:] = False
  159. x_coords = np.arange(image.shape[1])[valid_x]
  160. y_start = shifted_min_y_values[valid_x]
  161. y_end = y_start + line_thickness
  162. # todo 使用Numpy广播机制创建线条区域的索引
  163. # todo 鞋子曲线线条
  164. if settings.SHADOW_PROCESSING == 0:
  165. for x, start, end in zip(x_coords, y_start, y_end):
  166. image[start:end, x, :3] = line_color # 只修改RGB通道
  167. # 计算整个图像的最低非透明点
  168. lowest_y = (
  169. np.max(min_y_values[min_y_values != -1]) if np.any(min_y_values != -1) else -1
  170. )
  171. # 绘制原最低非透明点处的线
  172. cv2.line(
  173. image,
  174. (one_line_pos[0], lowest_y + settings.LOWER_Y),
  175. (one_line_pos[1], lowest_y + 5),
  176. line_color,
  177. thickness=line_thickness,
  178. )
  179. # 调整 _y = lowest_y + 18
  180. _y = lowest_y + 200
  181. if _y > image.shape[0]: # 超过图片尺寸
  182. _y = image.shape[0] - settings.CHECK_LOWER_Y
  183. return image, _y
  184. def clean_colors(img):
  185. # 转成灰度图
  186. img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  187. return img
  188. def calculated_shadow_brightness(img: Image):
  189. # 打开图片并转换为灰度模式
  190. image = img.convert('L')
  191. # 将图片数据转为numpy数组
  192. image_data = np.array(image)
  193. # 创建布尔掩码以识别非白色区域
  194. non_white_mask = image_data < 252
  195. # 使用掩码提取非白色像素的亮度值
  196. non_white_values = image_data[non_white_mask]
  197. # print(len(non_white_values),len(image_data))
  198. # 如果存在非白色像素,则计算平均亮度;否则返回0
  199. if len(non_white_values) > 0:
  200. average_brightness = np.mean(non_white_values)
  201. else:
  202. average_brightness = 0 # 没有非白色像素时的情况
  203. return average_brightness
  204. def levels_adjust(img, Shadow, Midtones, Highlight, OutShadow, OutHighlight, Dim):
  205. # 色阶处理
  206. # img 为cv2格式
  207. # dim = 3的时候调节RGB三个分量, 0调节B,1调节G,2调节R
  208. if Dim == 3:
  209. mask_shadow = img < Shadow
  210. img[mask_shadow] = Shadow
  211. mask_Highlight = img > Highlight
  212. img[mask_Highlight] = Highlight
  213. else:
  214. mask_shadow = img[..., Dim] < Shadow
  215. img[mask_shadow] = Shadow
  216. mask_Highlight = img[..., Dim] > Highlight
  217. img[mask_Highlight] = Highlight
  218. if Dim == 3:
  219. Diff = Highlight - Shadow
  220. rgbDiff = img - Shadow
  221. clRgb = np.power(rgbDiff / Diff, 1 / Midtones)
  222. outClRgb = clRgb * (OutHighlight - OutShadow) / 255 + OutShadow
  223. data = np.array(outClRgb * 255, dtype='uint8')
  224. img = data
  225. else:
  226. Diff = Highlight - Shadow
  227. rgbDiff = img[..., Dim] - Shadow
  228. clRgb = np.power(rgbDiff / Diff, 1 / Midtones)
  229. outClRgb = clRgb * (OutHighlight - OutShadow) / 255 + OutShadow
  230. data = np.array(outClRgb * 255, dtype='uint8')
  231. img[..., Dim] = data
  232. return img
  233. def calculate_average_brightness_opencv(img_gray, rows_to_check):
  234. # 二值化的图片 CV对象
  235. # 计算图片亮度
  236. height, width = img_gray.shape
  237. brightness_list = []
  238. for row in rows_to_check:
  239. if 0 <= row < height:
  240. # 直接计算该行的平均亮度
  241. row_data = img_gray[row, :]
  242. average_brightness = np.mean(row_data)
  243. brightness_list.append(average_brightness)
  244. else:
  245. print(f"警告:行号{row}超出图片范围,已跳过。")
  246. return brightness_list
  247. def get_extremes_from_transparent(img, alpha_threshold=10):
  248. """
  249. 直接从透明图获取最左和最右的XY坐标
  250. Args:
  251. image_path: 透明图像路径
  252. alpha_threshold: 透明度阈值,低于此值视为透明
  253. Returns:
  254. dict: 包含最左、最右坐标等信息
  255. """
  256. # 确保有alpha通道
  257. if img.mode != 'RGBA':
  258. img = img.convert('RGBA')
  259. # 转换为numpy数组
  260. img_array = np.array(img)
  261. # 提取alpha通道
  262. alpha = img_array[:, :, 3]
  263. # 根据阈值创建mask
  264. mask = alpha > alpha_threshold
  265. if not np.any(mask):
  266. print("警告: 没有找到非透明像素")
  267. return None
  268. # 获取所有非透明像素的坐标
  269. rows, cols = np.where(mask)
  270. if len(rows) == 0:
  271. return None
  272. # 找到最左和最右的像素
  273. # 最左: 列坐标最小
  274. leftmost_col = np.min(cols)
  275. # 最右: 列坐标最大
  276. rightmost_col = np.max(cols)
  277. # 对于最左列,找到所有在该列的像素,然后取中间或特定位置的Y坐标
  278. leftmost_rows = rows[cols == leftmost_col]
  279. rightmost_rows = rows[cols == rightmost_col]
  280. # 选择策略:可以取平均值、最小值、最大值或中位数
  281. strategy = 'median' # 可选: 'min', 'max', 'mean', 'median', 'top', 'bottom'
  282. def get_y_coordinate(rows_values, strategy='median'):
  283. if strategy == 'min':
  284. return np.min(rows_values)
  285. elif strategy == 'max':
  286. return np.max(rows_values)
  287. elif strategy == 'mean':
  288. return int(np.mean(rows_values))
  289. elif strategy == 'median':
  290. return int(np.median(rows_values))
  291. elif strategy == 'top':
  292. return np.min(rows_values)
  293. elif strategy == 'bottom':
  294. return np.max(rows_values)
  295. return int(np.median(rows_values))
  296. # 获取最左点的Y坐标
  297. leftmost_y = get_y_coordinate(leftmost_rows, strategy)
  298. # 获取最右点的Y坐标
  299. rightmost_y = get_y_coordinate(rightmost_rows, strategy)
  300. result = {
  301. 'leftmost': (int(leftmost_col), int(leftmost_y)),
  302. 'rightmost': (int(rightmost_col), int(rightmost_y)),
  303. 'image_size': img.size, # (width, height)
  304. 'alpha_threshold': alpha_threshold,
  305. 'pixel_count': len(rows),
  306. 'strategy': strategy
  307. }
  308. return result
  309. def create_polygon_mask_from_points(img, left_point, right_point):
  310. """
  311. 根据两个点和图片边界创建多边形mask
  312. 形成四边形:图片左上角 → left_point → right_point → 图片右上角 → 回到左上角
  313. Args:
  314. left_point: (x, y) 左侧点
  315. right_point: (x, y) 右侧点
  316. Returns:
  317. Image: 多边形mask
  318. list: 多边形顶点坐标
  319. """
  320. # 打开图片获取尺寸
  321. img_width, img_height = img.size
  322. # 创建mask(全黑)
  323. mask = Image.new('L', (img_width, img_height), 255)
  324. draw = ImageDraw.Draw(mask)
  325. # 定义多边形顶点(顺时针或逆时针顺序)
  326. # 四边形:左上角 → left_point → right_point → 右上角
  327. polygon_points = [
  328. (-1, -1), # 图片左上角
  329. (-1, left_point[1]), # 左侧点x=0
  330. (left_point[0], left_point[1]), # 左侧点
  331. (right_point[0], right_point[1]), # 右侧点
  332. (img_width, right_point[1]), # 右侧点y=0
  333. (img_width, -1), # 图片右上角
  334. ]
  335. # 绘制填充多边形
  336. draw.polygon(polygon_points, fill=0, outline=255)
  337. return mask
  338. def transparent_to_mask_pil(img, threshold=0, is_invert=False):
  339. """
  340. 将透明图像转换为mask
  341. threshold: 透明度阈值,低于此值的像素被视为透明
  342. """
  343. # 确保图像有alpha通道
  344. if img.mode != 'RGBA':
  345. img = img.convert('RGBA')
  346. # 分离通道
  347. r, g, b, a = img.split()
  348. # 将alpha通道转换为二值mask
  349. # 阈值处理:alpha值低于阈值的设为0(透明),否则设为255(不透明)
  350. if is_invert is False:
  351. mask = a.point(lambda x: 0 if x <= threshold else 255)
  352. else:
  353. mask = a.point(lambda x: 255 if x <= threshold else 0)
  354. return mask
  355. # 两个MASK取交集
  356. def mask_intersection(mask1: Image.Image, mask2: Image.Image) -> Image.Image:
  357. """
  358. 对两个 PIL mask 图像取交集(逻辑 AND)
  359. - 输入:两个 mode='L' 的灰度图(0=假,非0=真)
  360. - 输出:新的 mask,交集区域为 255,其余为 0(可选)
  361. """
  362. # 转为 numpy 数组
  363. arr1 = np.array(mask1)
  364. arr2 = np.array(mask2)
  365. # 确保形状一致
  366. assert arr1.shape == arr2.shape, "Mask shapes must match"
  367. # 转为布尔:非零即 True
  368. bool1 = arr1 > 0
  369. bool2 = arr2 > 0
  370. # 交集:逻辑与
  371. intersection = bool1 & bool2
  372. # 转回 uint8:True→255, False→0(标准 mask 格式)
  373. result = (intersection * 255).astype(np.uint8)
  374. return Image.fromarray(result, mode='L')
  375. def brightness_check(img_gray, mask):
  376. img_gray = cv2_to_pil(img_gray)
  377. img = Image.new("RGBA", img_gray.size, (255, 255, 255, 0))
  378. img.paste(im=img_gray, mask=mask)
  379. data = np.array(img) # shape: (H, W, 4)
  380. # 分离通道
  381. r, g, b, a = data[..., 0], data[..., 1], data[..., 2], data[..., 3]
  382. # 创建非透明掩码(Alpha > 0)
  383. mask = a > 0
  384. # 如果没有非透明像素,返回 0 或 NaN
  385. if not np.any(mask):
  386. return 0.0 # 或者 raise ValueError("No opaque pixels")
  387. # 计算亮度(仅对非透明区域)
  388. # 使用 ITU-R BT.601 标准权重
  389. luminance = 0.299 * r[mask] + 0.587 * g[mask] + 0.114 * b[mask]
  390. # 返回平均亮度
  391. return float(np.mean(luminance))
  392. def get_png_brightness(img_gray, mask):
  393. # 计算非透明区域的平均亮度
  394. # transparent_im = Image.new('RGB', img.size, (0, 0, 0))
  395. img_gray = cv2_to_pil(img_gray)
  396. _im = Image.new("RGB", img_gray.size, (0, 0, 0))
  397. _im.paste(im=img_gray, mask=mask)
  398. # img = Image.open(img)
  399. # _im = Image.new('RGB', img.size, (0, 0, 0))
  400. # _im.paste(img, (0, 0), img)
  401. # _im.show()
  402. # raise 1
  403. img = cv2.cvtColor(np.asarray(_im), cv2.COLOR_RGB2BGR)
  404. hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
  405. H, S, V = cv2.split(hsv)
  406. # print(V[0])
  407. v = V[V != 0] # 亮度非零的值
  408. average_v = sum(v) / len(v)
  409. return average_v