Procházet zdrojové kódy

```
feat(image-processing): 新增图片阴影处理v3版本功能

- 添加get_mask_and_config_v3方法,实现新的阴影蒙版生成算法
- 支持动态配置阴影处理参数,包括亮度阈值和透明度
- 优化图片处理流程,提升处理速度和效果质量
- 新增多个图片处理基础函数,包括透明图坐标获取、多边形蒙版创建等
- 实现亮度自动检测和色阶调整功能
```

rambo před 2 týdny
rodič
revize
06ba0de8d8

+ 142 - 16
python/service/grenerate_main_image_test.py

@@ -33,7 +33,113 @@ class GeneratePic(object):
         self.is_test = is_test
         self.saver = ImageSaver()
         pass
+    @time_it
+    def get_mask_and_config_v3(self, im_jpg: Image, im_png: Image, curve_mask: bool,grenerate_main_pic_brightness:int):
+        """
+        步骤:
+        1、尺寸进行对应缩小
+        2、查找并设定鞋底阴影蒙版
+        3、自动色阶检查亮度
+        4、输出自动色阶参数、以及放大的尺寸蒙版
+        """
+        # ===================尺寸进行对应缩小(提升处理速度)
+        im_jpg = to_resize(im_jpg, width=600)
+        im_png = to_resize(im_png, width=600)
+
+
+        # =========================两个蒙版叠加,删除上半部分的图
+        # 获取透明图的左右点
+        result = get_extremes_from_transparent(im_png)
+        # 创建多边形mask(并进行左右偏移)
+        left_point = (result["leftmost"][0], result["leftmost"][1] - 50)
+        right_point = (result["rightmost"][0], result["rightmost"][1] - 50)
+        mask_other_2 = create_polygon_mask_from_points(img=im_png, left_point=left_point, right_point=right_point)
+
+        # 透明图转mask 将原图扩边一些,并填充白色
+        mask_other_1 = transparent_to_mask_pil(im_png, is_invert=False)
+        mask_other_1 = expand_or_shrink_mask(pil_image=mask_other_1, expansion_radius=40, blur_radius=0)
+        new_image_1 = Image.new("RGBA", im_png.size, (255, 255, 255, 0))
+        im_grey_jpg = im_jpg.convert("L").convert("RGB")
+        inverted_mask_other_1 = ImageChops.invert(mask_other_1)
+
+        # 两个mask 取交集
+        mask_other_2 = mask_other_2.convert("L")
+        # 返回的蒙版区域
+        return_mask = mask_other_2
+
+        new_mask = mask_intersection(inverted_mask_other_1, mask_other_2)
+        # new_mask.show()
+        # return_mask.show()
+        # TODO 待移除
+
+        # ====================生成新的图片
+        print("84  生成新的图片")
+        bg = Image.new(mode="RGB", size=im_png.size, color=(255, 255, 255))
+        bg.paste(im=im_jpg, mask=new_mask)  # 只粘贴有阴影的地方
+        # bg.show()
+
+        # ==================自动色阶处理======================
+        # 对上述拼接后的图片进行自动色阶处理
+        _im = cv2.cvtColor(np.asarray(bg), cv2.COLOR_RGB2BGR)
+        # 背景阴影
+        im_shadow = cv2.cvtColor(_im, cv2.COLOR_BGR2GRAY)
+
+        print("copy.copy(im_shadow)")
+        _im_shadow = copy.copy(im_shadow)
+        Midtones = 0.7
+        Highlight = 235
+        k = copy.copy(settings.COLOR_GRADATION_CYCLES)
+        print("开始循环识别")
+        xunhuan = 0
+        while k:
+            xunhuan += 1
+            k -= 1
+            Midtones += 0.035
+            if Midtones > 1.7:
+                Midtones = 1.7
+            Highlight -= 3
+
+            _im_shadow = levels_adjust(img=im_shadow,
+                                       Shadow=0,
+                                       Midtones=Midtones,
+                                       Highlight=Highlight,
+                                       OutShadow=0,
+                                       OutHighlight=255, Dim=3)
+
+            brightness_value = brightness_check(img_gray=_im_shadow, mask=new_mask)
 
