Kaynağa Gözat

Merge remote-tracking branch 'origin/segment-object-0713' into feature-frontend

panqiuyao 14 saat önce
ebeveyn
işleme
a7fa643916

Dosya farkı çok büyük olduğundan ihmal edildi
+ 310 - 269
python/api.py


+ 15 - 15
python/config.ini

@@ -1,31 +1,31 @@
 [app]
 # 应用名称
-app_name=智慧拍-后端应用
+app_name = 智慧拍-后端应用
 # 应用版本号
-version=1.0.0
+version = 1.0.0
 # 应用host地址
-host=127.0.0.1
+host = 127.0.0.1
 # 应用服务启动名称
-app_run=api:app
+app_run = api:app
 # 端口号
-port=7074
-debug=false
-env=prod
+port = 7074
+debug = false
+env = prod
 # 线程数
-works=1
-project=惠利玛
-is_upload=true
+works = 1
+project = 惠利玛
+is_upload = true
 [log]
 # 日志相关
-log_file_name=app.log
+log_file_name = app.log
 #最大字节数
-max_bytes=1024000
+max_bytes = 1024000
 #备份数量
-backup_counts=3
+backup_counts = 3
 # 地址
-hlm_host=https://dev2.pubdata.cn
+hlm_host = https://dev2.pubdata.cn
 
-project=红蜻蜓
+project = 红蜻蜓
 
 
 

+ 88 - 17
python/conifg_info.py

@@ -153,7 +153,7 @@ class ConfigManager:
                 case "is_camera_has_focal":
                     dataParams[item["key"]] = True if value else False
                 case "camera_focal_distance":
-                    dataParams[item["key"]] = int(value)
+                    dataParams[item["key"]] = int(float(value))
                 case "camera_focal_ratio":
                     dataParams[item["key"]] = float(value)
         return dataParams
@@ -356,58 +356,70 @@ class ConfigManager:
             _key="auto_check_motor_stop", _addr=121, _tips="每秒自动检查电机是否停止"
         )
 
-        # 前后移动电机是否存在
-        self.turntable_move_is_exist = 1
-        self._set_key_int(
-            _key="turntable_move_is_exist", _addr=122, _tips="前后移动电机是否存在"
-        )
-
         # 升降电机的到位偏差
         self.camera_high_motor_arrived_deviation = 100
         self._set_key_int(
-            _key="camera_high_motor_arrived_deviation", _addr=123, _tips="升降电机的到位偏差"
+            _key="camera_high_motor_arrived_deviation", _addr=122, _tips="升降电机的到位偏差"
         )
 
         # 相机角度电机的到位偏差
         self.camera_steering_arrived_deviation = 100
         self._set_key_int(
-            _key="camera_steering_arrived_deviation", _addr=124, _tips="相机角度电机的到位偏差"
+            _key="camera_steering_arrived_deviation", _addr=123, _tips="相机角度电机的到位偏差"
         )
 
         # 焦段电机的到位偏差
         self.camera_zoom_arrived_deviation = 100
         self._set_key_int(
-            _key="camera_zoom_arrived_deviation", _addr=125, _tips="焦段电机的到位偏差"
+            _key="camera_zoom_arrived_deviation", _addr=124, _tips="焦段电机的到位偏差"
         )
 
         # 前后移动电机的到位偏差
         self.move_turntable_arrived_deviation = 100
         self._set_key_int(
-            _key="move_turntable_arrived_deviation", _addr=126, _tips="前后移动电机的到位偏差"
+            _key="move_turntable_arrived_deviation", _addr=125, _tips="前后移动电机的到位偏差"
         )
 
         # 转盘角度电机的到位偏差
         self.turntable_steering_arrived_deviation = 100
         self._set_key_int(
-            _key="turntable_steering_arrived_deviation", _addr=127, _tips="转盘角度电机的到位偏差"
+            _key="turntable_steering_arrived_deviation", _addr=126, _tips="转盘角度电机的到位偏差"
         )
 
         # 转盘转速比例
         self.turntable_speed_raito = 1.0
         self._set_key_float(
-            _key="turntable_speed_raito", _addr=128, _tips="转盘转速比例"
+            _key="turntable_speed_raito", _addr=127, _tips="转盘转速比例"
         )
 
         # 变焦初始化力矩
         self.c_torque_max_electric = 800
         self._set_key_int(
-            _key="c_torque_max_electric", _addr=129, _tips="变焦初始化力矩"
+            _key="c_torque_max_electric", _addr=128, _tips="变焦初始化力矩"
         )
 
         # 变焦运动方向
         self.zoom_dir = 0
         self._set_key_int(
-            _key="zoom_dir", _addr=130, _tips="变焦运动方向"
+            _key="zoom_dir", _addr=129, _tips="变焦运动方向"
+        )
+
+        # 伺服加速时间单位ms
+        self.servo_up_time = 1300
+        self._set_key_int(
+            _key="servo_up_time", _addr=130, _tips="伺服加速时间单位ms"
+        )
+
+        # 伺服减速时间单位ms
+        self.servo_down_time = 1300
+        self._set_key_int(
+            _key="servo_down_time", _addr=131, _tips="伺服减速时间单位ms"
+        )
+
+        # 伺服最大速度
+        self.servo_max_speed = 1800
+        self._set_key_int(
+            _key="servo_max_speed", _addr=132, _tips="伺服最大速度"
         )
 
         # -------------------------------------
@@ -464,8 +476,10 @@ class ConfigManager:
         self._set_key_int(_key="camera_has_focal", _addr=208, _tips="相机调焦功能支持")
 
         # 相机焦段最大运动距离
-        self.camera_max_distance = 8000
-        self._set_key_int(_key="camera_max_distance", _addr=209, _tips="相机焦段最大运动距离")
+        self.camera_zoom_max_distance = 1
+        self._set_key_float(
+            _key="camera_zoom_max_distance", _addr=209, _tips="相机焦段最大运动距离", _precision=2
+        )
 
         # 相机焦段步进ratio
         self.camera_focal_ratio = 0.0
@@ -488,6 +502,30 @@ class ConfigManager:
             _key="servo_add_v", _addr=212, _tips="伺服电机每次运动后的偏移数值"
         )
 
+        # 伺服触发报警后安全时间
+        self.error_delay = 2.0
+        self._set_key_float(
+            _key="error_delay", _addr=213, _tips="伺服触发报警后安全时间"
+        )
+
+        # 是否自动判断关机
+        self.is_c_to_power_off = 1
+        self._set_key_int(
+            _key="is_c_to_power_off", _addr=214, _tips="是否自动判断关机"
+        )
+
+        # 相机焦段状态
+        self.camera_zoom_motor_state = 0
+        self._set_key_float(
+            _key="camera_zoom_motor_state", _addr=215, _tips="相机焦段状态", _readonly=True
+        )
+
+        # 电机判断停止速度
+        self.servo_speed_stop = 100
+        self._set_key_int(
+            _key="servo_speed_stop", _addr=216, _tips="电机判断停止速度"
+        )
+
         # =====================================================
         # 获取剩余内存
         self.get_memory = 0
@@ -503,3 +541,36 @@ class ConfigManager:
         self._set_key_float(
             _key="camera_focal_set", _addr=400, _tips="相机焦段功能设置", _readonly=True
         )
+        # 伺服力矩限制
+        self.servo_torque = 60
+        self._set_key_int(_key="servo_torque", _addr=219, _tips="伺服力矩限制", )
+        # 中断触发是否反转
+        self.irq_change = 1
+        self._set_key_int(_key="irq_change", _addr=220, _tips="中断触发是否反转", )
+
+        # 光栅升降沿触发
+        self.light_estop_irq = 1
+        self._set_key_int(_key="light_estop_irq", _addr=217, _tips="光栅升降沿触发", )
+
+        # 升降底部预留高度
+        self.bottom_limit = 20
+        self._set_key_int(_key="bottom_limit", _addr=218, _tips="升降底部预留高度", )
+
+        # 转盘加速度
+        self.turntable_up_speed = 250
+        self._set_key_int(_key="turntable_up_speed", _addr=133, _tips="转盘加速度", )
+        # 转盘减速度
+        self.turntable_down_speed = 250
+        self._set_key_int(_key="turntable_down_speed", _addr=134, _tips="转盘减速度", )
+        # 转盘最大速度 默认可以是1000
+        self.turntable_max_speed = 0
+        self._set_key_int(_key="turntable_max_speed", _addr=135, _tips="转盘最大速度", )
+        # 前后加速度
+        self.qh_up_speed = 250
+        self._set_key_int(_key="qh_up_speed", _addr=136, _tips="前后加速度", )
+        # 前后减速度
+        self.qh_down_speed = 250
+        self._set_key_int(_key="qh_down_speed", _addr=137, _tips="前后减速度", )
+        # 前后最大速度 默认可以是1000
+        self.qh_max_speed = 0
+        self._set_key_int(_key="qh_max_speed", _addr=138, _tips="前后最大速度", )

+ 0 - 0
python/custom_plugins/plugins/detail_template/miniso/__init__.py


+ 374 - 0
python/custom_plugins/plugins/detail_template/miniso/detail_miniso1.py

