image_deal_base_func.py 15 KB

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