+            print("循环识别:{},Midtones:{},Highlight:{},brightness_value:{}".format(xunhuan,
+                                                                                Midtones,
+                                                                                Highlight,
+                                                                                brightness_value))
+                                                                                    
+
+            if brightness_value >= grenerate_main_pic_brightness:
+                # //GRENERATE_MAIN_PIC_BRIGHTNESS 亮度校验
+                break
+
+        im_shadow = cv2_to_pil(_im_shadow)
+        # if self.is_test:
+        #     im_shadow.show()
+
+        # ========================================================
+        # 计算阴影的亮度,用于确保阴影不要太黑
+
+        # 1、图片预处理,只保留阴影
+        only_shadow_img = im_shadow.copy()
+        only_shadow_img.paste(Image.new(mode="RGBA", size=only_shadow_img.size, color=(255, 255, 255, 255)),
+                              mask=im_png)
+        # only_shadow_img.show()
+        average_brightness = calculated_shadow_brightness(only_shadow_img)
+        print("average_brightness:", average_brightness)
+
+        config = {
+            "Midtones": Midtones,
+            "Highlight": Highlight,
+            "average_brightness": average_brightness,
+        }
+
+        return return_mask, config
     @time_it
     def get_mask_and_config(self, im_jpg: Image, im_png: Image, curve_mask: bool):
         """
@@ -491,9 +597,17 @@ class GeneratePic(object):
 
         # ================自动色阶处理
         _s = time.time()
-        shadow_mask, config = self.get_mask_and_config(
-            im_jpg=im_shadow, im_png=cut_image, curve_mask=curve_mask
-        )
+        image_mask_config = settings.getSysConfigs("basic_configs", "image_mask_config", {"mode":0,"opacity":0.5,"grenerate_main_pic_brightness":254})
+        print("阴影图处理参数===>>>",image_mask_config)
+        image_mask_mode = image_mask_config.get("mode",0)
+        image_mask_opacity = float(image_mask_config.get("opacity",0.5))
+        image_mask_grenerate_main_pic_brightness = int(image_mask_config.get("grenerate_main_pic_brightness",254))
+        if image_mask_mode ==0:
+            shadow_mask, config = self.get_mask_and_config(
+                im_jpg=im_shadow, im_png=cut_image, curve_mask=curve_mask
+            )
+        else:
+            shadow_mask, config = self.get_mask_and_config_v3(im_jpg=im_shadow, im_png=cut_image, curve_mask=curve_mask,grenerate_main_pic_brightness=image_mask_grenerate_main_pic_brightness)
         print("242  need_time_2:{}".format(time.time() - _s))
 
         shadow_mask = shadow_mask.resize(im_shadow.size)
@@ -521,25 +635,37 @@ class GeneratePic(object):
 
         # ================处理阴影的亮度==================
         average_brightness = config["average_brightness"]
-        if config["average_brightness"] < 180:
-            # 调整阴影亮度
+        if image_mask_mode ==0:
+            if config["average_brightness"] < 180:
+                # 调整阴影亮度
+                backdrop_prepped = np.asfarray(
+                    Image.new(mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255))
+                )
+                im_shadow = im_shadow.convert("RGBA")
+                source_prepped = np.asfarray(im_shadow)
+                # im_shadow.show()
+
+                opacity = (average_brightness - 30) / 160
+                opacity = max(0.5, min(opacity, 1))
+
+                print("阴影透明度:{}%".format(int(opacity * 100)))
+                blended_np = multiply(
+                    backdrop_prepped, source_prepped, opacity=int(opacity * 100) / 100
+                )
+                im_shadow = Image.fromarray(np.uint8(blended_np)).convert("RGB")
+            # im_shadow.show()
+        else:
             backdrop_prepped = np.asfarray(
-                Image.new(mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255))
-            )
+                    Image.new(mode="RGBA", size=im_shadow.size, color=(255, 255, 255, 255))
+                )
             im_shadow = im_shadow.convert("RGBA")
             source_prepped = np.asfarray(im_shadow)
-            # im_shadow.show()
-
-            opacity = (average_brightness - 30) / 160
-            opacity = max(0.5, min(opacity, 1))
-
-            print("阴影透明度:{}%".format(int(opacity * 100)))
+            opacity_params = int(image_mask_opacity * 100)
+            print("阴影透明度:{}%".format(opacity_params))
             blended_np = multiply(
-                backdrop_prepped, source_prepped, opacity=int(opacity * 100) / 100
+                backdrop_prepped, source_prepped, opacity=opacity_params / 100
             )
             im_shadow = Image.fromarray(np.uint8(blended_np)).convert("RGB")
-            # im_shadow.show()
-
         # 把原图粘贴回去,避免色差
         im_shadow.paste(cut_image, (0, 0), mask=cut_image)
         # _new_im_shadow.show()

+ 173 - 1
python/service/image_deal_base_func.py

@@ -1,6 +1,6 @@
 import cv2
 import numpy as np
-from PIL import Image, ImageEnhance, ImageFilter, ImageOps
+from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageDraw, ImageChops, ImageStat
 import settings
 
 # 锐化图片
@@ -272,3 +272,175 @@ def calculate_average_brightness_opencv(img_gray, rows_to_check):
             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))

+ 5 - 1
python/settings.py

@@ -275,7 +275,11 @@ IMAGE_SAVE_MAX_WORKERS = int(
 COLOR_GRADATION_CYCLES = int(
     getSysConfigs("other_configs", "color_gradation_cycles", 23)
 )  # 色阶处理循环次数
-
+# grenerate_main_pic_brightness 亮度范围 默认值254 范围 200 -255 步长 1
+# opacity 透明度 默认值 0.5 范围 0.5-1 步长 0.1
+# IMAGE_MASK_CONFIG = int(
+#     getSysConfigs("basic_configs", "image_mask_config", {"mode":0,"opacity":0.5,"grenerate_main_pic_brightness":254})
+# )  # 色阶处理循环次数
 
 def recordDataPoint(token=None, page="", uuid=None, data=""):
     """记录日志"""