@@ -0,0 +1,374 @@
+"""
+步骤:
+1、整理需要处理的款号图-输出款号图文件夹
+2、整理所有相关的图片作为素材图
+3、按要求进行拼接
+"""
+import os
+from PIL import ImageFont
+import sys
+import settings
+
+# from module.view_control.generate_goods_no_detail_pic.detail_generate_base import DetailBase
+# from module.view_control.generate_goods_no_detail_pic.pic_deal import PictureProcessing
+is_test_plugins = False
+try:
+    is_test_plugins = settings.is_test_plugins
+except:
+    is_test_plugins = False
+
+if is_test_plugins:
+    from custom_plugins.plugins_mode.detail_generate_base import DetailBase
+    from custom_plugins.plugins_mode.pic_deal import PictureProcessing
+else:
+    from plugins_mode.detail_generate_base import DetailBase
+    from plugins_mode.pic_deal import PictureProcessing
+
+plugins_name = "详情模板"
+company_name_list = ["miniso"]
+template_name = "miniso-1"
+
+
+# 运动鞋详情页
+
+class DetailPicGet(DetailBase):
+    need_view = ["俯视", "侧视", "后跟", "鞋底", "内里"]
+    root = r"{}\resources\detail_temp\miniso\1".format(os.getcwd())
+
+    def __init__(self, goods_no, goods_no_value: dict, out_put_dir, windows=None, test=False, excel_data=None,
+                 assigned_page_list=None, **kwargs):
+
+        super().__init__(goods_no, goods_no_value, out_put_dir, windows=windows, excel_data=excel_data,
+                         assigned_page_list=assigned_page_list)
+        self.template_name = template_name
+        self.root = r"{}\resources\detail_temp\miniso\1".format(os.getcwd())
+        self.basic_width = PictureProcessing(r"{}\1.jpg".format(self.root)).width
+        print(f"run {template_name}")
+        # 红色
+        self.red_color = (248, 55, 69)
+        # 灰色
+        self.gray_color = (227, 227, 227)
+        self.white_color = (255, 255, 255)
+        self.black_color = (0, 0, 0)
+        self.base_bg_color = (255, 255, 255)
+        self.deal_pic_func_list = [
+            self.deal_pic_1,
+            self.deal_pic_2,
+            self.deal_pic_3,
+            self.deal_pic_4,
+            # self.deal_pic_5,
+        ]
+        if test:
+            self.run_test()
+        else:
+            self.run_all()
+
+    def run_test(self):
+        detailed_images = []
+        detailed_images.append(self.deal_pic_1())
+        detailed_images.append(self.deal_pic_2())
+        detailed_images.append(self.deal_pic_3())
+        detailed_images.append(self.deal_pic_4())
+        # detailed_images.append(self.deal_pic_5())
+        img = self.add_pic(detailed_images)
+        if img:
+            self.create_folder(r"{}/{}".format(self.out_put_dir, template_name))
+            img.save(r"{}/{}/{}.jpg".format(self.out_put_dir, template_name, self.goods_no, format="JPEG"))
+            img.show()
+
+    # 斜视图展示  多色进行拼接
+    def deal_pic_1(self):
+        detailed_images = []
+        font_title = ImageFont.truetype(r"resources\ttf\mi\MiSans-Bold.ttf", 60)  # 颜色名称
+        font_ml = ImageFont.truetype(r"resources\ttf\mi\MiSans-Bold.ttf", 40)  # 颜色名称
+        first_bg = PictureProcessing(r"{}\2.jpeg".format(self.root))
+        first_bg = first_bg.resize(value=self.basic_width)
+        circle_bg = PictureProcessing(r"{}\circle.png".format(self.root))
+        circle_bg = circle_bg.resize(value=circle_bg.width + 60)
+        circle_bg = circle_bg.resize(base='high', value=circle_bg.height + 10)
+        title_text = self.get_text_value("标题一")
+        ml_text = self.get_text_value("毫升")
+        title_bg = PictureProcessing().get_text_image_advanced(
+            font=font_title,
+            text=title_text,
+            fill=self.white_color,
+            return_mode="min_image",
+        )
+        ml_bg = PictureProcessing().get_text_image_advanced(
+            font=font_ml,
+            text=ml_text,
+            fill=self.white_color,
+            return_mode="min_image",
+        )
+        first_bg = first_bg.paste_img(top_img=title_bg, value=(40, 100), base="nw")
+        circle_bg = circle_bg.paste_img(top_img=ml_bg, value=(0, 0), base="cc")
+        first_bg = first_bg.paste_img(top_img=circle_bg, value=(40, 300), base="nw")
+        goods_art_no_list = list(self.data.keys())
+        pp_jpg_1, pp_png_1 = self.image_one_pic(return_orign=True,
+                                                goods_art_no=goods_art_no_list[0],
+                                                name="俯视",
+                                                )
+        pp_jpg_1 = pp_jpg_1.resize(value=self.basic_width / 2.8)
+        pp_png_1 = pp_png_1.resize(value=self.basic_width / 2.8)
+        first_bg = first_bg.to_overlay_pic_advance(top_img=pp_jpg_1, top_png_img=pp_png_1, base="nc", value=(0, 380))
+        # 图2
+        second_bg = PictureProcessing("RGB", (self.basic_width, 825), self.white_color)
+        second_bg = second_bg.radius(value=80, circular_pos=(1, 1, 1, 1))
+        second_font_title = ImageFont.truetype(r"resources\ttf\mi\MiSans-Bold.ttf", 70)  # 颜色名称
+        second_title_text = self.get_text_value("标题二")
+        second_title_bg = PictureProcessing().get_text_image_advanced(
+            font=second_font_title,
+            text=second_title_text,
+            fill=self.black_color,
+            return_mode="min_image",
+        )
+        second_bg = second_bg.paste_img(top_img=second_title_bg, value=(68, 50), base="nw")
+        second_sub_bg = PictureProcessing("RGB", (self.basic_width * 0.9, 500), self.red_color)
+        second_sub_bg = second_sub_bg.radius(value=80, circular_pos=(1, 1, 1, 1))
+        _, pp_png_1 = self.image_one_pic(return_orign=True,
+                                         goods_art_no=goods_art_no_list[0],
+                                         name="内里",
+                                         )
+        pp_cup = pp_png_1.resize(value=second_sub_bg.width * 0.6)
+        second_sub_bg = second_sub_bg.paste_img(top_img=pp_cup, value=(0, 0), base="cc")
+        second_bg = second_bg.paste_img(top_img=second_sub_bg, value=(0, 260), base="nc")
+        # 图2结束
+        basic_bg = PictureProcessing("RGB", (self.basic_width, first_bg.height + second_bg.height - 100),
+                                     (227, 227, 227))
+        basic_bg = basic_bg.paste_img(top_img=first_bg, value=(0, 0), base="nc")
+        basic_bg = basic_bg.paste_img(top_img=second_bg, value=(0, first_bg.height - 120), base="nc")
+        detailed_images.append(basic_bg)
+
+        return PictureProcessing(im=self.add_pic(detailed_images))
+
+    # 展示基础信息
+    def deal_pic_2(self):
+        detailed_images = []
+        goods_art_no_list = list(self.data.keys())
+        # 区域1
+        font_title = ImageFont.truetype(r"resources\ttf\mi\MiSans-Bold.ttf", 60)  # 颜色名称
+        title_text = self.get_text_value("标题三")
+        area_1 = PictureProcessing("RGB", (self.basic_width * 0.9, 500), self.red_color)
+        area_1 = area_1.radius(value=80, circular_pos=(1, 1, 1, 1))
+        _, pp_png_1 = self.image_one_pic(return_orign=True,
+                                         goods_art_no=goods_art_no_list[0],
+                                         name="侧视",
+                                         )
+        pp_png_1_copy = pp_png_1
+        pp_png_1 = pp_png_1.resize(value=area_1.width * 0.4)
+        pp_png_1 = pp_png_1.rotate_advance(doge=40)
+        area_1 = area_1.paste_img(top_img=pp_png_1, value=(-100, 20), base="en")
+        # 区域1结束
+        # 区域2
+        area_2 = PictureProcessing("RGB", (self.basic_width * 0.43, 500), self.red_color)
+        area_2 = area_2.radius(value=40, circular_pos=(1, 1, 1, 1))
+        pp_png_1_copy = pp_png_1_copy.resize(value=area_2.width * 1.1)
+        pp_png_1_copy = pp_png_1_copy.rotate_advance(doge=40)
+        area_2 = area_2.paste_img(top_img=pp_png_1_copy, value=(-area_2.width * 1.1, 50), base="en")
+        # 区域2结束
+        # 区域3
+        _, pp_png_3 = self.image_one_pic(return_orign=True,
+                                         goods_art_no=goods_art_no_list[0],
+                                         name="后跟",
+                                         )
+        area_3 = PictureProcessing("RGB", (self.basic_width * 0.43, area_2.height / 2 - 20), self.red_color)
+        area_3 = area_3.radius(value=40, circular_pos=(1, 1, 1, 1))
+        pp_png_3 = pp_png_3.resize(value=area_3.width / 1.5)
+        area_3 = area_3.paste_img(top_img=pp_png_3, value=(0, 0), base="en")
+        # 区域3结束
+        # 区域4
+        _, pp_png_4 = self.image_one_pic(return_orign=True,
+                                         goods_art_no=goods_art_no_list[0],
+                                         name="鞋底",
+                                         )
+        area_4 = PictureProcessing("RGB", (self.basic_width * 0.43, area_2.height / 2 - 20), self.red_color)
+        area_4 = area_4.radius(value=40, circular_pos=(1, 1, 1, 1))
+        pp_png_4 = pp_png_4.resize(value=area_4.width / 1.2)
+        pp_png_4 = pp_png_4.rotate_advance(doge=36)
+        area_4 = area_4.paste_img(top_img=pp_png_4, value=(-40, -80), base="en")
+        # 区域4结束
+        title_bg = PictureProcessing().get_text_image_advanced(
+            font=font_title,
+            text=title_text,
+            fill=self.black_color,
+            return_mode="min_image",
+        )
+        basic_bg = PictureProcessing("RGB", (self.basic_width, area_1.height + area_2.height + 170), self.white_color)
+        basic_bg = basic_bg.radius(value=80, circular_pos=(1, 1, 1, 1))
+        basic_bg = basic_bg.paste_img(top_img=area_1, value=(0, 150), base="nc")
+        basic_bg = basic_bg.paste_img(top_img=area_2, value=(self.basic_width * 0.05, area_1.height + 170), base="nw")
+        basic_bg = basic_bg.paste_img(top_img=area_3, value=(self.basic_width * 0.05, area_1.height + 170), base="en")
+        basic_bg = basic_bg.paste_img(top_img=area_4,
+                                      value=(self.basic_width * 0.05, area_1.height + area_3.height + 210), base="en")
+        basic_bg = basic_bg.paste_img(top_img=title_bg, value=(self.basic_width * 0.1, 50), base="nw")
+        basic_bg = basic_bg.radius(value=80, circular_pos=(1, 1, 0, 0))
+        # 文本介绍
+        # 介绍卖点
+        font_desc = ImageFont.truetype(r"resources\ttf\mi\MiSans-Semibold.ttf", 35)  # 颜色名称
+        text_list = [
+            {
+                "text": "细节一",
+                "default": "轻盈缓震,健步如飞,久走不累",
+                "pos": (self.basic_width * 0.08, 458),
+                "base": "nw",
+            },
+            {
+                "text": "细节二",
+                "default": "透气网面,干爽舒适,告别闷热",
+                "pos": (self.basic_width * 0.08, 1078),
+                "base": "nw",
+            },
+            {
+                "text": "细节三",
+                "default": "柔韧大底,灵活弯折,自在穿行",
+                "pos": (self.basic_width * 0.3, 858),
+                "base": "en",
+            },
+            {
+                "text": "细节四",
+                "default": "柔韧大底,灵活弯折,自在穿行",
+                "pos": (self.basic_width * 0.33, 1118),
+                "base": "en",
+            },
+        ]
+        for text_data in text_list:
+            text = self.get_text_value(text_data["text"])
+            if not text:
+                text = text_data["default"]
+            temp_pp = PictureProcessing().get_text_image_advanced(
+                font=font_desc,
+                text=text,
+                fill=self.white_color,
+                return_mode="min_image",
+            )
+            basic_bg = basic_bg.paste_img(top_img=temp_pp, value=text_data["pos"], base=text_data["base"])
+        hide_basic_bg = PictureProcessing("RGB", (self.basic_width, basic_bg.height),
+                                          self.gray_color)
+        hide_basic_bg = hide_basic_bg.paste_img(top_img=basic_bg, value=(0, 0), base="cc")
+        detailed_images.append(hide_basic_bg)
+        bottom_bg = PictureProcessing("RGB", (self.basic_width, 150), self.white_color)
+        small_title = ImageFont.truetype(r"resources\ttf\mi\MiSans-Light.ttf", 20)  # 颜色名称
+        small_text = "*数据为第三方实验室侧视所得。保温效果因使用环境\n及初始水温可能略有差异,请以实际为准。"
+        small_text_bg = PictureProcessing().get_text_image_advanced(
+            font=small_title,
+            text=small_text,
+            fill=self.black_color,
+            align="center",
+            return_mode="min_image",
+        )
+        hide_bottom_bg = PictureProcessing("RGB", (self.basic_width, bottom_bg.height + 20),
+                                           self.gray_color)
+        bottom_bg = bottom_bg.paste_img(top_img=small_text_bg, value=(0, 0), base="cc")
+        bottom_bg = bottom_bg.radius(value=80, circular_pos=(0, 0, 1, 1))
+        hide_bottom_bg = hide_bottom_bg.paste_img(top_img=bottom_bg, value=(0, 0), base="nc")
+        detailed_images.append(hide_bottom_bg)
+        return PictureProcessing(im=self.add_pic(detailed_images))
+
+    # 细节展示
+    def deal_pic_3(self):
+        detailed_images = []
+        goods_art_no_list = list(self.data.keys())
+        hidden_bg = PictureProcessing("RGB", (self.basic_width, 1140), self.gray_color)
+        white_bg = PictureProcessing("RGB", (self.basic_width, hidden_bg.height - 50), self.white_color)
+        white_bg = white_bg.radius(value=80, circular_pos=(1, 1, 1, 1))
+        red_bg = PictureProcessing(r"{}\2.jpeg".format(self.root))
+        red_bg = red_bg.resize(value=white_bg.width * 0.9)
+        red_bg = red_bg.resize(value=white_bg.height - 60, base='high')
+        # 粘贴
+        pp_jpg_1, pp_png_1 = self.image_one_pic(return_orign=True,
+                                                goods_art_no=goods_art_no_list[0],
+                                                name="俯视",
+                                                )
+        pp_jpg_1 = pp_jpg_1.resize(value=self.basic_width / 2.3)
+        pp_png_1 = pp_png_1.resize(value=self.basic_width / 2.3)
+        red_bg = red_bg.to_overlay_pic_advance(top_img=pp_jpg_1, top_png_img=pp_png_1, base="nc", value=(0, 80))
+        red_bg = red_bg.radius(value=80, circular_pos=(1, 1, 1, 1))
+        font_ml = ImageFont.truetype(r"resources\ttf\mi\MiSans-Bold.ttf", 40)  # 颜色名称
+        circle_bg = PictureProcessing(r"{}\circle.png".format(self.root))
+        circle_bg = circle_bg.resize(value=circle_bg.width + 80)
+        ml_text = "产品实拍"
+        ml_bg = PictureProcessing().get_text_image_advanced(
+            font=font_ml,
+            text=ml_text,
+            fill=self.white_color,
+            return_mode="min_image",
+        )
+        circle_bg = circle_bg.paste_img(top_img=ml_bg, value=(0, 0), base="cc")
+        red_bg = red_bg.paste_img(top_img=circle_bg, value=(40, 50), base="nw")
+        white_bg = white_bg.paste_img(top_img=red_bg, value=(0, 0), base="cc")
+        hidden_bg = hidden_bg.paste_img(top_img=white_bg, value=(0, 0), base="cc")
+        # 展示鞋面材质  粘贴内里顶视图
+        detailed_images.append(hidden_bg)
+        # raise 1
+        return PictureProcessing(im=self.add_pic(detailed_images))
+
+    # 展示产品信息和颜色图
+    def deal_pic_4(self):
+        detailed_images = []
+        face = PictureProcessing(r"{}\face.png".format(self.root))
+        goods_art_no_list = list(self.data.keys())
+        font_1 = ImageFont.truetype(r"resources\ttf\mi\MiSans-Bold.ttf", 60)  # 颜色名称
+        hidden_bg = PictureProcessing("RGB", (self.basic_width, 950), self.gray_color)
+        white_bg = PictureProcessing("RGB", (self.basic_width, hidden_bg.height), self.white_color)
+        white_bg = white_bg.radius(value=80, circular_pos=(1, 1, 0, 0))
+        red_bg2 = PictureProcessing("RGB", (white_bg.width * 0.9, 800), self.red_color)
+        red_bg2 = red_bg2.radius(value=80, circular_pos=(1, 1, 1, 1))
+        ml_text = "产品信息"
+        ml_bg = PictureProcessing().get_text_image_advanced(
+            font=font_1,
+            text=ml_text,
+            fill=self.black_color,
+            return_mode="min_image",
+        )
+        _, pp_png_fu = self.image_one_pic(return_orign=True,
+                                          goods_art_no=goods_art_no_list[0],
+                                          name="俯视",
+                                          )
+        pp_png_fu = pp_png_fu.resize(value=red_bg2.width * 0.4)
+        _, pp_png_ce = self.image_one_pic(return_orign=True,
+                                          goods_art_no=goods_art_no_list[0],
+                                          name="侧视",
+                                          )
+        pp_png_ce = pp_png_ce.resize(value=red_bg2.width * 0.4)
+        red_bg2 = red_bg2.paste_img(top_img=pp_png_fu, value=(40, 50), base="wc")
+        red_bg2 = red_bg2.paste_img(top_img=pp_png_ce, value=(40, 50), base="ec")
+        white_bg = white_bg.paste_img(top_img=red_bg2, value=(0, 130), base="nc")
+        white_bg = white_bg.paste_img(top_img=face, value=(40, 50), base="en")
+        white_bg = white_bg.paste_img(top_img=ml_bg, value=(40, 50), base="nw")
+        hidden_bg = hidden_bg.paste_img(top_img=white_bg, value=(0, 0), base="cc")
+        detailed_images.append(hidden_bg)
+        bottom = PictureProcessing(r"{}\1.jpg".format(self.root))
+        detailed_images.append(bottom)
+        return PictureProcessing(im=self.add_pic(detailed_images))
+
+    # 添加注意事项
+    def deal_pic_5(self):
+        detailed_images = []
+        detailed_images.append(PictureProcessing(r"{}\19.jpg".format(self.root)))
+        detailed_images.append(PictureProcessing(r"{}\20.jpg".format(self.root)))
+        return PictureProcessing(im=self.add_pic(detailed_images))
+
+    def copy_view(self, pp_jpg: PictureProcessing, pp_png: PictureProcessing):
+        pp_bg_jpg = PictureProcessing("RGB", (1600, 1600), (255, 255, 255))
+        pp_bg_png = PictureProcessing("RGBA", (1600, 1600), (255, 255, 255, 0))
+
+        pp_jpg_1_list = [pp_jpg, pp_jpg]
+        pp_png_1_list = [pp_png, pp_png]
+
+        x, y = 0, 0
+        for index, pp_jpg in enumerate(pp_jpg_1_list):
+            pp_bg_jpg = pp_bg_jpg.to_overlay_pic_advance(top_img=pp_jpg,
+                                                         value=(x, y))
+            x += pp_jpg.width - 20
+
+        x, y = 0, 0
+        for index, pp_png in enumerate(pp_png_1_list):
+            pp_bg_png = pp_bg_png.paste_img(top_img=pp_png,
+                                            value=(x, y))
+            x += pp_jpg.width - 20
+
+        min_bbox = pp_bg_png.getbbox()
+        min_bbox = PictureProcessing().expand_bbox(min_bbox, value=50)
+        pp_bg_jpg = pp_bg_jpg.crop(bbox=min_bbox)
+        pp_bg_png = pp_bg_png.crop(bbox=min_bbox)
+        return pp_bg_jpg, pp_bg_png

+ 53 - 45
python/custom_plugins/plugins_mode/detail_generate_base.py

@@ -25,11 +25,14 @@ from logger import logger
 from PIL import ImageFont
 import settings
 from settings import sendSocketMessage
+
 # 全局线程池
 _executor = ThreadPoolExecutor(max_workers=4)
 # 全局事件循环和线程
 _message_loop = None
 _message_thread = None
+
+
 def _start_message_loop():
     """在单独线程中启动事件循环"""
     global _message_loop
@@ -77,14 +80,14 @@ _init_message_thread()
 
 class DetailBase(object):
     def __init__(
-        self,
-        goods_no,
-        goods_no_value: dict,
-        out_put_dir,
-        windows=None,
-        excel_data=None,
-        assigned_page_list=None,
-        output_queue=None,
+            self,
+            goods_no,
+            goods_no_value: dict,
+            out_put_dir,
+            windows=None,
+            excel_data=None,
+            assigned_page_list=None,
+            output_queue=None,
     ):
         self.goods_no = goods_no
         self.output_queue = output_queue
@@ -146,20 +149,21 @@ class DetailBase(object):
 
     def del_detail_folder(self):
         out_path = "{out_put_dir}/切片图-{template_name}".format(
-            out_put_dir=self.out_put_dir,template_name=self.template_name
+            out_put_dir=self.out_put_dir, template_name=self.template_name
         )
         detail_image_path = "{out_put_dir}/详情页-{template_name}".format(
-            out_put_dir=self.out_put_dir,template_name=self.template_name
+            out_put_dir=self.out_put_dir, template_name=self.template_name
         )
         if not os.path.exists(out_path):
             return
         try:
-            shutil.rmtree(out_path,onerror=settings.handle_remove_readonly)
+            shutil.rmtree(out_path, onerror=settings.handle_remove_readonly)
         except BaseException as e:
             print("删除文件夹失败", e)
+
     def del_detail_longimage(self):
         detail_image_path = "{out_put_dir}/详情页-{template_name}".format(
-            out_put_dir=self.out_put_dir,template_name=self.template_name
+            out_put_dir=self.out_put_dir, template_name=self.template_name
         )
         if not os.path.exists(detail_image_path):
             return
@@ -183,7 +187,7 @@ class DetailBase(object):
         detailed_images = self.deal_details()
         self.create_folder(self.out_put_dir)
         detail_path = "{out_put_dir}/切片图-{template_name}".format(
-            out_put_dir=self.out_put_dir, goods_no=self.goods_no,template_name=self.template_name
+            out_put_dir=self.out_put_dir, goods_no=self.goods_no, template_name=self.template_name
         )
         self.create_folder(detail_path)
         self.save_to_png(detailed_images=detailed_images, detail_path=detail_path)
@@ -208,12 +212,12 @@ class DetailBase(object):
             out_put_dir=self.out_put_dir
         )
         for pic_data in self.goods_no_value["货号资料"]:
-            print("正在复制 pic_data",pic_data)
-            scene_image = pic_data.get("场景图",None)
-            model_image = pic_data.get("模特图",None)
-            goods_art_no = pic_data.get("货号",None)
-            print("正在复制 scene_image",scene_image)
-            print("正在复制 model_image",model_image)
+            print("正在复制 pic_data", pic_data)
+            scene_image = pic_data.get("场景图", None)
+            model_image = pic_data.get("模特图", None)
+            goods_art_no = pic_data.get("货号", None)
+            print("正在复制 scene_image", scene_image)
+            print("正在复制 model_image", model_image)
             print("正在复制===================================>")
             # {out_put_dir}/{goods_number}
             # 资料长度,决定是否添加货号后缀
@@ -224,25 +228,28 @@ class DetailBase(object):
             if scene_image:
                 self.copyImage(scene_image, f"{scp_path}/场景图{concat_shuffix}.jpg")
         return True
-    def copyImage(self,src_path,limit_path):
+
+    def copyImage(self, src_path, limit_path):
         try:
-          shutil.copy(src_path, limit_path)
+            shutil.copy(src_path, limit_path)
         except Exception as e:
-            logger.info(f"copyImage 复制模特图/场景图出错:{str(e)}",src_path,limit_path)
-    def concatAigcImage(self,image_path,resize=1600,bg_color=(255,255,255)):
+            logger.info(f"copyImage 复制模特图/场景图出错:{str(e)}", src_path, limit_path)
+
+    def concatAigcImage(self, image_path, resize=1600, bg_color=(255, 255, 255)):
         """拼接模特图场景图"""
         try:
