import cv2 import numpy as np from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageDraw, ImageChops, ImageStat import settings # 锐化图片 def sharpen_image(img, factor=1.0): # 创建一个ImageEnhance对象 enhancer = ImageEnhance.Sharpness(img) # 应用增强,值为0.0给出模糊图像,1.0给出原始图像,大于1.0给出锐化效果 # 调整这个值来增加或减少锐化的程度 sharp_img = enhancer.enhance(factor) return sharp_img def to_resize(_im, width=None, high=None) -> Image: _im_x, _im_y = _im.size if width and high: if _im_x >= _im_y: high = None else: width = None if width: re_x = int(width) re_y = int(_im_y * re_x / _im_x) else: re_y = int(high) re_x = int(_im_x * re_y / _im_y) _im = _im.resize((re_x, re_y),resample=settings.RESIZE_IMAGE_MODE) return _im def pil_to_cv2(pil_image): # 将 PIL 图像转换为 RGB 或 RGBA 格式 if pil_image.mode != 'RGBA': pil_image = pil_image.convert('RGBA') # 将 PIL 图像转换为 numpy 数组 cv2_image = np.array(pil_image) # 由于 PIL 的颜色顺序是 RGB,而 OpenCV 的颜色顺序是 BGR,因此需要交换颜色通道 cv2_image = cv2.cvtColor(cv2_image, cv2.COLOR_RGBA2BGRA) return cv2_image def cv2_to_pil(cv_img): return Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)) def get_mini_crop_img(img): old_x, old_y = img.size x1, y1, x2, y2 = img.getbbox() goods_w, goods_h = x2 - x1, y2 - y1 _w, _h = int(goods_w / 10), int(goods_h / 10) # 上下左右扩展位置 new_x1, new_y1, new_x2, new_y2 = x1 - _w, y1 - _h, x2 + _w, y2 + _h # 防止超限 new_x1 = 0 if new_x1 < 0 else new_x1 new_y1 = 0 if new_y1 < 0 else new_y1 new_x2 = old_x if new_x2 > old_x else new_x2 new_y2 = old_y if new_y2 > old_y else new_y2 img = img.crop((new_x1, new_y1, new_x2, new_y2)) # 切图 box = (new_x1, new_y1, new_x2, new_y2) return img, box def expand_or_shrink_mask(pil_image, expansion_radius=5, iterations=1, blur_radius=0): """ 对输入的PIL黑白图像(掩膜)进行膨胀或腐蚀操作,以扩大或缩小前景区域。 :param pil_image: 输入的PIL黑白图像对象 :param expansion_radius: 结构元素大小,默认是一个3x3的小正方形;负值表示收缩 :param iterations: 操作迭代次数,默认为1次 :param blur_radius: 高斯模糊的半径,默认不应用模糊 :return: 修改后的PIL黑白图像对象 """ # 将PIL图像转换为numpy数组,并确保其为8位无符号整数类型 img_np = np.array(pil_image).astype(np.uint8) # 如果不是二值图像,则应用阈值处理 if len(np.unique(img_np)) > 2: # 检查是否为二值图像 _, img_np = cv2.threshold(img_np, 127, 255, cv2.THRESH_BINARY) # 定义结构元素(例如正方形) abs_expansion_radius = abs(expansion_radius) kernel = np.ones((abs_expansion_radius, abs_expansion_radius), np.uint8) # 根据expansion_radius的符号选择膨胀或腐蚀操作 if expansion_radius >= 0: modified_img_np = cv2.dilate(img_np, kernel, iterations=iterations) else: modified_img_np = cv2.erode(img_np, kernel, iterations=iterations) # 如果提供了blur_radius,则应用高斯模糊 if blur_radius > 0: modified_img_np = cv2.GaussianBlur(modified_img_np, (blur_radius * 2 + 1, blur_radius * 2 + 1), 0) # 将numpy数组转换回PIL图像 modified_pil_image = Image.fromarray(modified_img_np) return modified_pil_image def expand_mask(mask, expansion_radius=5, blur_radius=0): # 对蒙版进行膨胀处理 mask = mask.filter(ImageFilter.MaxFilter(expansion_radius * 2 + 1)) # 应用高斯模糊滤镜 if blur_radius > 0: mask = mask.filter(ImageFilter.GaussianBlur(blur_radius)) return mask def find_lowest_non_transparent_points(cv2_png): # cv2_png 为cv2格式的带有alpha通道的图片 alpha_channel = cv2_png[:, :, 3] """使用Numpy快速查找每列的最低非透明点""" h, w = alpha_channel.shape # 创建一个掩码,其中非透明像素为True mask = alpha_channel > 0 # 使用np.argmax找到每列的第一个非透明像素的位置 # 因为是从底部向上找,所以需要先翻转图像 flipped_mask = np.flip(mask, axis=0) min_y_values = h - np.argmax(flipped_mask, axis=0) - 1 # 将全透明列的值设置为-1 min_y_values[~mask.any(axis=0)] = -1 return min_y_values def draw_shifted_line( image, min_y_values, shift_amount=15, one_line_pos=(0, 100), line_color=(0, 0, 0), line_thickness=20, app=None, crop_image_box=None, ): """ image:jpg cv2格式的原始图 min_y_values 透明图中,不透明区域的最低那条线 shift_amount:向下偏移值 line_color:线颜色 line_thickness:线宽 """ # 将最低Y值向下迁移20个像素,但确保不超过图片的高度 # 创建空白图片 image = np.ones((image.shape[0], image.shape[1], 3), dtype=np.uint8) * 255 # 对线条取转成图片 shifted_min_y_values = np.clip(min_y_values + shift_amount, 0, image.shape[0] - 1) # 使用Numpy索引批量绘制直线 min_y_threshold = 50 # Y轴像素小于50的不处理 valid_x = (shifted_min_y_values >= min_y_threshold) & (shifted_min_y_values != -1) # print("valid_x", len(valid_x)) # 对曲线取平均值 # # 对曲线取平均值 # min_y = np.max(min_y_values) # min_y_values_2 = min_y_values + min_y # min_y_values_2 = min_y_values_2 / 2 # min_y_values_2 = min_y_values_2.astype(int) # shifted_min_y_values = np.clip(min_y_values_2 + shift_amount, 0, image.shape[0] - 1) if settings.SHADOW_PROCESSING == 0: if crop_image_box: # 800像素宽;鞋子前后20%进行移除 shoe_width = crop_image_box[2] - crop_image_box[0] _half_show_width = int(shoe_width * 0.15) valid_x[: crop_image_box[0] + _half_show_width] = False valid_x[crop_image_box[2] - _half_show_width :] = False x_coords = np.arange(image.shape[1])[valid_x] y_start = shifted_min_y_values[valid_x] y_end = y_start + line_thickness # todo 使用Numpy广播机制创建线条区域的索引 # todo 鞋子曲线线条 if settings.SHADOW_PROCESSING == 0: for x, start, end in zip(x_coords, y_start, y_end): image[start:end, x, :3] = line_color # 只修改RGB通道 # 计算整个图像的最低非透明点 lowest_y = ( np.max(min_y_values[min_y_values != -1]) if np.any(min_y_values != -1) else -1 ) # 绘制原最低非透明点处的线 cv2.line( image, (one_line_pos[0], lowest_y + settings.LOWER_Y), (one_line_pos[1], lowest_y + 5), line_color, thickness=line_thickness, ) # 调整 _y = lowest_y + 18 _y = lowest_y + 200 if _y > image.shape[0]: # 超过图片尺寸 _y = image.shape[0] - settings.CHECK_LOWER_Y return image, _y def clean_colors(img): # 转成灰度图 img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) return img def calculated_shadow_brightness(img: Image): # 打开图片并转换为灰度模式 image = img.convert('L') # 将图片数据转为numpy数组 image_data = np.array(image) # 创建布尔掩码以识别非白色区域 non_white_mask = image_data < 252 # 使用掩码提取非白色像素的亮度值 non_white_values = image_data[non_white_mask] # print(len(non_white_values),len(image_data)) # 如果存在非白色像素,则计算平均亮度;否则返回0 if len(non_white_values) > 0: average_brightness = np.mean(non_white_values) else: average_brightness = 0 # 没有非白色像素时的情况 return average_brightness def levels_adjust(img, Shadow, Midtones, Highlight, OutShadow, OutHighlight, Dim): # 色阶处理 # img 为cv2格式 # dim = 3的时候调节RGB三个分量, 0调节B,1调节G,2调节R if Dim == 3: mask_shadow = img < Shadow img[mask_shadow] = Shadow mask_Highlight = img > Highlight img[mask_Highlight] = Highlight else: mask_shadow = img[..., Dim] < Shadow img[mask_shadow] = Shadow mask_Highlight = img[..., Dim] > Highlight img[mask_Highlight] = Highlight if Dim == 3: Diff = Highlight - Shadow rgbDiff = img - Shadow clRgb = np.power(rgbDiff / Diff, 1 / Midtones) outClRgb = clRgb * (OutHighlight - OutShadow) / 255 + OutShadow data = np.array(outClRgb * 255, dtype='uint8') img = data else: Diff = Highlight - Shadow rgbDiff = img[..., Dim] - Shadow clRgb = np.power(rgbDiff / Diff, 1 / Midtones) outClRgb = clRgb * (OutHighlight - OutShadow) / 255 + OutShadow data = np.array(outClRgb * 255, dtype='uint8') img[..., Dim] = data return img def calculate_average_brightness_opencv(img_gray, rows_to_check): # 二值化的图片 CV对象 # 计算图片亮度 height, width = img_gray.shape brightness_list = [] for row in rows_to_check: if 0 <= row < height: # 直接计算该行的平均亮度 row_data = img_gray[row, :] average_brightness = np.mean(row_data) brightness_list.append(average_brightness) else: print(f"警告:行号{row}超出图片范围,已跳过。") return brightness_list def get_extremes_from_transparent(img, alpha_threshold=10): """ 直接从透明图获取最左和最右的XY坐标 Args: image_path: 透明图像路径 alpha_threshold: 透明度阈值,低于此值视为透明 Returns: dict: 包含最左、最右坐标等信息 """ # 确保有alpha通道 if img.mode != 'RGBA': img = img.convert('RGBA') # 转换为numpy数组 img_array = np.array(img) # 提取alpha通道 alpha = img_array[:, :, 3] # 根据阈值创建mask mask = alpha > alpha_threshold if not np.any(mask): print("警告: 没有找到非透明像素") return None # 获取所有非透明像素的坐标 rows, cols = np.where(mask) if len(rows) == 0: return None # 找到最左和最右的像素 # 最左: 列坐标最小 leftmost_col = np.min(cols) # 最右: 列坐标最大 rightmost_col = np.max(cols) # 对于最左列,找到所有在该列的像素,然后取中间或特定位置的Y坐标 leftmost_rows = rows[cols == leftmost_col] rightmost_rows = rows[cols == rightmost_col] # 选择策略:可以取平均值、最小值、最大值或中位数 strategy = 'median' # 可选: 'min', 'max', 'mean', 'median', 'top', 'bottom' def get_y_coordinate(rows_values, strategy='median'): if strategy == 'min': return np.min(rows_values) elif strategy == 'max': return np.max(rows_values) elif strategy == 'mean': return int(np.mean(rows_values)) elif strategy == 'median': return int(np.median(rows_values)) elif strategy == 'top': return np.min(rows_values) elif strategy == 'bottom': return np.max(rows_values) return int(np.median(rows_values)) # 获取最左点的Y坐标 leftmost_y = get_y_coordinate(leftmost_rows, strategy) # 获取最右点的Y坐标 rightmost_y = get_y_coordinate(rightmost_rows, strategy) result = { 'leftmost': (int(leftmost_col), int(leftmost_y)), 'rightmost': (int(rightmost_col), int(rightmost_y)), 'image_size': img.size, # (width, height) 'alpha_threshold': alpha_threshold, 'pixel_count': len(rows), 'strategy': strategy } return result def create_polygon_mask_from_points(img, left_point, right_point): """ 根据两个点和图片边界创建多边形mask 形成四边形:图片左上角 → left_point → right_point → 图片右上角 → 回到左上角 Args: left_point: (x, y) 左侧点 right_point: (x, y) 右侧点 Returns: Image: 多边形mask list: 多边形顶点坐标 """ # 打开图片获取尺寸 img_width, img_height = img.size # 创建mask(全黑) mask = Image.new('L', (img_width, img_height), 255) draw = ImageDraw.Draw(mask) # 定义多边形顶点(顺时针或逆时针顺序) # 四边形:左上角 → left_point → right_point → 右上角 polygon_points = [ (-1, -1), # 图片左上角 (-1, left_point[1]), # 左侧点x=0 (left_point[0], left_point[1]), # 左侧点 (right_point[0], right_point[1]), # 右侧点 (img_width, right_point[1]), # 右侧点y=0 (img_width, -1), # 图片右上角 ] # 绘制填充多边形 draw.polygon(polygon_points, fill=0, outline=255) return mask def transparent_to_mask_pil(img, threshold=0, is_invert=False): """ 将透明图像转换为mask threshold: 透明度阈值,低于此值的像素被视为透明 """ # 确保图像有alpha通道 if img.mode != 'RGBA': img = img.convert('RGBA') # 分离通道 r, g, b, a = img.split() # 将alpha通道转换为二值mask # 阈值处理:alpha值低于阈值的设为0(透明),否则设为255(不透明) if is_invert is False: mask = a.point(lambda x: 0 if x <= threshold else 255) else: mask = a.point(lambda x: 255 if x <= threshold else 0) return mask # 两个MASK取交集 def mask_intersection(mask1: Image.Image, mask2: Image.Image) -> Image.Image: """ 对两个 PIL mask 图像取交集(逻辑 AND) - 输入:两个 mode='L' 的灰度图(0=假,非0=真) - 输出:新的 mask,交集区域为 255,其余为 0(可选) """ # 转为 numpy 数组 arr1 = np.array(mask1) arr2 = np.array(mask2) # 确保形状一致 assert arr1.shape == arr2.shape, "Mask shapes must match" # 转为布尔:非零即 True bool1 = arr1 > 0 bool2 = arr2 > 0 # 交集:逻辑与 intersection = bool1 & bool2 # 转回 uint8:True→255, False→0(标准 mask 格式) result = (intersection * 255).astype(np.uint8) return Image.fromarray(result, mode='L') def brightness_check(img_gray, mask): img_gray = cv2_to_pil(img_gray) img = Image.new("RGBA", img_gray.size, (255, 255, 255, 0)) img.paste(im=img_gray,mask=mask) data = np.array(img) # shape: (H, W, 4) # 分离通道 r, g, b, a = data[..., 0], data[..., 1], data[..., 2], data[..., 3] # 创建非透明掩码(Alpha > 0) mask = a > 0 # 如果没有非透明像素,返回 0 或 NaN if not np.any(mask): return 0.0 # 或者 raise ValueError("No opaque pixels") # 计算亮度(仅对非透明区域) # 使用 ITU-R BT.601 标准权重 luminance = 0.299 * r[mask] + 0.587 * g[mask] + 0.114 * b[mask] # 返回平均亮度 return float(np.mean(luminance))