-                mote_img = PictureProcessing(image_path)
-                mote_img = mote_img.resize(value=resize)
-                bg_img = PictureProcessing(
-                    "RGB", (mote_img.width, mote_img.height), bg_color
-                )
-                bg_img = bg_img.paste_img(top_img=mote_img, base="nc", value=(0, 0))
-                print("拼接模特图场景图====>",image_path)
-                return bg_img
+            mote_img = PictureProcessing(image_path)
+            mote_img = mote_img.resize(value=resize)
+            bg_img = PictureProcessing(
+                "RGB", (mote_img.width, mote_img.height), bg_color
+            )
+            bg_img = bg_img.paste_img(top_img=mote_img, base="nc", value=(0, 0))
+            print("拼接模特图场景图====>", image_path)
+            return bg_img
         except Exception as e:
-             logger.info(f"copyImage 拼接模特图/场景图出错:{str(e)}")
-             return
+            logger.info(f"copyImage 拼接模特图/场景图出错:{str(e)}")
+            return
+
     # 移动一张图片到新的文件夹
     def move_one_pic(self, old_path, new_path, new_name):
         image_file = os.listdir(old_path)[0]
@@ -268,9 +275,9 @@ class DetailBase(object):
 
             return [x for x in detailed_images if x]
         except KeyError as e:
-          raise UnicornException(f"缺少详情页资料:[{e}],请检查系统商品信息或excel是否缺少该字段")
+            raise UnicornException(f"缺少详情页资料:[{e}],请检查系统商品信息或excel是否缺少该字段")
         except Exception as e:
-          raise UnicornException(str(e))
+            raise UnicornException(str(e))
 
     # 生成拼接的图片
     def generate_spliced_picture(self):
@@ -286,7 +293,7 @@ class DetailBase(object):
         #     },
         # )
         detail_path = "{out_put_dir}/切片图-{template_name}".format(
-            out_put_dir=self.out_put_dir, goods_no=self.goods_no,template_name=self.template_name
+            out_put_dir=self.out_put_dir, goods_no=self.goods_no, template_name=self.template_name
         )
         if not os.path.exists(detail_path):
             return
@@ -296,7 +303,7 @@ class DetailBase(object):
         # 生成拼接图
         img = self.add_pic(detailed_images)
         join_path = "{out_put_dir}/详情页-{template_name}".format(
-            out_put_dir=self.out_put_dir, goods_no=self.goods_no,template_name=self.template_name
+            out_put_dir=self.out_put_dir, goods_no=self.goods_no, template_name=self.template_name
         )
         # self.create_folder(join_path)
         img.save("{}.jpg".format(join_path), format="JPEG")
@@ -386,7 +393,7 @@ class DetailBase(object):
 
             return text
         except:
-          raise UnicornException(f"缺少货号资料:[{key}],请检查系统商品信息或excel是否缺少该字段")
+            raise UnicornException(f"缺少货号资料:[{key}],请检查系统商品信息或excel是否缺少该字段")
 
     def create_folder(self, path):
         if not os.path.exists(path):
@@ -442,7 +449,7 @@ class DetailBase(object):
         return return_data
 
     def get_overlay_pic_from_dict(
-        self, goods_art_no, color_name, bg_color
+            self, goods_art_no, color_name, bg_color
     ) -> PictureProcessing:
         self.check_state_end()
         # 增加逻辑,获取任意货号下的组合图
@@ -586,6 +593,7 @@ class DetailBase(object):
             # 移动颜色图=====================
             # goods_art_no_f = "{}/{}".format(self.out_put_dir, self.goods_no)
             sorted_list_800 = natsorted(goods_art_no_dict["800x800"], key=lambda x: x.split("(")[1].split(")")[0])
+            print("sorted_list_800", sorted_list_800)
             goods_art_no_f = "{}".format(self.out_put_dir)
             self.create_folder(goods_art_no_f)
             # 放入一张主图
@@ -599,14 +607,14 @@ class DetailBase(object):
 
             # 把其他主图放入作为款号图=====================
             if move_main_pic:
-                for idx,pic_path in enumerate(sorted_list_800):
+                for idx, pic_path in enumerate(sorted_list_800):
                     index = idx + 1
                     try:
-                      split_size = pic_path.split("_")[1].split(".")[0]
+                        split_size = pic_path.split("_")[1].split(".")[0]
                     except:
-                      split_size = ""
-                    suffix_name = "_"+split_size if split_size else ""
-                    print("pic_path=========>",split_size)
+                        split_size = ""
+                    suffix_name = "_" + split_size if split_size else ""
+                    print("pic_path=========>", split_size)
                     e = os.path.splitext(pic_path)[1]
                     shutil.copy(
                         pic_path,
@@ -769,7 +777,7 @@ class DetailBase(object):
                         if settings.getSysConfigs(
                             "basic_configs", "image_sharpening", "1"
                         )
-                        == ""
+                           == ""
                         else settings.getSysConfigs(
                             "basic_configs", "image_sharpening", "1"
                         )

+ 3 - 5
python/detail_template_test.py

@@ -3,18 +3,16 @@ import settings
 
 settings.is_test_plugins = True
 
-
-from custom_plugins.plugins.detail_template.qingyangyizhan.detail_qingyangyizhan2 import (
+from custom_plugins.plugins.detail_template.miniso.detail_miniso1 import (
     DetailPicGet,
 )
 
-
-data = json.load(open("detail_template_test_qingyangyizhan.json", mode="r", encoding="utf-8"))
+data = json.load(open("detail_template_test_miniso.json", mode="r", encoding="utf-8"))
 for goods_no, value in data.items():
     d = DetailPicGet(
         goods_no=goods_no,
         goods_no_value=value,
-        out_put_dir=r"C:\Development\project\python\CameraMachine\python\output\detail_qingyangyizhan2",
+        out_put_dir=r"C:\Development\project\python\CameraMachine\python\output\detail_miniso",
         test=False,
     )
 print("生成成功")

+ 49 - 0
python/detail_template_test_miniso.json

@@ -0,0 +1,49 @@
+{
+  "AC5120913": {
+    "款号": "AC5120913",
+    "货号资料": [
+      {
+        "货号": "hb",
+        "文件夹名称": "hb",
+        "编号": "hb",
+        "颜色名称": "枪色",
+        "模特图": "",
+        "场景图": "",
+        "pics": {
+          "俯视-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(1)_俯视_抠图.png",
+          "俯视-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(1)_俯视_阴影.png",
+          "侧视-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(2)_侧视_抠图.png",
+          "侧视-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(2)_侧视_阴影.png",
+          "后跟-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(3)_后跟_抠图.png",
+          "后跟-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(3)_后跟_阴影.png",
+          "鞋底-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(4)_鞋底_抠图.png",
+          "鞋底-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(4)_鞋底_阴影.png",
+          "内里-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(5)_内里_抠图.png",
+          "内里-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(5)_内里_阴影.png",
+          "组合-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(6)_组合_抠图.png",
+          "组合-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(6)_组合_阴影.png",
+          "组合2-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(7)_组合2_抠图.png",
+          "组合2-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(7)_组合2_阴影.png",
+          "组合3-抠图": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(8)_组合3_抠图.png",
+          "组合3-阴影": "C:/Users/15001/Desktop/miniso/hb/阴影图处理/hb(8)_组合3_阴影.png"
+        },
+        "800x800": [
+          "C:/Users/15001/Desktop/miniso/hb/800x800/hb(1)_1500.jpg",
+          "C:/Users/15001/Desktop/miniso/hb/800x800/hb(2)_1500.jpg",
+          "C:/Users/15001/Desktop/miniso/hb/800x800/hb(3)_1500.jpg",
+          "C:/Users/15001/Desktop/miniso/hb/800x800/hb(4)_1500.jpg",
+          "C:/Users/15001/Desktop/miniso/hb/800x800/hb(5)_1500.jpg"
+        ]
+      }
+    ],
+    "文件夹名称": "hb",
+    "标题一": "蝴蝶结系列\n三丽欧悦享提手双饮保温杯",
+    "毫升": "500ML",
+    "标题二": "316不锈钢内胆\n守护饮水健康",
+    "标题三": "好用藏在细节里",
+    "细节一": "保温能效水平:IV级\n*20°室温下,装入96℃\n以上热水,静止不开启状\n态下6小时后≥44℃",
+    "细节二": "安全锁扣\n密封锁水",
+    "细节三": "PP吸嘴",
+    "细节四": "直饮口"
+  }
+}

+ 2 - 5
python/mcu/BaseClass.py

@@ -16,12 +16,9 @@ class BaseClass:
         # -1连接失败  0未连接 1连接中  2连接成功  3端口占用
         # self.device_status = 2
 
-    def sendSocketMessage(self, code=0, msg="", data=None, device_status=2, msg_type=None):
+    def sendSocketMessage(self, code=0, msg="", data=None, device_status=2):
         t_start = time.time()
-        if msg_type:
-            message_type = msg_type
-        else:
-            message_type = self.msg_type
+        message_type = self.msg_type
         payload = {
             "code": code,
             "msg": msg,

+ 92 - 4
python/mcu/DeviceControl.py

@@ -197,6 +197,32 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
 
         return self.config_manager.CONFIG_METADATA
 
+    def get_dynamic_config_signle(self, name):
+        """
+        获取动态参数配置-单独
+        """
+        data = {"addr": name}
+        print("参数值打印", data)
+        item_value = self.config_manager.get_dynamic_value(data)
+
+        return item_value
+
+    def one_key_down(self):
+        cmd = [0x55, 0x55, 0x69, 0x96, 0x00, 0x03, 0x00, 0xC8, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+               0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
+               0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02]
+        # self.add_send_data_queue(cmd)
+        buf = bytearray(b"")
+        buf.extend(cmd)
+        # 55 55 02 5a 01 a4
+        if self.serial_ins.serial_handle is not None:
+            print("one_key_down buf  {}".format(self.serial_ins.change_hex_to_int(buf)))
+            self.serial_ins.serial_handle.write(buf)
+
     def get_device_info(self):
         if not self.init_state:
             self.sendSocketMessage(code=1, msg="mcu设备未初始化", device_status=4)
@@ -602,11 +628,14 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             else:
                 # print("设备异常数据打印:", data)
                 logger.info(f"115  设备异常数据打印:%{data}")
+                current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
                 msg_data = {
-                    "message": data
+                    "message": data,
+                    "current_time": current_time
                 }
-                self.sendSocketMessage(msg="设备信息打印", data=msg_data, device_status=2,
-                                       msg_type="print_mcu_error_data")
+                self.msg_type = "print_mcu_error_data"
+                self.sendSocketMessage(msg="设备信息打印", data=msg_data, device_status=2)
+                self.msg_type = "mcu"
         except BaseException as e:
             print("117 error {}".format(e))
             logger.info(f"117 error %{e}")
@@ -683,6 +712,24 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             # 重置初始化标记为  从未初始化
             self.is_just_init_time = False
 
+        return
+
+    def set_stepper_disable(self, device_name, flag):
+        if device_name in self.device_name_dict:
+            device_id = self.device_name_dict[device_name]
+            buf = [self.command["set_stepper_disable"],
+                   device_id,
+                   1 if flag else 0,
+                   ]
+            self.add_send_data_queue(buf)
+
+    def to_recheck_focal(self, device_name, is_force=False):
+        # 重新校准对焦圈
+        device_id = self.device_name_dict[device_name]
+        cmd = 2
+        data = [cmd, device_id, 1]
+
+        self.add_send_data_queue(data)
         return True
 
     # 设置其他信息
@@ -1705,10 +1752,51 @@ class DeviceControl(BaseClass, metaclass=SingletonType):
             case "to_deal_device":
                 self.to_deal_device(device_name, value=value, _type=0, times=1)
             case "camera_zoom_motor":
-                self.to_deal_device(device_name="camera_zoom_motor")
+                self.to_device_move(device_name="camera_zoom_motor", value=int(value))
+            case "camera_focal_distance":
+                self.to_device_move(device_name="camera_zoom_motor", value=int(value))
             case _:
                 pass
 
+    async def _do_camera_check(self):
+        """执行一次完整的状态查询检测,返回是否全部停止"""
+        self.send_get_all_info_to_mcu()
+        await asyncio.sleep(0.5)
+        return all(
+            value == 2
+            for value in [
+                self.state_camera_motor,
+                self.state_camera_steering,
+                self.state_turntable_steering,
+                self.state_overturn_steering,
+            ]
+        )
+
+    async def camera_check_mcu_move_is_stop(self, re_check=False):
+        _s = time.time()
+        check_times = 0
+        await self.cleanAllReceiveData()
+        while 1:
+            if self.action_state != 1:
+                return False
+            # 发送获取设备状态消息
+            if await self._do_camera_check():
+                # 首次检测通过,追加一次确认检测,防止读到旧状态误判
+                logger.info("拍照前运动检测首次通过,发起确认检测")
+                if await self._do_camera_check():
+                    logger.info("拍照前运动检测状态[成功],确认通过,耗时%.1f秒", time.time() - _s)
+                    await asyncio.sleep(0.5)
+                    return True
+                else:
+                    logger.warning("拍照前运动检测确认失败,继续等待")
+                    check_times += 1
+            else:
+                check_times += 1
+                if check_times > 5:
+                    logger.info("拍照前运动检测状态[失败],耗时%.1f秒", time.time() - _s)
+                    return False
+            await asyncio.sleep(0.2)
+
     def checkDevice(self):
         print("检查设备是否运行中")
         logger.info("检查设备是否运行中")

+ 40 - 29
python/mcu/ProgramItem.py

@@ -12,20 +12,21 @@ import logging
 
 logger = logging.getLogger(__name__)
 
+
 class ProgramItem(BaseClass):
     # program_sign = Signal(dict)
     # program_refresh_photo_list_sign = Signal()
 
     def __init__(
-        self,
-        websocket_manager,
-        action_data: any,
-        mcu,
-        goods_art_no: str = None,
-        image_index: int = -1,
-        record_id: int = -1,
-        smart_shooter=None,
-        is_get_mcu_state=True,
+            self,
+            websocket_manager,
+            action_data: any,
+            mcu,
+            goods_art_no: str = None,
+            image_index: int = -1,
+            record_id: int = -1,
+            smart_shooter=None,
+            is_get_mcu_state=True,
     ):
         super().__init__(BaseClass)
         # 1 表示等待中,2表示没有等待
@@ -61,6 +62,8 @@ class ProgramItem(BaseClass):
         self.camera_angle = float(self.get_value(action_data, "camera_angle", 0.0))
         self.af_times = self.get_value(action_data, "number_focus", 0)
         self.shoe_overturn = self.get_value(action_data, "shoe_upturn", False)
+        # 焦段距离
+        self.camera_focal_distance = self.get_value(action_data, "camera_focal_distance", 1)
         self.is_photograph = self.get_value(action_data, "take_picture", True)
         self.turntable_position = float(
             self.get_value(action_data, "turntable_position", 0.0)
@@ -188,6 +191,7 @@ class ProgramItem(BaseClass):
         print("\033[1;31m执行结束\033[0m", self.mcu.action_state)
         # await asyncio.sleep(0.1)
         # self.mcu.to_get_mcu_base_info()
+
     async def _do_camera_check(self):
         """执行一次完整的状态查询检测,返回是否全部停止"""
         self.mcu.send_get_all_info_to_mcu()
@@ -227,6 +231,7 @@ class ProgramItem(BaseClass):
                     logger.info("拍照前运动检测状态[失败],耗时%.1f秒", time.time() - _s)
                     return False
             await asyncio.sleep(0.2)
+
     async def run(self, total_len=5, *args):
         if total_len == 1:
             self.mode_type = "其他配置"
@@ -277,6 +282,8 @@ class ProgramItem(BaseClass):
                     device_name="turntable_steering", value=self.turntable_angle
                 )
                 time.sleep(0.1)
+            if self.camera_focal_distance is not None:
+                self.mcu.to_device_move(device_name="camera_zoom_motor", value=self.camera_focal_distance)
             loop = asyncio.get_event_loop()
             # self.mcu.send_all_cmd()
             loop.create_task(self.mcu.send_all_cmd())
@@ -287,7 +294,7 @@ class ProgramItem(BaseClass):
         #     return False
         start_time = time.time()
         current_time = time.time()
-        self.mcu.is_get_mcu_state = self.is_get_mcu_state 
+        self.mcu.is_get_mcu_state = self.is_get_mcu_state
         # ============连接MCU 处理步进电机与舵机等
         if self.is_move_device:
             if settings.IS_MCU:
@@ -307,8 +314,11 @@ class ProgramItem(BaseClass):
                 if self.shoe_overturn:
                     self.mcu.to_deal_device(device_name="overturn_steering")
                     await asyncio.sleep(0.001)
+                if self.camera_focal_distance is not None:
+                    # 移动距离
+                    self.mcu.to_device_move(device_name="camera_zoom_motor", value=self.camera_focal_distance)
                 if self.camera_height is not None:
-                    if (current_time - self.last_move_time)>110:
+                    if (current_time - self.last_move_time) > 110:
                         if self.camera_height == 0:
                             self.mcu.to_device_move(
                                 device_name="camera_high_motor", value=1
@@ -319,10 +329,10 @@ class ProgramItem(BaseClass):
                             )
                         else:
                             self.mcu.to_device_move(
-                                device_name="camera_high_motor", value=self.camera_height-1
+                                device_name="camera_high_motor", value=self.camera_height - 1
                             )
                         await asyncio.sleep(0.01)
-                        logger.info("设备延迟执行===>,%s",time.time())
+                        logger.info("设备延迟执行===>,%s", time.time())
                     self.mcu.to_device_move(
                         device_name="camera_high_motor", value=self.camera_height
                     )
@@ -333,17 +343,17 @@ class ProgramItem(BaseClass):
                         self.mcu.to_device_move(
                             device_name="camera_steering", value=-39.9
                         )
-                        logger.info("转盘位置首次运动===>,%s",-39.9)
+                        logger.info("转盘位置首次运动===>,%s", -39.9)
                     elif self.turntable_position == 40:
                         self.mcu.to_device_move(
                             device_name="camera_steering", value=39.9
                         )
-                        logger.info("转盘位置首次运动===>,%s",39.9)
+                        logger.info("转盘位置首次运动===>,%s", 39.9)
                     else:
                         self.mcu.to_device_move(
-                            device_name="camera_steering", value=self.camera_angle-0.1
+                            device_name="camera_steering", value=self.camera_angle - 0.1
                         )
-                        logger.info("转盘位置首次运动===>,%s",self.camera_angle-0.1)
+                        logger.info("转盘位置首次运动===>,%s", self.camera_angle - 0.1)
                     await asyncio.sleep(0.01)
                     self.mcu.to_device_move(
                         device_name="camera_steering", value=self.camera_angle
@@ -353,25 +363,25 @@ class ProgramItem(BaseClass):
                 if self.turntable_position is not None:
                     if self.turntable_position == 0:
                         self.mcu.to_device_move(
-                                device_name="turntable_position_motor", value=1
-                            )
-                        logger.info("转盘位置首次运动===>,%s",1)
+                            device_name="turntable_position_motor", value=1
+                        )
+                        logger.info("转盘位置首次运动===>,%s", 1)
                     elif self.turntable_position == 800:
                         self.mcu.to_device_move(
                             device_name="turntable_position_motor", value=799
                         )
-                        logger.info("转盘位置首次运动===>,%s",799)
+                        logger.info("转盘位置首次运动===>,%s", 799)
                     else:
                         self.mcu.to_device_move(
-                            device_name="turntable_position_motor", value=self.turntable_position-1
+                            device_name="turntable_position_motor", value=self.turntable_position - 1
                         )
-                        logger.info("转盘位置首次运动===>,%s",self.turntable_position-1)
+                        logger.info("转盘位置首次运动===>,%s", self.turntable_position - 1)
                     await asyncio.sleep(0.01)
                     self.mcu.to_device_move(
                         device_name="turntable_position_motor",
                         value=self.turntable_position,
                     )
-                    logger.info("转盘位置2次运动===>,%s",self.turntable_position)
+                    logger.info("转盘位置2次运动===>,%s", self.turntable_position)
                     await asyncio.sleep(0.01)
 
                 if self.turntable_angle is not None:
@@ -417,8 +427,8 @@ class ProgramItem(BaseClass):
                     "iso_config",
                     None,
                 )
-                print(" camera_configs p_item",camera_configs)
-                CameraKey = camera_configs[self.point_name].get("CameraKey",None)
+                print(" camera_configs p_item", camera_configs)
+                CameraKey = camera_configs[self.point_name].get("CameraKey", None)
                 print("smart shooter CameraShooter", record_id, goods_art_no, CameraKey)
                 await self.smart_shooter.CameraShooter(
                     msg_type="run_mcu",
@@ -440,7 +450,7 @@ class ProgramItem(BaseClass):
             await asyncio.sleep(self.after_delay_time)
         return True
 
-    async def RunSmartShooter(self, goods_art_no,record_id):
+    async def RunSmartShooter(self, goods_art_no, record_id):
         await asyncio.gather(
             self.smart_shooter.CameraShooter(
                 msg_type="run_mcu",
@@ -448,6 +458,7 @@ class ProgramItem(BaseClass):
                 id=record_id,
             ),
         )
+
     def digicam_take_picture(self):
         self.mcu.to_deal_device(device_name="buzzer", times=1)
         # 用于临时拍照计数
@@ -464,8 +475,8 @@ class ProgramItem(BaseClass):
         4、重新触发进行拍照,并更新最近拍照时间
         """
         if (
-            settings.getSysConfigs("other_configs", "running_mode", "普通模式")
-            == "普通模式"
+                settings.getSysConfigs("other_configs", "running_mode", "普通模式")
+                == "普通模式"
         ):
             return
 

+ 179 - 171
python/service/base_deal.py

@@ -6,7 +6,7 @@ from threading import Lock
 from middleware import UnicornException
 import settings
 from collections import defaultdict
-from .remove_bg_ali import RemoveBgALi, Picture
+from .remove_bg_ali import RemoveBgALi, Picture, RemoveUltraBackground
 from .deal_cutout import DealCutout
 
 import time
@@ -24,7 +24,10 @@ import copy, asyncio
 from settings import sendSocketMessage
 from utils.common import message_queue
 from logger import logger
-def sendAsyncMessage(msg="", goods_arts=[], status="",progress={}):
+from .online_request.ding_robot import sendDingMsg
+
+
+def sendAsyncMessage(msg="", goods_arts=[], status="", progress={}):
     """异步发送消息"""
     data = {
         "code": 0,
@@ -34,15 +37,15 @@ def sendAsyncMessage(msg="", goods_arts=[], status="",progress={}):
             "status": status,
             "goods_art_nos": goods_arts,
         },
-        "progress":{
-                "msg_type":"segment_progress",
-                "name":"抠图",
-                "goods_art_no":progress.get("goods_art_no",""),
-                "status":progress.get("status"),
-                "current":progress.get("current",0),
-                "total":progress.get("total",0),
-                "error":progress.get("error",0)
-            },
+        "progress": {
+            "msg_type": "segment_progress",
+            "name": "抠图",
+            "goods_art_no": progress.get("goods_art_no", ""),
+            "status": progress.get("status"),
+            "current": progress.get("current", 0),
+            "total": progress.get("total", 0),
+            "error": progress.get("error", 0)
+        },
         "msg_type": "segment_progress",
     }
     message_queue.put_nowait(data)
@@ -65,14 +68,14 @@ class BaseDealImage(object):
         pass
 
     def run_main(
-        self,
-        all_goods_art_no_folder_data,
-        callback_func=None,
-        cutout_mode=None,
-        resize_image_view=None,
-        windows=None,
-        logo_path=None,
-        image_order_list=None,
+            self,
+            all_goods_art_no_folder_data,
+            callback_func=None,
+            cutout_mode=None,
+            resize_image_view=None,
+            windows=None,
+            logo_path=None,
+            image_order_list=None,
     ):
         """
         执行主流程处理
@@ -87,7 +90,7 @@ class BaseDealImage(object):
             }
         """
         from logger import logger
-        
+
         # 对所有缺失已抠图的进行抠图处理
         self.run_cutout_image(
             all_goods_art_no_folder_data=all_goods_art_no_folder_data,
@@ -99,7 +102,7 @@ class BaseDealImage(object):
         successful_num = 0
         successful_folders = []
         failed_folders = []
-        
+
         for goods_art_no_folder_data in all_goods_art_no_folder_data:
             if goods_art_no_folder_data["label"] != "待处理":
                 continue
@@ -108,9 +111,9 @@ class BaseDealImage(object):
                     break
             folder_name = goods_art_no_folder_data["folder_name"]
             callback_func("开始处理文件夹==========  {} ".format(folder_name))
-            
+
             flag = None
-            if settings.IS_TEST:
+            try:
                 flag = self.shoes_run_one_folder_to_deal(
                     goods_art_no_folder_data=goods_art_no_folder_data,
                     resize_image_view=resize_image_view,
@@ -119,27 +122,17 @@ class BaseDealImage(object):
                     callback_func=callback_func,
                     windows=windows,
                 )
-            else:
-                try:
-                    flag = self.shoes_run_one_folder_to_deal(
-                        goods_art_no_folder_data=goods_art_no_folder_data,
-                        resize_image_view=resize_image_view,
-                        logo_path=logo_path,
-                        image_order_list=image_order_list,
-                        callback_func=callback_func,
-                        windows=windows,
-                    )
-                except BaseException as e:
-                    error_num += 1
-                    import traceback
-                    traceback.print_exc()
-                    logger.error(f"货号 {folder_name} 处理异常: {e}")
-                    callback_func(
-                        "货号:{} 图片生成处理异常,原因:{}".format(folder_name, e)
-                    )
-                    failed_folders.append(goods_art_no_folder_data)
-                    continue
-            
+            except BaseException as e:
+                error_num += 1
+                import traceback
+                traceback.print_exc()
+                logger.error(f"货号 {folder_name} 处理异常: {e}")
+                callback_func(
+                    "货号:{} 图片生成处理异常,原因:{}".format(folder_name, e)
+                )
+                failed_folders.append(goods_art_no_folder_data)
+                continue
+
             # 判断处理结果
             if flag is None:
                 callback_func("货号:{} 数据异常".format(folder_name))
@@ -154,9 +147,9 @@ class BaseDealImage(object):
                     error_num += 1
                     callback_func("货号:{} 图片生成处理失败".format(folder_name))
                     failed_folders.append(goods_art_no_folder_data)
-        
+
         callback_func("处理成功:{}个,失败:{}个".format(successful_num, error_num))
-        
+
         # 返回详细的处理结果
         result = {
             'success': error_num == 0,
@@ -165,15 +158,15 @@ class BaseDealImage(object):
             'successful_num': successful_num,
             'error_num': error_num
         }
-        
+
         logger.info(f"[主流程完成] 成功: {successful_num}, 失败: {error_num}")
         if failed_folders:
             logger.warning(f"[主流程完成] 失败货号列表: {[f['folder_name'] for f in failed_folders]}")
-        
+
         return result
 
     def checkImageAmount(
-        self, image_dir: str, amount: int, todo_goods_art_no_folder_name_list=None
+            self, image_dir: str, amount: int, todo_goods_art_no_folder_name_list=None
     ) -> dict:
         result = {"code": 0, "msg": "", "data": {}}
         for goods_art_no_folder in self.list_dir(image_dir):
@@ -189,7 +182,7 @@ class BaseDealImage(object):
             if "无法" in goods_art_no_folder:
                 continue
             if "原始图" not in self.list_dir(
-                "{}/{}".format(image_dir, goods_art_no_folder)
+                    "{}/{}".format(image_dir, goods_art_no_folder)
             ):
                 result["data"][goods_art_no_folder] = "文件夹下,没有 原始图 文件夹\n"
                 continue
@@ -218,7 +211,7 @@ class BaseDealImage(object):
         return result
 
     def check_folders_image_amount(
-        self, all_goods_art_no_folder_data, image_order_list
+            self, all_goods_art_no_folder_data, image_order_list
     ):
         print("*****************check_folders_image_amount************************")
         amount = len(image_order_list)
@@ -268,7 +261,7 @@ class BaseDealImage(object):
         imageOrderList = (
             image_order.replace(",", ",").replace(" ", "").replace("图", "").split(",")
         )
-        print("imageOrderList",imageOrderList)
+        print("imageOrderList", imageOrderList)
         if len(set(imageOrderList)) != len(imageOrderList):
             return {"code": 1, "msg": "图片位置与顺序重复,请检查您的输入"}
 
@@ -307,7 +300,7 @@ class BaseDealImage(object):
                 "组合26",
             ]
             if val not in image_orders:
-                print("val",val)
+                print("val", val)
                 image_orders_str = ','.join(map(str, image_orders))
                 return {
                     "code": 1,
@@ -320,13 +313,13 @@ class BaseDealImage(object):
         return {"code": 0, "msg": "sucess", "imageOrderList": imageOrderList}
 
     def shoes_run_one_folder_to_deal(
-        self,
-        goods_art_no_folder_data,
-        image_order_list: list,
-        resize_image_view: str,
-        logo_path="",
-        windows=None,
-        callback_func=None,
+            self,
+            goods_art_no_folder_data,
+            image_order_list: list,
+            resize_image_view: str,
+            logo_path="",
+            windows=None,
+            callback_func=None,
     ):
         """
         操作步骤:
@@ -396,11 +389,11 @@ class BaseDealImage(object):
 
         # 删除目录再新建
         try:
-          if os.path.exists("{}/阴影图处理".format(folder_path)):
-            shutil.rmtree("{}/阴影图处理".format(folder_path),onerror=settings.handle_remove_readonly)
+            if os.path.exists("{}/阴影图处理".format(folder_path)):
+                shutil.rmtree("{}/阴影图处理".format(folder_path), onerror=settings.handle_remove_readonly)
         except Exception as e:
-          print('An exception occurred')
-          logger.info(f"base deal 抠图前目录删除出现问题:{str(e)}")
+            print('An exception occurred')
+            logger.info(f"base deal 抠图前目录删除出现问题:{str(e)}")
 
         self.crate_all_folders(folder_path)
         print(
@@ -439,7 +432,7 @@ class BaseDealImage(object):
                     is_image_deal_mode = 0
                     print("开始识别左右脚=========>")
                     if OnePicDeal(self.token).check_shoe_is_right(
-                        image_path=original_move_bg_image_path
+                            image_path=original_move_bg_image_path
                     ):
                         is_image_deal_mode = 1  # 1表示要镜像,0表示不做镜像
             print(
@@ -479,14 +472,14 @@ class BaseDealImage(object):
                 resize_mode = 2
 
             if (
-                settings.getSysConfigs("other_configs", "product_type", "鞋类")
-                == "皮具"
+                    settings.getSysConfigs("other_configs", "product_type", "鞋类")
+                    == "皮具"
             ):
                 max_box = (1000, 1200)
             out_pic_size = (
                 [1600]
                 if settings.getSysConfigs("basic_configs", "main_image_size", [1600])
-                == ""
+                   == ""
                 else settings.getSysConfigs("basic_configs", "main_image_size", [1600])
             )  # 主图大小
             if resize_mode == 2:
@@ -502,19 +495,19 @@ class BaseDealImage(object):
             curve_mask = True if "俯视" in image_order_list else False
             try:
                 if not generate_pic.run(
-                    image_path=original_image_path,
-                    cut_image_path=original_move_bg_image_path,
-                    out_path=out_path,
-                    image_deal_mode=is_image_deal_mode,
-                    # resize_mode=resize_mode,
-                    resize_mode=1,#将这里得缩放模式改为强制不缩放 2025-10-22
-                    out_pic_size=out_pic_size,
-                    is_logo=True if i_n == 1 else False,
-                    out_process_path_1=out_process_path_1,
-                    out_process_path_2=out_process_path_2,
-                    max_box=max_box,
-                    logo_path=logo_path,
-                    curve_mask=curve_mask,
+                        image_path=original_image_path,
+                        cut_image_path=original_move_bg_image_path,
+                        out_path=out_path,
+                        image_deal_mode=is_image_deal_mode,
+                        # resize_mode=resize_mode,
+                        resize_mode=1,  # 将这里得缩放模式改为强制不缩放 2025-10-22
+                        out_pic_size=out_pic_size,
+                        is_logo=True if i_n == 1 else False,
+                        out_process_path_1=out_process_path_1,
+                        out_process_path_2=out_process_path_2,
+                        max_box=max_box,
+                        logo_path=logo_path,
+                        curve_mask=curve_mask,
                 ):
                     print("**********222222222222222222222222222********************")
                     is_successful = False
@@ -575,12 +568,23 @@ class BaseDealImage(object):
             self.check_path(path)
 
     def run_cutout_image(
-        self,
-        all_goods_art_no_folder_data,
-        callback_func=None,
-        cutout_mode=1,
-        windows=None,
+            self,
+            all_goods_art_no_folder_data,
+            callback_func=None,
+            cutout_mode=1,
+            windows=None,
     ):
+        cutout_mode_str = "普通抠图"
+        match cutout_mode:
+            case "1":
+                cutout_mode_str = "普通抠图"
+            case "2":
+                cutout_mode_str = "精细化抠图"
+            case "3":
+                cutout_mode_str = "旗舰AI抠图"
+            case "旗舰AI抠图-精细":
+                cutout_mode_str = "4"
+        logger.info(f"当前抠图模式:{cutout_mode_str}")
         """
         处理所有的抠图
         """
@@ -592,12 +596,12 @@ class BaseDealImage(object):
         total_progress = len(all_goods_art_no_folder_data)
         finish_progress = 0
         error_progress = 0
-        progress = {"status":"正在处理",
-                "current":finish_progress,
-                "total":total_progress,
-                "error":error_progress}
+        progress = {"status": "正在处理",
+                    "current": finish_progress,
+                    "total": total_progress,
+                    "error": error_progress}
         sendAsyncMessage(
-            msg="开始处理抠图", goods_arts=goods_art_nos, status="开始处理",progress=progress
+            msg="开始处理抠图", goods_arts=goods_art_nos, status="开始处理", progress=progress
         )
         error_goods_art_no_folder = []
         for goods_art_no_folder_data in all_goods_art_no_folder_data:
@@ -609,12 +613,12 @@ class BaseDealImage(object):
             images = [x for x in self.list_dir("{}/原始图".format(folder_path))]
             cutImageList = []
             goods_art_floder_name = goods_art_no_folder_data["folder_name"]
-            progress = {"status":"正在处理",
-                "current":finish_progress,
-                "total":total_progress,
-                "error":error_progress,
-                "goods_art_no":goods_art_floder_name
-                }
+            progress = {"status": "正在处理",
+                        "current": finish_progress,
+                        "total": total_progress,
+                        "error": error_progress,
+                        "goods_art_no": goods_art_floder_name
+                        }
             sendAsyncMessage(
                 msg="正在抠图",
                 goods_arts=[goods_art_floder_name],
@@ -637,6 +641,7 @@ class BaseDealImage(object):
                     if not os.path.exists(original_move_bg_image_path):
                         # 没有抠图文件,进行抠图生成
                         callback_func("正在抠图 货号:{}".format(file_name))
+                        print("cutout_mode=============>", cutout_mode)
                         if cutout_mode == "2":
                             cutImageList.append(
                                 {
@@ -651,50 +656,59 @@ class BaseDealImage(object):
                                 }
                             )
                         else:
-                            remove_pic_ins = RemoveBgALi()
-                            if settings.IS_TEST:
-                                im = remove_pic_ins.get_image_cut(
-                                    file_path=original_image_path,
-                                    out_file_path=original_move_bg_image_path,
-                                )
-                            else:
-                                try:
+                            try:
+                                if cutout_mode == "1":
+                                    remove_pic_ins = RemoveBgALi()
                                     im = remove_pic_ins.get_image_cut(
                                         file_path=original_image_path,
                                         out_file_path=original_move_bg_image_path,
                                     )
-                                except FunctionTimedOut as f:
-                                    callback_func(
-                                        "货号图{} 抠图处理超时~".format(file_name)
+                                if cutout_mode == "3":
+                                    remove_pic_ins = RemoveUltraBackground()
+                                    im = remove_pic_ins.ultra_segment_fast(
+                                        file_path=original_image_path,
+                                        out_file_path=original_move_bg_image_path,
                                     )
-                                    error_goods_art_no_folder.append(folder_path)
-                                    im = None
-                                except BaseException as e:
-                                    callback_func(
-                                        "货号图{} 抠图处理失败,原因{}".format(
-                                            file_name, e
-                                        )
+                                if cutout_mode == "4":
+                                    remove_pic_ins = RemoveUltraBackground()
+                                    im = remove_pic_ins.run_ultra_segment(
+                                        file_path=original_image_path,
+                                        out_file_path=original_move_bg_image_path,
                                     )
-                                    error_goods_art_no_folder.append(folder_path)
-                                    im = None
-
+                            except FunctionTimedOut as f:
+                                callback_func(
+                                    "货号图{} 抠图处理超时~".format(file_name)
+                                )
+                                sendDingMsg(f"抠图服务:{cutout_mode_str}", f"货号图{file_name} 抠图处理超时~")
+                                error_goods_art_no_folder.append(folder_path)
+                                im = None
+                            except BaseException as e:
+                                callback_func(
+                                    "货号图{} 抠图处理失败,原因{}".format(
+                                        file_name, e
+                                    )
+                                )
+                                sendDingMsg(f"抠图服务:{cutout_mode_str}", f"货号图{file_name} 抠图处理失败,原因:{e}")
+                                error_goods_art_no_folder.append(folder_path)
+                                im = None
                             if not im:
                                 callback_func(
                                     "货号图{} 抠图处理失败~".format(file_name)
                                 )
+                                sendDingMsg(f"抠图服务:{cutout_mode_str}", f"货号图{file_name} 抠图处理失败")
                                 error_goods_art_no_folder.append(folder_path)
                                 continue
                             else:
                                 callback_func("货号图{} 抠图完成~".format(file_name))
             progress = {
-                "status":"正在处理",
-                "current":finish_progress,
-                "total":total_progress,
-                "error":error_progress,
-                "goods_art_no":goods_art_floder_name
-                }
+                "status": "正在处理",
+                "current": finish_progress,
+                "total": total_progress,
+                "error": error_progress,
+                "goods_art_no": goods_art_floder_name
+            }
             if goods_art_floder_name not in error_goods_art_no_folder:
-                finish_progress+=1
+                finish_progress += 1
                 sendAsyncMessage(
                     msg="正在处理",
                     goods_arts=[goods_art_floder_name],
@@ -721,11 +735,11 @@ class BaseDealImage(object):
                         break
         error_progress = len(error_goods_art_no_folder)
         progress = {
-                "status":"处理完成",
-                "current":finish_progress,
-                "total":total_progress,
-                "error":error_progress
-                }
+            "status": "处理完成",
+            "current": finish_progress,
+            "total": total_progress,
+            "error": error_progress
+        }
         if error_goods_art_no_folder:
             print("以下货号抠图失败~\n {}".format(error_goods_art_no_folder))
             callback_func("以下货号抠图失败~\n {}".format(error_goods_art_no_folder))
@@ -738,12 +752,6 @@ class BaseDealImage(object):
         else:
             pass
         progress["status"] = "处理失败" if error_progress == total_progress else "处理完成"
-        # sendAsyncMessage(
-        #             msg="抠图完成",
-        #             goods_arts=[],
-        #             status="抠图完成",
-        #             progress=progress
-        #         )
 
     def checkCutoutImage(self, image_dir: str, todo_goods_art_no_folder_name_list=None):
         """
@@ -765,7 +773,7 @@ class BaseDealImage(object):
             if "无法" in goods_art_no_folder:
                 continue
             if "原始图" not in self.list_dir(
-                "{}/{}".format(image_dir, goods_art_no_folder)
+                    "{}/{}".format(image_dir, goods_art_no_folder)
             ):
                 error_goods_art_no_folder.append(goods_art_no_folder)
                 continue
@@ -869,15 +877,15 @@ class BaseDealImage(object):
                             print("550 文件夹重名命失败:{}".format(e))
 
     def cutImagePiju(
-        self,
-        image_dir: str,
-        image_order="",
-        is_check_number=True,
-        is_filter=True,
-        resize_image_view="后跟",
-        callback_func=None,
-        event=None,
-        todo_goods_art_no_folder_name_list=None,
+            self,
+            image_dir: str,
+            image_order="",
+            is_check_number=True,
+            is_filter=True,
+            resize_image_view="后跟",
+            callback_func=None,
+            event=None,
+            todo_goods_art_no_folder_name_list=None,
     ):
         """
         1、遍历文件夹,基于生成的结果图看哪些需要进行抠图等处理
@@ -997,13 +1005,13 @@ class BaseDealImage(object):
         return {"code": 0, "msg": "ok"}
 
     def run_one_folder_to_deal(
-        self,
-        goods_art_no_folder,
-        image_dir,
-        image_order,
-        resize_image_view,
-        callback_func=None,
-        logo_path="",
+            self,
+            goods_art_no_folder,
+            image_dir,
+            image_order,
+            resize_image_view,
+            callback_func=None,
+            logo_path="",
     ):
 
         _img_all = self.list_dir("{}/{}/原始图".format(image_dir, goods_art_no_folder))
@@ -1030,7 +1038,7 @@ class BaseDealImage(object):
                 all_original_images.append(new_file_name)
 
         if os.path.exists(
-            "{}/{}/原始图/镜像.txt".format(image_dir, goods_art_no_folder)
+                "{}/{}/原始图/镜像.txt".format(image_dir, goods_art_no_folder)
         ):
             file_mirror_mark = True
         else:
@@ -1112,7 +1120,7 @@ class BaseDealImage(object):
                         goods_class = "鞋"
                         # 如果图片已存在,则需要通过加载图片判断是否为左右脚
                         if OnePicDeal().check_shoe_is_right(
-                            image_path=original_move_bg_image_path
+                                image_path=original_move_bg_image_path
                         ):
                             image_deal_mode = 1  # 1表示要镜像,0表示不做镜像
                             is_image_deal_mode = 1
@@ -1191,24 +1199,24 @@ class BaseDealImage(object):
                     if settings.getSysConfigs(
                         "basic_configs", "main_image_size", [1600]
                     )
-                    == ""
+                       == ""
                     else settings.getSysConfigs(
                         "basic_configs", "main_image_size", [1600]
                     )
                 )  # 主图大小
                 if generate_pic.run(
-                    image_path=original_image_path,
-                    cut_image_path=original_move_bg_image_path,
-                    out_path=out_path,
-                    image_deal_mode=is_image_deal_mode,
-                    image_index=image_index,
-                    out_pic_size=out_pic_size,
-                    is_logo=True if i_n == 1 else False,
-                    out_process_path_1=out_process_path_1,
-                    out_process_path_2=out_process_path_2,
-                    max_box=max_box,
-                    logo_path=logo_path,
-                    curve_mask=curve_mask,
+                        image_path=original_image_path,
+                        cut_image_path=original_move_bg_image_path,
+                        out_path=out_path,
+                        image_deal_mode=is_image_deal_mode,
+                        image_index=image_index,
+                        out_pic_size=out_pic_size,
+                        is_logo=True if i_n == 1 else False,
+                        out_process_path_1=out_process_path_1,
+                        out_process_path_2=out_process_path_2,
+                        max_box=max_box,
+                        logo_path=logo_path,
+                        curve_mask=curve_mask,
                 ):
                     # self.show_progress_detail("货号图{} _{} 已完成800*800图片制作~".format(image_index, file_name))
                     callback_func(
@@ -1248,7 +1256,7 @@ class BaseDealImage(object):
             return None
 
     def get_goods_art_no_info(
-        self, numbers_list=None, goods_art_list=None, headers=None
+            self, numbers_list=None, goods_art_list=None, headers=None
     ):
         # 获取商品基础信息,入参为商品的编号
         url = "{domain}/api/backend/goods_client/goods_query".format(
@@ -1301,7 +1309,7 @@ class BaseDealImage(object):
         return goods_art_no_dict
 
     def get_goods_art_no_info(
-        self, numbers_list=None, goods_art_list=None, headers=None
+            self, numbers_list=None, goods_art_list=None, headers=None
     ):
         # 获取商品基础信息,入参为商品的编号
         url = "{domain}/api/backend/goods_client/goods_query".format(
@@ -1572,7 +1580,7 @@ class BaseDealImage(object):
         shutil.move(old_image_path, original_image_path)
 
     def pixianRemoveImageBg(
-        self, file_path: str, out_file_path: str, callbackek_func=None
+            self, file_path: str, out_file_path: str, callbackek_func=None
     ):
         url = self.dataModeMatchPhoto.get_online_data.uploadImage(local_path=file_path)
 

+ 279 - 125
python/service/grenerate_main_image_test.py

@@ -13,6 +13,9 @@ from .get_mask_by_green import GetMask
 from middleware import UnicornException
 from logger import logger
 from custom_plugins.plugins_mode.pic_deal import PictureProcessing
+from service.remove_bg_ali import Segment
+
+
 def time_it(func):
     @wraps(func)  # 使用wraps来保留原始函数的元数据信息
     def wrapper(*args, **kwargs):
@@ -33,8 +36,10 @@ 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):
+    def get_mask_and_config_v3(self, im_jpg: Image, im_png: Image, curve_mask: bool,
+                               grenerate_main_pic_brightness: int):
         """
         步骤:
         1、尺寸进行对应缩小
@@ -46,7 +51,6 @@ class GeneratePic(object):
         im_jpg = to_resize(im_jpg, width=600)
         im_png = to_resize(im_png, width=600)
 
-
         # =========================两个蒙版叠加,删除上半部分的图
         # 获取透明图的左右点
         result = get_extremes_from_transparent(im_png)
@@ -109,10 +113,9 @@ class GeneratePic(object):
             brightness_value = brightness_check(img_gray=_im_shadow, mask=new_mask)
 
             print("循环识别:{},Midtones:{},Highlight:{},brightness_value:{}".format(xunhuan,
-                                                                                Midtones,
-                                                                                Highlight,
-                                                                                brightness_value))
-                                                                                    
+                                                                                    Midtones,
+                                                                                    Highlight,
+                                                                                    brightness_value))
 
             if brightness_value >= grenerate_main_pic_brightness:
                 # //GRENERATE_MAIN_PIC_BRIGHTNESS 亮度校验
@@ -140,6 +143,148 @@ class GeneratePic(object):
         }
 
         return return_mask, config
+
+    @time_it
+    def fast_remove_black_dots(self, mask_img, max_area=50):
+        """
+        极速去除黑白蒙版中面积 <= max_area 的黑色噪点
+        """
+        # 1. 确保是纯黑白二值图 (0 和 255)
+        # 如果原图有灰度,先做二值化,否则连通域计算会出错
+        if mask_img.mode != 'L':
+            mask_img = mask_img.convert('L')
+        img_array = np.array(mask_img)
+        _, binary = cv2.threshold(img_array, 127, 255, cv2.THRESH_BINARY)
+
+        # 2. 连通域分析 (C++底层,极快)
+        # 注意:OpenCV 默认白色(255)是前景,黑色(0)是背景
+        # 我们要找的是“黑色的噪点”,所以先取反
+        inv_binary = cv2.bitwise_not(binary)
+        num_labels, labels, stats, _ = cv2.connectedComponentsWithStats(inv_binary, connectivity=8)
+
+        # 3. 提取需要去除的黑色区域
+        # stats 包含每个连通域的面积 (cv2.CC_STAT_AREA)
+        # 第 0 个标签是背景(原图的大面积白色),从 1 开始是黑色噪点
+        areas = stats[1:, cv2.CC_STAT_AREA]
+        small_dots_indices = np.where(areas <= max_area)[0] + 1  # 索引要加1,对应回 labels
+
+        # 4. 批量修改原图(向量化操作,无需 for 循环)
+        mask_to_remove = np.isin(labels, small_dots_indices)
+        binary[mask_to_remove] = 255  # 将小噪点涂白
+        return Image.fromarray(binary, mode='L')
+
+    @time_it
+    def get_mask_and_config_v4_online(self, ori_im_jpg: Image, ori_im_png: Image, im_jpg: Image, im_png: Image):
+        print("179------当前计算函数:get_mask_and_config_v4_online")
+        """
+        步骤:
+        1、尺寸进行对应缩小
+        2、查找并设定鞋底阴影蒙版
+        3、自动色阶检查亮度
+        4、输出自动色阶参数、以及放大的尺寸蒙版
+        """
+        # ===================尺寸进行对应缩小(提升处理速度)
+        ori_im_jpg = to_resize(ori_im_jpg, width=1200)
+        ori_im_png = to_resize(ori_im_png, width=1200)
+
+        im_jpg = to_resize(im_jpg, width=600)
+        im_png = to_resize(im_png, width=600)
+        segment = Segment()
+        api_url = f"{settings.DOMAIN}/api/ai_image/segment_shadow/platform_shadow"
+        bg_mask_image_url = segment.get_platform_shadow(ori_im_jpg, api_url=api_url)
+        if bg_mask_image_url:
+            response = requests.get(bg_mask_image_url)
+            pic = response.content
+            bg_mask = Image.open(BytesIO(pic))  # 阿里返回的抠图结果 已转PIL对象
+            bg_mask = bg_mask.convert("L")
+            bg_mask = ImageChops.invert(bg_mask)
+            _, new_box = get_mini_crop_img(img=ori_im_png)
+            bg_mask = bg_mask.crop(new_box)  # 切图
+            bg_mask = bg_mask.resize(im_png.size)
+            bg_mask = self.fast_remove_black_dots(bg_mask, max_area=50)
+            # bg_mask = expand_or_shrink_mask(pil_image=bg_mask, expansion_radius=6, blur_radius=0)
+        else:
+            bg_mask = False
+            bg_mask = Image.new("RGB", im_png.size, (255, 255, 255))
+            bg_mask = bg_mask.convert("L")
+
+        # 透明图转mask 将原图扩边一些,并填充白色
+        shoe_png_mask = transparent_to_mask_pil(im_png, is_invert=False)
+        shoe_png_mask = expand_or_shrink_mask(pil_image=shoe_png_mask, expansion_radius=40, blur_radius=0)
+        shoe_png_mask = ImageChops.invert(shoe_png_mask)
+        # 两个mask 取交集
+        if bg_mask is not False:
+            # new_mask 背景+鞋子+鞋子阴影的mask
+            new_mask = mask_intersection(shoe_png_mask, bg_mask)
+        else:
+            new_mask = shoe_png_mask
+
+        # 黑色表示鞋子+背景
+        # new_mask.show()
+
+        # ====================生成图片(一张图减去背景、减去阴影、减去鞋子,即只有底盘的图片,其他区域为白色)
+        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)
+
+        _im_shadow = copy.copy(im_shadow)
+
+        Midtones = 0.8
+        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 = get_png_brightness(img_gray=_im_shadow, mask=new_mask)
+
+            print("128----循环识别:{},Midtones:{},Highlight:{},brightness_value:{},阀值:{}".format(xunhuan,
+                                                                                                   Midtones,
+                                                                                                   Highlight,
+                                                                                                   brightness_value,
+                                                                                                   settings.GRENERATE_MAIN_PIC_BRIGHTNESS
+                                                                                                   ))
+
+            if brightness_value >= settings.GRENERATE_MAIN_PIC_BRIGHTNESS:
+                break
+
+        im_shadow = cv2_to_pil(_im_shadow)
+        # ========================================================
+        # 计算阴影的亮度,用于确保阴影不要太黑
+        # 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 bg_mask, config
+
     @time_it
     def get_mask_and_config(self, im_jpg: Image, im_png: Image, curve_mask: bool):
         """
@@ -457,104 +602,106 @@ class GeneratePic(object):
         time.sleep(3)
         if output_queue is not None:
             output_queue.put(True)
-    def paste_img(self,image, top_img, base="nw", value=(0, 0), ):
-            """
-            {
-                "command": "paste_img",
-                "im": 需要粘贴的图片
-                "pos": {"plugins_mode": "relative",  # pixel
-                        "base": "center",  # nw,nc,ne,ec ... 各个方向参考点
-                        "value": (100, 100),
-                        "percentage": (0.5, 0.5),
-                        },
-                "margins": (0, 0, 0, 0),  # 上下左右边距
-            }
-            """
-            value = (int(value[0]), int(value[1]))
-            # 处理默认值
-            base = "nw" if not base else base
-            top, down, left, right = 0, 0, 0, 0
-
-            # 基于右边,上下居中
-            if base == "ec" or base == "ce":
-                p_x = int(image.width - (top_img.width + value[0]))
-                p_y = int((image.height - top_img.height) / 2) + value[1]
-
-            # 基于顶部,左右居中
-            if base == "nc" or base == "cn":
-                # 顶部对齐
-                deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
-                    (image.height - top_img.height) / 2
-                )
-                p_x = deviation_x + value[0] + left
-                p_y = value[1]
-
-            # 基于右上角
-            if base == "en" or base == "ne":
-                p_x = int(image.width - (top_img.width + value[0])) + left
-                p_y = value[1]
-
-            # 基于左上角
-            if base == "nw" or base == "wn":
-                deviation_x, deviation_y = 0, 0
-                p_x, p_y = value
-
-            # 基于底部,左右居中
-            if base == "cs" or base == "sc":
-                deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
-                    (image.height - top_img.height) / 2
-                )
 
-                p_y = image.height - (top_img.height + value[1] + down)
-                p_x = deviation_x + value[0] + left
+    def paste_img(self, image, top_img, base="nw", value=(0, 0), ):
+        """
+        {
+            "command": "paste_img",
+            "im": 需要粘贴的图片
+            "pos": {"plugins_mode": "relative",  # pixel
+                    "base": "center",  # nw,nc,ne,ec ... 各个方向参考点
+                    "value": (100, 100),
+                    "percentage": (0.5, 0.5),
+                    },
+            "margins": (0, 0, 0, 0),  # 上下左右边距
+        }
+        """
+        value = (int(value[0]), int(value[1]))
+        # 处理默认值
+        base = "nw" if not base else base
+        top, down, left, right = 0, 0, 0, 0
+
+        # 基于右边,上下居中
+        if base == "ec" or base == "ce":
+            p_x = int(image.width - (top_img.width + value[0]))
+            p_y = int((image.height - top_img.height) / 2) + value[1]
+
+        # 基于顶部,左右居中
+        if base == "nc" or base == "cn":
+            # 顶部对齐
+            deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
+                (image.height - top_img.height) / 2
+            )
+            p_x = deviation_x + value[0] + left
+            p_y = value[1]
+
+        # 基于右上角
+        if base == "en" or base == "ne":
+            p_x = int(image.width - (top_img.width + value[0])) + left
+            p_y = value[1]
+
+        # 基于左上角
+        if base == "nw" or base == "wn":
+            deviation_x, deviation_y = 0, 0
+            p_x, p_y = value
+
+        # 基于底部,左右居中
+        if base == "cs" or base == "sc":
+            deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
+                (image.height - top_img.height) / 2
+            )
 
-            # 上下左右居中
-            if base == "center" or base == "cc":
-                deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
-                    (image.height - top_img.height) / 2
-                )
-                p_x = deviation_x + value[0] + left
-                p_y = deviation_y + value[1] + top
+            p_y = image.height - (top_img.height + value[1] + down)
+            p_x = deviation_x + value[0] + left
 
-            # 基于左下角
-            if base == "sw" or base == "ws":
-                # deviation_x, deviation_y = 0, int((img.height - img_1.height))
-                p_x = value[0] + left
-                p_y = image.height - (top_img.height + value[1] + down)
+        # 上下左右居中
+        if base == "center" or base == "cc":
+            deviation_x, deviation_y = int((image.width - top_img.width) / 2), int(
+                (image.height - top_img.height) / 2
+            )
+            p_x = deviation_x + value[0] + left
+            p_y = deviation_y + value[1] + top
 
-            # 基于左边,上下居中
-            if base == "wc" or base == "cw":
-                p_x = value[0] + left
-                p_y = int((image.height - top_img.height) / 2) + value[1] + top
+        # 基于左下角
+        if base == "sw" or base == "ws":
+            # deviation_x, deviation_y = 0, int((img.height - img_1.height))
+            p_x = value[0] + left
+            p_y = image.height - (top_img.height + value[1] + down)
 
-            # 基于右下角
-            if base == "es" or base == "se":
-                p_x = int(image.width - (top_img.width + value[0])) + left
-                p_y = image.height - (top_img.height + value[1] + down) + top
+        # 基于左边,上下居中
+        if base == "wc" or base == "cw":
+            p_x = value[0] + left
+            p_y = int((image.height - top_img.height) / 2) + value[1] + top
 
-            try:
-                image.paste(top_img, box=(p_x, p_y), mask=top_img)
-            except:
-                image.paste(top_img, box=(p_x, p_y), mask=top_img.convert("RGBA"))
+        # 基于右下角
+        if base == "es" or base == "se":
+            p_x = int(image.width - (top_img.width + value[0])) + left
+            p_y = image.height - (top_img.height + value[1] + down) + top
+
+        try:
+            image.paste(top_img, box=(p_x, p_y), mask=top_img)
+        except:
+            image.paste(top_img, box=(p_x, p_y), mask=top_img.convert("RGBA"))
+
+        return image
 
-            return image
     @time_it
     def run(
-        self,
-        image_path,
-        cut_image_path,
-        out_path,
-        image_deal_mode=0,
-        image_index=99,
-        out_pic_size=1024,
-        is_logo=True,
-        out_process_path_1=None,
-        out_process_path_2=None,
-        resize_mode=None,
-        max_box=None,
-        logo_path="",
-        curve_mask=False,
-        **kwargs,
+            self,
+            image_path,
+            cut_image_path,
+            out_path,
+            image_deal_mode=0,
+            image_index=99,
+            out_pic_size=1024,
+            is_logo=True,
+            out_process_path_1=None,
+            out_process_path_2=None,
+            resize_mode=None,
+            max_box=None,
+            logo_path="",
+            curve_mask=False,
+            **kwargs,
     ):  # im 为cv对象
         """
         image_path:原始图
@@ -583,31 +730,37 @@ class GeneratePic(object):
         rgb_color = settings.hex_to_rgb(color_800image)
         # ==========先进行剪切原图
         _s = time.time()
-        with Image.open(image_path) as orign_im:
-            # 复制图像以便后续操作
-            orign_im = orign_im.copy()
+        orign_im = Image.open(image_path)
         print("242  need_time_1:{}".format(time.time() - _s))
         orign_x, orign_y = orign_im.size
-        with Image.open(cut_image_path) as cut_image:
-            # 复制图像以便后续操作
-            cut_image = cut_image.copy()
-        cut_image, new_box = get_mini_crop_img(img=cut_image)
+        orign_im_cut = Image.open(cut_image_path)  # 原始图的已扣图
+        cut_image, new_box = get_mini_crop_img(img=orign_im_cut)
         im_shadow = orign_im.crop(new_box)  # 切图
         new_x, new_y = im_shadow.size
 
         # ================自动色阶处理
         _s = time.time()
-        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:
+        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
             )
+        elif image_mask_mode == 1:
+            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)
+        elif image_mask_mode == 2:
+            shadow_mask, config = self.get_mask_and_config_v4_online(ori_im_jpg=orign_im,
+                                                                     ori_im_png=orign_im_cut,
+                                                                     im_jpg=im_shadow,
+                                                                     im_png=cut_image)
         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)
+            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)
@@ -635,7 +788,7 @@ class GeneratePic(object):
 
         # ================处理阴影的亮度==================
         average_brightness = config["average_brightness"]
-        if image_mask_mode ==0:
+        if image_mask_mode == 0:
             if config["average_brightness"] < 180:
                 # 调整阴影亮度
                 backdrop_prepped = np.asfarray(
@@ -656,8 +809,8 @@ class GeneratePic(object):
             # 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)
             opacity_params = int(image_mask_opacity * 100)
@@ -679,7 +832,7 @@ class GeneratePic(object):
                 out_image_1 = out_image_1.transpose(Image.FLIP_LEFT_RIGHT)
 
             self.saver.save_image(
-                image=out_image_1, file_path=out_process_path_1, quality=100,dpi=(350, 350), _format="PNG"
+                image=out_image_1, file_path=out_process_path_1, quality=100, dpi=(350, 350), _format="PNG"
             )
             # save_image_by_thread(image=out_image_1, out_path=out_process_path_1)
             # out_image_1.save(out_process_path_1)
@@ -691,7 +844,7 @@ class GeneratePic(object):
                 out_image_2 = out_image_2.transpose(Image.FLIP_LEFT_RIGHT)
 
             self.saver.save_image(
-                image=out_image_2, file_path=out_process_path_2, quality=100,dpi=(350, 350), _format="PNG"
+                image=out_image_2, file_path=out_process_path_2, quality=100, dpi=(350, 350), _format="PNG"
             )
             # save_image_by_thread(image=out_image_2, out_path=out_process_path_2, save_mode="png")
             # out_image_2.save(out_process_path_2)
@@ -717,7 +870,8 @@ class GeneratePic(object):
             offset_x, offset_y = _x - (cut_image.width - _w) / 2, _y - (cut_image.height - _h) / 2,
             # print("中心偏移量:", offset_x, offset_y)
             # 透明底最小矩形
-            scale_rate = self.get_scale(base_by_box=(bg_size[0] - image_margin * 2, bg_size[1] - image_margin * 2), image_size=(_w, _h))
+            scale_rate = self.get_scale(base_by_box=(bg_size[0] - image_margin * 2, bg_size[1] - image_margin * 2),
+                                        image_size=(_w, _h))
             # 计算缩放比例,以及顶点相对位置
             # print("缩放比例:", scale_rate)
             # 偏移量
@@ -751,7 +905,6 @@ class GeneratePic(object):
                         im_shadow = to_resize(_im=im_shadow, high=1400)
                         cut_image = to_resize(_im=cut_image, high=1400)
 
-
         # 创建底层背景
         # 用户可设置的颜色值参数
         # image_bg = Image.new("RGB", bg_size, rgb_color)
@@ -759,10 +912,10 @@ class GeneratePic(object):
         # image_bg = self.paste_img(image=image_bg, top_img=cut_image, base="cc", value=(_offset_x * -1, _offset_y * -1))
         image_bg = PictureProcessing("RGB", bg_size, rgb_color)
         image_bg = image_bg.to_overlay_pic_advance(mode="pixel",
-                                                    top_img=PictureProcessing(im=im_shadow),
-                                                    base="cc",
-                                                    value=(_offset_x * -1, _offset_y * -1),
-                                                    top_png_img=PictureProcessing(im=cut_image),)
+                                                   top_img=PictureProcessing(im=im_shadow),
+                                                   base="cc",
+                                                   value=(_offset_x * -1, _offset_y * -1),
+                                                   top_png_img=PictureProcessing(im=cut_image), )
         image_bg = image_bg.im
         image_bg_x, image_bg_y = image_bg.size
         image_x, image_y = im_shadow.size
@@ -809,7 +962,7 @@ class GeneratePic(object):
             if dot_index != -1:
                 # 拆分文件路径和后缀
                 file_without_suffix = out_path[:dot_index]
-                suffix = out_path[dot_index + 1 :]
+                suffix = out_path[dot_index + 1:]
             else:
                 file_without_suffix = out_path
                 suffix = ""
@@ -818,8 +971,8 @@ class GeneratePic(object):
             image_size_str = str(imageSize)
             new_file_path = f"{file_without_suffix}_{image_size_str}.{suffix}"
             image_bg = image_bg.resize(
-                    (image_size_int, image_size_int), resample=settings.RESIZE_IMAGE_MODE
-                )
+                (image_size_int, image_size_int), resample=settings.RESIZE_IMAGE_MODE
+            )
             if image_size_int < 3000:
                 if out_pci_mode == ".jpg":
                     self.saver.save_image(
@@ -872,11 +1025,12 @@ class GeneratePic(object):
             if output_queue is not None:
                 output_queue.put(True)
         return True
-    def get_scale(self,base_by_box, image_size):
+
+    def get_scale(self, base_by_box, image_size):
         box_width, box_height = int(base_by_box[0]), int(base_by_box[1])
         width, height = image_size[0], image_size[1]
         if box_width / box_height < width / height:
             scale = box_width / width
         else:
             scale = box_height / height
-        return scale
+        return scale

+ 67 - 13
python/service/image_deal_base_func.py

@@ -1,8 +1,32 @@
 import cv2
-import numpy as np
+from io import BytesIO
+import numpy as np, requests
 from PIL import Image, ImageEnhance, ImageFilter, ImageOps, ImageDraw, ImageChops, ImageStat
 import settings
 
+
+def uploadImage(im: Image) -> str:
+    img_byte_io = BytesIO()
+    # 根据图片模式选择保存格式
+    if im.mode == 'RGBA':
+        im.save(img_byte_io, format='PNG')
+    else:
+        im.save(img_byte_io, format='JPEG')
+    img_byte_io.seek(0)  # 重置指针到开头
+
+    post_headers = {"Authorization": settings.USER_TOKEN}
+    url = settings.DOMAIN + "/api/upload"
+
+    # 使用字节流上传
+    resultData = requests.post(
+        url,
+        files={"file": ("image.jpg", img_byte_io, "image/jpeg")},
+        headers=post_headers
+    ).json()
+
+    return resultData["data"]["url"]
+
+
 # 锐化图片
 def sharpen_image(img, factor=1.0):
     # 创建一个ImageEnhance对象
@@ -26,7 +50,7 @@ def to_resize(_im, width=None, high=None) -> Image:
     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)
+    _im = _im.resize((re_x, re_y), resample=settings.RESIZE_IMAGE_MODE)
     return _im
 
 
@@ -60,6 +84,7 @@ def get_mini_crop_img(img):
     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黑白图像(掩膜)进行膨胀或腐蚀操作,以扩大或缩小前景区域。
@@ -97,6 +122,7 @@ def expand_or_shrink_mask(pil_image, expansion_radius=5, iterations=1, blur_radi
 
     return modified_pil_image
 
+
 def expand_mask(mask, expansion_radius=5, blur_radius=0):
     # 对蒙版进行膨胀处理
     mask = mask.filter(ImageFilter.MaxFilter(expansion_radius * 2 + 1))
@@ -124,14 +150,14 @@ def find_lowest_non_transparent_points(cv2_png):
 
 
 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,
+        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格式的原始图
@@ -165,7 +191,7 @@ def draw_shifted_line(
             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
+            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]
@@ -272,6 +298,8 @@ 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坐标
@@ -347,6 +375,8 @@ def get_extremes_from_transparent(img, alpha_threshold=10):
     }
 
     return result
+
+
 def create_polygon_mask_from_points(img, left_point, right_point):
     """
     根据两个点和图片边界创建多边形mask
@@ -400,6 +430,7 @@ def transparent_to_mask_pil(img, threshold=0, is_invert=False):
         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:
     """
@@ -426,10 +457,11 @@ def mask_intersection(mask1: Image.Image, mask2: Image.Image) -> Image.Image:
 
     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)
+    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]
@@ -443,4 +475,26 @@ def brightness_check(img_gray, mask):
     luminance = 0.299 * r[mask] + 0.587 * g[mask] + 0.114 * b[mask]
 
     # 返回平均亮度
-    return float(np.mean(luminance))
+    return float(np.mean(luminance))
+
+
+def get_png_brightness(img_gray, mask):
+    # 计算非透明区域的平均亮度
+    # transparent_im = Image.new('RGB', img.size, (0, 0, 0))
+    img_gray = cv2_to_pil(img_gray)
+    _im = Image.new("RGB", img_gray.size, (0, 0, 0))
+    _im.paste(im=img_gray, mask=mask)
+
+    # img = Image.open(img)
+    # _im = Image.new('RGB', img.size, (0, 0, 0))
+    # _im.paste(img, (0, 0), img)
+    # _im.show()
+    # raise 1
+    img = cv2.cvtColor(np.asarray(_im), cv2.COLOR_RGB2BGR)
+
+    hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
+    H, S, V = cv2.split(hsv)
+    # print(V[0])
+    v = V[V != 0]  # 亮度非零的值
+    average_v = sum(v) / len(v)
+    return average_v

+ 17 - 0
python/service/online_request/ding_robot.py

@@ -0,0 +1,17 @@
+import requests
+
+url = "https://oapi.dingtalk.com/robot/send?access_token=2add07f31eb0245fa45eed86101253363813b5e70c4d55d0aeb0df2f8b421372"
+
+
+def sendDingMsg(error_type, error_message):
+    # 'Content-Type' => 'application/json'
+    headers = {
+        "Content-Type": "application/json"
+    }
+    data = {
+        "text": {
+            "content": f"【拍照机】异常-{error_type}\n{error_message}"
+        },
+        "msgtype": "text"
+    }
+    requests.post(url, json=data, headers=headers)

+ 477 - 15
python/service/remove_bg_ali.py

@@ -16,11 +16,36 @@ import numpy as np
 from func_timeout import func_set_timeout
 from func_timeout import FunctionTimedOut
 from .multi_threaded_image_saving import ImageSaver
+import settings, time, math
+from logger import logger
 
 # 自己的
 AccessKeyId = "LTAI5tCk4p881X8hymj2FYFk"
 AccessKeySecret = "yBYIYzX8CL24r5ZgEx2AgZyDBmFkIK"
 
+
+def uploadImage(im: Image) -> str:
+    img_byte_io = BytesIO()
+    # 根据图片模式选择保存格式
+    if im.mode == 'RGBA':
+        im.save(img_byte_io, format='PNG')
+    else:
+        im.save(img_byte_io, format='JPEG')
+    img_byte_io.seek(0)  # 重置指针到开头
+
+    post_headers = {"Authorization": settings.USER_TOKEN}
+    url = settings.DOMAIN + "/api/upload"
+
+    # 使用字节流上传
+    resultData = requests.post(
+        url,
+        files={"file": ("image.jpg", img_byte_io, "image/jpeg")},
+        headers=post_headers
+    ).json()
+
+    return resultData["data"]["url"]
+
+
 # 惠利玛公司的KEY
 # AccessKeyId = 'LTAI5tCk4p881X8hymj2FYFk'
 # AccessKeySecret = 'rQMgHwciTN4Gusbpt8CM8tflgsxh1V'
@@ -66,12 +91,144 @@ class Segment(object):
             # print(error.code)
             # tips: 可通过error.__dict__查看属性名称
 
+    def get_image_orientation(self, img):
+        # 获取EXIF数据
+        exif = None
+        try:
+            if hasattr(img, 'getexif'):
+                exif_data = img.getexif()
+                if exif_data:
+                    exif = dict(exif_data)
+            elif hasattr(img, '_getexif'):
+                exif = img._getexif()
+        except Exception:
+            print("获取EXIF数据失败", img)
+        if exif is not None:
+            # EXIF标签274对应的是Orientation
+            orientation = exif.get(0x0112)
+            print("orientation", orientation)
+            if orientation == 2:
+                # 水平翻转
+                img = img.transpose(Image.FLIP_LEFT_RIGHT)
+            elif orientation == 3:
+                # 旋转180度
+                img = img.rotate(180, expand=True)
+            elif orientation == 4:
+                # 垂直翻转
+                img = img.transpose(Image.FLIP_TOP_BOTTOM)
+            elif orientation == 5:
+                # 水平翻转后顺时针旋转90度
+                img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_270)
+            elif orientation == 6:
+                # 顺时针旋转90度
+                img = img.transpose(Image.ROTATE_270)
+            elif orientation == 7:
+                # 水平翻转后逆时针旋转90度
+                img = img.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.ROTATE_90)
+            elif orientation == 8:
+                # 逆时针旋转90度
+                img = img.transpose(Image.ROTATE_90)
+        else:
+            print("没有EXIF数据或没有方向信息")
+            orientation = 1
+
+        return img
+
+    def get_bo_bg_goods_ultra_background(self, im, is_shadow=False, api_url=None):
+        imageUrl = uploadImage(im)
+        # imageUrl = imageUrl + "?x-oss-process=image/auto-orient,1"
+        if not settings.USER_TOKEN:
+            print("错误:USER_TOKEN 未配置或为空")
+            return None
+        print("图片上传成功", imageUrl)
+        post_headers = {"Authorization": "Bearer " + settings.USER_TOKEN}
+        data = {
+            "image_url": imageUrl,
+            "is_shadow": is_shadow
+        }
+        response = requests.post(
+            api_url, json=data, headers=post_headers
+        )
+        print(f"响应状态码: {response.status_code}")
+        print(f"响应内容: {response.text[:500]}")  # 只打印前500字符
+
+        resultData = response.json()
+        print("旗舰版抠图请求", resultData)
+        # 安全地获取返回值
+        data = resultData.get("data")
+        if data is None:
+            print("旗舰版抠图返回的data为空")
+            return None
+        cutout_image = data.get("cutout_image")
+        if cutout_image is None:
+            print("旗舰版抠图返回的cutout_image为空")
+            return None
+        return cutout_image
+
+    def get_platform_shadow(self, original_image, api_url=None):
+        image_original_url = uploadImage(original_image)
+        if not settings.USER_TOKEN:
+            print("错误:USER_TOKEN 未配置或为空")
+            return None
+        print("阴影图处理,图片上传成功", image_original_url)
+        post_headers = {"Authorization": "Bearer " + settings.USER_TOKEN}
+        data = {
+            "oraginal_image": image_original_url,
+        }
+        print("阴影处理参数:", data)
+        response = requests.post(
+            api_url, json=data, headers=post_headers
+        )
+        print(f"响应状态码: {response.status_code}")
+        print(f"响应内容: {response.text[:500]}")  # 只打印前500字符
+        resultData = response.json()
+        print("旗舰版阴影处理请求", resultData)
+        # 安全地获取返回值
+        data = resultData.get("data")
+        if data is None:
+            return None
+        oss_url = data.get("oss_url")
+        if oss_url is None:
+            print("旗舰版 阴影处理 返回的 matte_image为空")
+            return None
+        return oss_url
+
+    def get_ultra_shadow(self, original_image, cutout_image, opacity, bright_target, api_url=None):
+        image_original_url = uploadImage(original_image)
+        image_cutcou_url = uploadImage(cutout_image)
+        # imageUrl = imageUrl + "?x-oss-process=image/auto-orient,1"
+        if not settings.USER_TOKEN:
+            print("错误:USER_TOKEN 未配置或为空")
+            return None
+        print("阴影图处理,图片上传成功", image_original_url)
+        post_headers = {"Authorization": "Bearer " + settings.USER_TOKEN}
+        data = {
+            "oraginal_image": image_original_url,
+            "cutout_image": image_cutcou_url,
+            "opacity": opacity,
+            "bright_target": bright_target
+        }
+        print("阴影处理参数:", data)
+        response = requests.post(
+            api_url, json=data, headers=post_headers
+        )
+        print(f"响应状态码: {response.status_code}")
+        print(f"响应内容: {response.text[:500]}")  # 只打印前500字符
+
+        resultData = response.json()
+        print("旗舰版抠图请求", resultData)
+        # 安全地获取返回值
+        data = resultData.get("data")
+        if data is None:
+            print("旗舰版抠图返回的data为空")
+            return None
+        matte_image = data.get("matte_image")
+        if matte_image is None:
+            print("旗舰版 阴影处理 返回的 matte_image为空")
+            return None
+        return matte_image
+
     def get_no_bg_goods(self, file_path=None, _im=None):
-        # file_path = r"D:\MyDocuments\PythonCode\MyPython\red_dragonfly\deal_pics\change_color_2\test\_MG_9061.jpg"
-        # file_path_1 = r"D:\MyDocuments\PythonCode\MyPython\red_dragonfly\deal_pics\change_color_2\test\_MG_9061_resize.png"
-        # if file_path:
-        #     img = open(file_path, 'rb')
-        # if _im:
         # https://blog.csdn.net/weixin_43411585/article/details/107780941
         im = _im
         # im.save(file_path)
@@ -79,13 +236,6 @@ class Segment(object):
         im.save(img, format='JPEG')  # format: PNG or JPEG
         img.seek(0)  # rewind to the start
 
-        # img = img_byte.getvalue()  # im对象转为二进制流
-        # with open(file_path, "wb") as binary_file:
-        #     binary_file.write(im.tobytes())
-
-        # file_path = r"D:\MyDocuments\PythonCode\MyPython\red_dragonfly\deal_pics\change_color_2\test\1.png"
-        # img = open(file_path, 'rb')
-
         request = imageseg_20191230_models.SegmentCommodityAdvanceRequest()
         request.image_urlobject = img
         client = self.create_client()
@@ -145,6 +295,7 @@ class RemoveBgALi(object):
     def __init__(self):
         self.saver = ImageSaver()
         self.segment = Segment()
+
     @func_set_timeout(40)
     def get_image_cut_new(self, file_path, out_file_path=None, original_im=None):
         if original_im:
@@ -185,6 +336,7 @@ class RemoveBgALi(object):
         new_pp4_im.save(byte_io, format='PNG')  # 将图像保存为 PNG 格式到 BytesIO 对象
         byte_io.seek(0)  # 将指针重置到流的开头,以便后续读取
         return byte_io
+
     @func_set_timeout(40)
     def get_image_cut(self, file_path, out_file_path=None, original_im=None):
         if original_im:
@@ -254,7 +406,7 @@ class RemoveBgALi(object):
         if out_file_path:
             self.saver.save_image(
                 image=_img_im, file_path=out_file_path,
-                quality=100,dpi=(350, 350), _format="PNG"
+                quality=100, dpi=(350, 350), _format="PNG"
             )
             # _img_im.save(out_file_path)
         return _img_im
@@ -306,8 +458,318 @@ class RemoveBgALi(object):
             f.write(pic)
 
 
+class RemoveUltraBackground:
+    def __init__(self):
+        self.api_url = '/api/ai_image/segment_shadow/segment_service'
+        self.headers = {
+            'Content-Type': 'application/json',
+            'Accept': 'application/json'
+        }
+        self.saver = ImageSaver()
+        self.segment = Segment()
+        self.r_ali = RemoveBgALi()
+        self.logger = logger
+
+    @func_set_timeout(40)
+    def get_image_cut_ultra(self, out_file_path=None, im_image=None):
+        original_pic = Picture(in_path=None, im=im_image)
+        original_pic.im = self.segment.get_image_orientation(im_image)
+        original_pic.x, original_pic.y = original_pic.im.size
+        if original_pic.im.mode != "RGB":
+            print("抠图图片不能是PNG")
+            return None
+
+        new_pic = copy.copy(original_pic)
+        # after_need_resize = False
+        # if new_pic.x > new_pic.y:
+        #     if new_pic.x > 2000:
+        #         after_need_resize = True
+        #         new_pic.resize(2000)
+        # else:
+        #     if new_pic.y > 2000:
+        #         after_need_resize = True
+        #         new_pic.resize_by_heigh(heigh=2000)
+        print("使用旗舰版抠图")
+        try:
+            api_url = f"{settings.DOMAIN}{self.api_url}"
+            image_url = self.segment.get_bo_bg_goods_ultra_background(im=new_pic.im, api_url=api_url)
+        except BaseException as e:
+            print("旗舰版抠图异常:", e)
+            # 处理失败,需要删除过程图片
+            return None
+        if image_url is None:
+            return None
+        # 字节流转PIL对象
+        print("image_url", image_url)
+        response = requests.get(image_url)
+        pic = response.content
+        _img_im = Image.open(BytesIO(pic))  # 阿里返回的抠图结果 已转PIL对象
+        # # 原图更大,则需要执行CV处理
+        # if after_need_resize:
+        #     # 将抠图结果转成mask
+        #     # _img_im = Image.open(_path)
+        #     # 将抠图结果放大到原始图大小
+        #     _img_im = _img_im.resize(original_pic.im.size)
+        #     new_big_mask = Image.new('RGB', _img_im.size, (0, 0, 0))
+        #     white = Image.new('RGB', _img_im.size, (255, 255, 255))
+        #     new_big_mask.paste(white, mask=_img_im.split()[3])
+        #
+        #     # ---------制作选区缩小的mask
+        #     # mask = cv2.imread(mask_path)
+        #     # mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
+        #     mask = cv2.cvtColor(np.asarray(new_big_mask), cv2.COLOR_BGR2GRAY)  # 将PIL 格式转换为 CV对象
+        #     mask[mask != 255] = 0
+        #     # 黑白反转
+        #     # mask = 255 - mask
+        #     # 选区缩小10
+        #     kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
+        #     erode_im = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
+        #
+        #     # -------再进行抠图处理
+        #     mask = Image.fromarray(cv2.cvtColor(erode_im, cv2.COLOR_GRAY2RGBA))  # CV 对象转 PIL
+        #     transparent_im = Image.new('RGBA', original_pic.im.size, (0, 0, 0, 0))
+        #     # original_pic.im.show()
+        #     # mask.show()
+        #     transparent_im.paste(original_pic.im, (0, 0), mask.convert('L'))
+        #     # transparent_im.show()
+        #     # 上述抠图结果进行拼接
+        #     _img_im.paste(transparent_im, (0, 0), transparent_im)
+        return _img_im
+
+    def add_log(self, text, _type="info"):
+        self.logger.info(
+            f"旗舰抠图,{text}"
+        )
+
+    @func_set_timeout(40)
+    def get_image_cut_ali(self, file_path):
+        original_pic = Picture(in_path=file_path, im=None)
+        original_pic.im = self.segment.get_image_orientation(original_pic.im)
+        original_pic.x, original_pic.y = original_pic.im.size
+
+        original_pic.im = original_pic.im.convert("RGB")
+        image_deal_info = {}
+        image_deal_info["原始图片大小"] = (original_pic.x, original_pic.y)
+
+        # 原始图过小,则不需要使用阿里进行预处理
+        if original_pic.x * original_pic.y < 1000000:
+            cut_image = original_pic.im
+            image_deal_info["抠图扩边后图片大小"] = cut_image.size
+            image_deal_info["二次抠图是否缩放"] = False
+            image_deal_info["抠图扩边后位置"] = (0, 0, original_pic.x, original_pic.y)
+        else:
+            self.add_log("开始预抠图处理")
+            cut_image = self.r_ali.get_image_cut(
+                file_path=None, out_file_path=None, original_im=original_pic.im
+            )
+
+            self.add_log("预抠图处理结束")
+
+            x1, y1, x2, y2 = cut_image.getbbox()
+            image_deal_info["鞋子原始位置"] = (x1, y1, x2, y2)
+            o_w, o_h = cut_image.size
+            image_deal_info["鞋子原始抠图后大小"] = (o_w, o_h)
+            # 扩边处理
+            _w, _h = x2 - x1, y2 - y1
+            out_px = 0.025
+            _w, _h = int(out_px * _w), int(out_px * _h)
+            n_x1, n_y1, n_x2, n_y2 = x1 - _w, y1 - _h, x2 + _w, y2 + _h
+            if n_x1 < 0:
+                n_x1 = 0
+            if n_y1 < 0:
+                n_y1 = 0
+            if n_x2 > o_w:
+                n_x2 = o_w
+            if n_y2 > o_h:
+                n_y2 = o_h
+            image_deal_info["抠图扩边后位置"] = (n_x1, n_y1, n_x2, n_y2)
+            cut_image = original_pic.im.crop(image_deal_info["抠图扩边后位置"])
+
+            image_deal_info["抠图扩边后图片大小"] = cut_image.size
+            x, y = image_deal_info["抠图扩边后图片大小"]
+
+            # 12000000
+            max_size = settings.MAX_PIXIAN_SIZE
+            if x * y > max_size:
+                r = math.sqrt(max_size) / math.sqrt(x * y)
+                r = r * 0.9
+                size = (int(x * r), int(y * r))
+                # print("图片:{} pixian触发二次缩放,原尺寸{}*{},新尺寸:{}".format(self.file_name, x, y, size))
+                self.add_log(
+                    text="图片进行压缩,压缩前:{},压缩后:{}".format(
+                        image_deal_info["抠图扩边后图片大小"], size
+                    )
+                )
+                image_deal_info["抠图扩边后PIL对象"] = copy.deepcopy(cut_image)
+                cut_image = cut_image.resize(size=size, resample=1)
+                # print(cut_image.size)
+                # print(image_deal_info["抠图扩边后PIL对象"].size)
+                image_deal_info["二次抠图是否缩放"] = True
+            else:
+                image_deal_info["二次抠图是否缩放"] = False
+        return cut_image, image_deal_info
+
+    def picture_resize_to_original(self, _img, original_im):
+        """
+
+        Parameters
+        ----------
+        _img 需要还原的PIL对象
+        original_im 原图对象
+
+        Returns
+        -------
+
+        """
+
+        # 将抠图结果转成mask
+        # 将抠图结果放大到原始图大小
+        _img = _img.resize(original_im.size, resample=1)
+        new_big_mask = Image.new("RGB", _img.size, (0, 0, 0))
+        white = Image.new("RGB", _img.size, (255, 255, 255))
+        new_big_mask.paste(white, mask=_img.split()[3])
+
+        # ---------制作选区缩小的mask
+        mask = cv2.cvtColor(
+            np.asarray(new_big_mask), cv2.COLOR_BGR2GRAY
+        )  # 将PIL 格式转换为 CV对象
+        mask[mask != 255] = 0
+        # 黑白反转
+        # mask = 255 - mask
+        # 选区缩小10
+        kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
+        erode_im = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
+
+        # -------再进行抠图处理
+        mask = Image.fromarray(
+            cv2.cvtColor(erode_im, cv2.COLOR_GRAY2RGBA)
+        )  # CV 对象转 PIL
+        transparent_im = Image.new("RGBA", original_im.size, (0, 0, 0, 0))
+        transparent_im.paste(original_im, (0, 0), mask.convert("L"))
+        # 上述抠图结果进行拼接
+        _img.paste(transparent_im, (0, 0), transparent_im)
+
+        return _img
+
+    @func_set_timeout(40)
+    def ultra_segment_fast(self, file_path, out_file_path=None):
+        '''
+        旗舰抠图快速版
+        '''
+        original_pic = Picture(file_path)
+        original_pic.im = self.segment.get_image_orientation(original_pic.im)
+        original_pic.x, original_pic.y = original_pic.im.size
+        if original_pic.im.mode != "RGB":
+            print("抠图图片不能是PNG")
+            return None
+
+        new_pic = copy.copy(original_pic)
+        after_need_resize = False
+        if new_pic.x > new_pic.y:
+            if new_pic.x > 2000:
+                after_need_resize = True
+                new_pic.resize(2000)
+        else:
+            if new_pic.y > 2000:
+                after_need_resize = True
+                new_pic.resize_by_heigh(heigh=2000)
+        print("使用旗舰版抠图")
+        try:
+            api_url = f"{settings.DOMAIN}{self.api_url}"
+            image_url = self.segment.get_bo_bg_goods_ultra_background(im=new_pic.im, api_url=api_url)
+        except BaseException as e:
+            print("旗舰版抠图异常:", e)
+            # 处理失败,需要删除过程图片
+            return None
+        if image_url is None:
+            return None
+        # 字节流转PIL对象
+        print("image_url", image_url)
+        response = requests.get(image_url)
+        pic = response.content
+        _img_im = Image.open(BytesIO(pic))  # 阿里返回的抠图结果 已转PIL对象
+        # 原图更大,则需要执行CV处理
+        if after_need_resize:
+            # 将抠图结果转成mask
+            # _img_im = Image.open(_path)
+            # 将抠图结果放大到原始图大小
+            _img_im = _img_im.resize(original_pic.im.size)
+            new_big_mask = Image.new('RGB', _img_im.size, (0, 0, 0))
+            white = Image.new('RGB', _img_im.size, (255, 255, 255))
+            new_big_mask.paste(white, mask=_img_im.split()[3])
+
+            # ---------制作选区缩小的mask
+            # mask = cv2.imread(mask_path)
+            # mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
+            mask = cv2.cvtColor(np.asarray(new_big_mask), cv2.COLOR_BGR2GRAY)  # 将PIL 格式转换为 CV对象
+            mask[mask != 255] = 0
+            # 黑白反转
+            # mask = 255 - mask
+            # 选区缩小10
+            kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
+            erode_im = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
+
+            # -------再进行抠图处理
+            mask = Image.fromarray(cv2.cvtColor(erode_im, cv2.COLOR_GRAY2RGBA))  # CV 对象转 PIL
+            transparent_im = Image.new('RGBA', original_pic.im.size, (0, 0, 0, 0))
+            # original_pic.im.show()
+            # mask.show()
+            transparent_im.paste(original_pic.im, (0, 0), mask.convert('L'))
+            # transparent_im.show()
+            # 上述抠图结果进行拼接
+            _img_im.paste(transparent_im, (0, 0), transparent_im)
+        # 原图更大,则需要执行CV处理
+        if out_file_path:
+            self.saver.save_image(
+                image=_img_im, file_path=out_file_path,
+                quality=100, dpi=(350, 350), _format="PNG"
+            )
+        return _img_im
+
+    def run_ultra_segment(self, file_path, out_file_path):
+        # 直接调用抠图
+        time.sleep(0.01)
+        # 1、增加获取key,2、key需要加密、3、429报错 重试再来拿一个KEY
+        self.add_log("开始处理")
+        cut_image_ali, image_deal_info = self.get_image_cut_ali(file_path)
+        ultra_cutout_data = self.get_image_cut_ultra(out_file_path, cut_image_ali)
+
+        if ultra_cutout_data is None:
+            return None
+
+        try:
+            if image_deal_info["二次抠图是否缩放"]:
+                # print("图片尺寸还原")
+                self.add_log(text="图片尺寸进行还原")
+                original_im = image_deal_info["抠图扩边后PIL对象"]
+                second_cut_image = self.picture_resize_to_original(
+                    ultra_cutout_data, original_im
+                )
+            else:
+                second_cut_image = ultra_cutout_data
+            # 创建空白图片并粘贴回去
+            _img_im = Image.new(
+                mode="RGBA", size=image_deal_info["原始图片大小"], color=(0, 0, 0, 0)
+            )
+            _img_im.paste(
+                second_cut_image,
+                box=(
+                    image_deal_info["抠图扩边后位置"][0],
+                    image_deal_info["抠图扩边后位置"][1],
+                ),
+            )
+            _img_im.save(out_file_path, dpi=(350, 350))
+            return _img_im
+
+        except BaseException as e:
+            # print(e)
+            text = "{} 图片处理错误,代码49990".format(e)
+            self.add_log(text)
+            return
+
+
 if __name__ == '__main__':
-    r = RemoveBgALi()
-    path = r"D:\MyDocuments\PythonCode\MyPython\red_dragonfly\deal_pics\auto_capture_V2\IPC\test\171112057820408.png"
+    r = RemoveUltraBackground()
+    path = r"C:\Users\15001\Desktop\miniso\hb\原始图\hb(1).JPG"
     out_path = "{}._no_bg-out.png".format(path)
     r.get_image_cut(path, out_file_path=out_path)

+ 10 - 6
python/sockets/connect_manager.py

@@ -1,13 +1,16 @@
 from models import WebSocket
 from logger import logger
-import json, asyncio,time
+import json, asyncio, time
 from starlette.websockets import WebSocketState
+
+
 class ConnectionManager:
     is_connected = False
+
     def __init__(self):
         self.active_connections: list[WebSocket] = []
 
-    def jsonMessage(self, code=0, msg="", data: object = None,msg_type=""):
+    def jsonMessage(self, code=0, msg="", data: object = None, msg_type=""):
         """json字符串数据"""
         jsonData = {"code": code, "msg": msg, "data": data, "msg_type": msg_type}
         return jsonData
@@ -24,15 +27,16 @@ class ConnectionManager:
         self.active_connections.remove(websocket)
         logger.info("socket 连接断开")
 
-    async def send_personal_message(self, message: str, websocket: WebSocket):
+    async def send_personal_message(self, message: str, websocket: WebSocket, is_print=False):
         '''向用户发送消息'''
         # await websocket.send_json(message)
         t_send_start = time.time()
         try:
-            print(f"[T4: {time.time()-t_send_start:.4f}s] 开始 websocket.send_json")
+            print(f"[T4: {time.time() - t_send_start:.4f}s] 开始 websocket.send_json")
             await websocket.send_json(message)
-            await asyncio.sleep(0) 
-            # print(f"[T5: {time.time()-t_send_start:.4f}s] websocket.send_json 返回",message)
+            await asyncio.sleep(0)
+            if is_print:
+                print(f"[T5: {time.time() - t_send_start:.4f}s] websocket.send_json 返回", message)
         except Exception as e:
             logger.info(f"socket 消息发送异常:{str(e)}")
             await asyncio.sleep(0.001)

+ 52 - 7
python/sockets/message_handler.py

@@ -279,6 +279,7 @@ async def handlerSend(
             msg_type = "run_mcu"
             action_info = data.get("action", "执行左脚程序")
             goods_art_no = data.get("goods_art_no", None)
+            goods_art_no = goods_art_no.strip()
             if goods_art_no == None or goods_art_no == "":
                 # 判断货号是否存在
                 data = manager.jsonMessage(
@@ -349,14 +350,15 @@ async def handlerSend(
                 name="run_mcu_single",
             )
         case "handler_take_picture":
+            print("收到单拍指令", 'handler_take_picture')
             if data is None:
                 PointName = "A"
             else:
                 PointName = data.get("point_name", "A")
+            print("收到单拍指令", PointName)
             device_ctrl = DeviceControl(
                 websocket_manager=manager, smart_shooter=smart_shooter
             )
-            print("收到单拍指令", 'handler_take_picture')
             await device_ctrl.controlDevice("laser_position", 0)
             blue_tooth = BlueToothMode(
                 websocket_manager=manager, smart_shooter=smart_shooter
@@ -618,6 +620,7 @@ async def handlerSend(
             # 兼容主图测试
             id = data.get("id", 0)
             goods_art_no = data.get("goods_art_no", "")
+            goods_art_no = goods_art_no.strip()
             if goods_art_no:
                 try:
                     limit_path = "{}/{}".format(settings.OUTPUT_DIR,
@@ -632,6 +635,12 @@ async def handlerSend(
                     )
                     await manager.send_personal_message(data, websocket)
                     return
+            if not await device_ctrl.camera_check_mcu_move_is_stop(re_check=True):
+                logger.warning("拍照前运动检测失败===>,等待0.5秒后再检测一次")
+                await asyncio.sleep(0.5)
+                if not await device_ctrl.camera_check_mcu_move_is_stop(re_check=True):
+                    logger.warning("拍照前运动检测二次失败===>,强制拍摄")
+                    await asyncio.sleep(0.5)
             is_af = True
             loop.create_task(
                 smart_shooter.CameraShooter(
@@ -676,6 +685,7 @@ async def handlerSend(
             msg_type = "segment_progress"
             obj = None
             token = data.get("token", "")
+            settings.USER_TOKEN = token
             token = "Bearer " + token
             uuid = data.get("uuid", "")
             run_main = RunMain(obj, token, uuid)
@@ -740,12 +750,18 @@ async def handlerSend(
                         return
             session.close()
             # try:
-            cutOutMode = (
-                "1"
-                if settings.getSysConfigs("other_configs", "cutout_mode", "普通抠图")
-                   == "普通抠图"
-                else "2"
-            )
+            cutout_mode = settings.getSysConfigs("other_configs", "cutout_mode", "普通抠图")
+            match cutout_mode:
+                case "普通抠图":
+                    cutOutMode = "1"
+                case "精细化抠图":
+                    cutOutMode = "2"
+                case "旗舰AI抠图":
+                    cutOutMode = "3"
+                case "旗舰AI抠图-精细":
+                    cutOutMode = "4"
+                case _:
+                    cutOutMode = "1"
             config_data = {
                 "image_dir": limit_path,
                 "image_order": (
@@ -796,6 +812,25 @@ async def handlerSend(
                 data=dynamic_configs,
             )
             await manager.send_personal_message(data, websocket)
+        case "one_key_down":
+            device_ctrl = DeviceControl(
+                websocket_manager=manager, smart_shooter=smart_shooter
+            )
+            device_ctrl.one_key_down()
+        case "get_dynamic_config_signle":
+            device_ctrl = DeviceControl(
+                websocket_manager=manager, smart_shooter=smart_shooter
+            )
+            name = data.get("name")
+            value = device_ctrl.get_dynamic_config_signle(name)
+            # print("dynamic_configs", dynamic_configs)
+            data = manager.jsonMessage(
+                code=0,
+                msg="成功",
+                msg_type="get_dynamic_config_signle",
+                data={"value": value},
+            )
+            await manager.send_personal_message(data, websocket)
         case "set_dynamic_config":
             # 设置动态参数
             device_ctrl = DeviceControl(
@@ -820,6 +855,16 @@ async def handlerSend(
             device_ctrl.getDeviationInfo()
             await asyncio.sleep(0.1)
             device_ctrl.get_device_info()
+        case "to_recheck_focal":
+            device_ctrl = DeviceControl(
+                websocket_manager=manager, smart_shooter=smart_shooter
+            )
+            device_ctrl.to_recheck_focal("camera_zoom_motor")
+        case "oneclick_release":
+            device_ctrl = DeviceControl(
+                websocket_manager=manager, smart_shooter=smart_shooter
+            )
+            device_ctrl.set_stepper_disable("mcu", False)
         case _:
             data = manager.jsonMessage(code=1, msg="未知消息")
             await manager.send_personal_message(data, websocket)

+ 28 - 22
python/sockets/socket_server.py

@@ -189,19 +189,22 @@ async def send_message(websocket):
             await asyncio.sleep(1)  # 防止死循环报错
 
 
-async def getActionInfo(record_info):
+async def getActionInfo(record_id):
     await asyncio.sleep(0.01)
-    if not record_info:
-        return None
-    action_id = record_info.action_id
-    if not action_id:
+    if not record_id:
         return None
     session = SqlQuery()
+    record_model = CRUD(PhotoRecord)
+    record_data = record_model.read(session, conditions={"id": record_id, "delete_time": None})
+    if not record_data:
+        session.close()
+        return None
     device_model = CRUD(DeviceConfig)
-    model = device_model.read(session, conditions={"id": action_id})
+    model = device_model.read(session, conditions={"id": record_data.action_id})
     if not model:
         session.close()
         return None
+    session.close()
     return model
 
 
@@ -223,25 +226,28 @@ async def MsgCallback(msg):
                 try:
                     if PhotoOrigin != "" and PhotoOrigin not in ["external", "ui"]:
                         goods_art_no, id = PhotoOrigin.split(",")
-                    # 创建任务来处理数据库更新,避免阻塞回调
-                    recordResult = await updateDataRecord(PhotoFilename, id)
-                except Exception as e:
-                    print("拍照更新异常", e)
-                    recordResult = None
-                actionModel = None
-                if recordResult:
-                    actionModel = await getActionInfo(recordResult)
-                data = conn_manager.jsonMessage(
-                    code=0,
-                    msg=f"照片获取成功",
-                    data={
+                        print("PhotoUpdated", goods_art_no, id)
+                        # 创建任务来处理数据库更新,避免阻塞回调
+                        await updateDataRecord(PhotoFilename, id)
+                    actionModel = None
+                    if id:
+                        actionModel = await getActionInfo(id)
+                    print("准备发送message_data", actionModel)
+                    message_data = {
                         "photo_file_name": PhotoFilename,
                         "goods_art_no": goods_art_no,
                         "action_name": actionModel.action_name if actionModel else None,
-                    },
-                    msg_type="smart_shooter_photo_take",
-                )
-                await conn_manager.send_personal_message(data, smart_shooter.websocket)
+                    }
+                    print("smart_shooter_photo_take message_data", message_data)
+                    data = conn_manager.jsonMessage(
+                        code=0,
+                        msg=f"照片获取成功",
+                        data=message_data,
+                        msg_type="smart_shooter_photo_take",
+                    )
+                    await conn_manager.send_personal_message(data, smart_shooter.websocket, is_print=True)
+                except Exception as e:
+                    print("拍照更新异常", e)
         case "LiveviewUpdated":
             CameraLiveviewImage = msg.get("CameraLiveviewImage", None)
             # base64_to_image(CameraLiveviewImage, "liveview.jpg")

+ 3 - 15
python/temp.py

@@ -1,4 +1,4 @@
-import zmq,json
+import zmq, json
 # def __send_tcp_message(socket, msg):
 #     socket.send_string(json.dumps(msg, ensure_ascii=False))
 #     rep = socket.recv()
@@ -24,18 +24,6 @@ import zmq,json
 # json_msg = __send_tcp_message(req_socket,req)
 # cameraInfo = json_msg.get("CameraInfo")
 # print("cameraInfo",json_msg)
-import zmq, json
-import asyncio, settings
-
-# # ... existing code ...
-from mcu.capture.smart_shooter_class import SmartShooter
-
-
-async def main():
-    sm = SmartShooter(None)
-    await sm.GetCameraInfo()
-
-
-if __name__ == "__main__":
-    asyncio.run(main())
+from service.online_request.ding_robot import sendDingMsg
 
+sendDingMsg("抠图异常", "抠图异常!!!")

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor