AnlaAnla преди 3 месеца
родител
ревизия
6bca4e6768

+ 17 - 4
Test/test01.py

@@ -1,6 +1,19 @@
-import json
+from multiprocessing import Process, Queue
 
-with open("../temp/test_return.json", 'r') as f:
-    data = json.load(f)
+q = Queue()
 
-print(data)
+
+def f(q_main, a):
+    q_main.put(a)
+    print(a)
+
+
+if __name__ == '__main__':
+
+    p = Process(target=f, args=(q, 1,))
+    p.start()
+
+    val = q.get(True)
+    print(val)
+
+    p.join()

+ 4 - 5
Test/切割合并.py

@@ -5,7 +5,6 @@ from typing import Dict
 from app.utils.CardDefectAggregator import CardDefectAggregator
 from pathlib import Path
 
-# 假设你的预测器类在这里,我们为了测试会创建一个MockPredictor
 from app.utils.card_inference.fry_bisenetv2_predictor_V04_250819 import FryBisenetV2Predictor
 
 BASE_PATH = Path(__file__).parent.parent.absolute()
@@ -37,7 +36,7 @@ def get_predictor(config_params: dict):
 
 
 def _test_face_big_img():
-    large_image_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg"
+    large_image_path = r"C:\Users\wow38\Downloads\_250813_1616_宝可梦非反光卡正面的面模型\pth_and_images\image_origin\250805_pokemon_0001.jpg"
 
     pokemon_front_face_no_reflect_defect = {
         "pth_path": "Model/pokemon_front_face_no_reflect_defect.pth",
@@ -58,7 +57,7 @@ def _test_face_big_img():
     # --- 执行任务 ---
     # 任务1: 对整个卡片表面进行缺陷检测
     aggregator.process_image(
-        image_path=large_image_path,
+        image=large_image_path,
         output_json_path="output/final_face_defects.json",
         mode='face'
     )
@@ -134,6 +133,6 @@ def _test_split_img(split_mode):
         print(f"分割出的最后一个文件是: {saved_tile_paths[-1]}")
 
 if __name__ == "__main__":
-    # _test_face_big_img()
-    _test_corner_big_img()
+    _test_face_big_img()
+    # _test_corner_big_img()
     # _test_split_img(split_mode='edge')

+ 23 - 0
Test/面积计算.py

@@ -0,0 +1,23 @@
+from app.utils.arean_anylize_draw import DefectProcessor, to_json_serializable
+import json
+
+if __name__ == '__main__':
+    json_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\corner_no_reflect\00006_250805_pokemon_0001_bottom_grid_r0_c5.json"
+
+    with open(json_path, 'r', encoding='utf-8') as f:
+        labelme_data = json.load(f)
+
+    processor = DefectProcessor(pixel_resolution=24.54)
+    result = processor.analyze_from_json(labelme_data)
+
+    print(result)
+    print(type(result))
+    # with open('result.json', 'w', encoding='utf-8') as f:
+    #     f.write(result_json)
+
+
+    # result_json = to_json_serializable(result.to_dict())
+    # print(result_json)
+    # print(json.loads(result_json))
+    # with open('result.json', 'w', encoding='utf-8') as f:
+    #     json.dump(result_json, f, ensure_ascii=False, indent=2)

+ 35 - 4
app/api/card_inference.py

@@ -1,20 +1,26 @@
 from fastapi import APIRouter, File, UploadFile, Depends, HTTPException, Path
+from fastapi.responses import FileResponse, JSONResponse
 from fastapi.concurrency import run_in_threadpool
 from enum import Enum
 from ..core.config import settings
 from app.services.card_service import CardInferenceService, card_service
+from app.services.defect_service import DefectInferenceService, defect_service
+from app.core.logger import logger
 import json
 
+
+
+
 router = APIRouter()
 
 model_names = list(settings.CARD_MODELS_CONFIG.keys())
+defect_names = list(settings.DEFECT_TYPE.keys())
 InferenceType = Enum("InferenceType", {name: name for name in model_names})
+DefectType = Enum("InferenceType", {name: name for name in defect_names})
 
-
-@router.post("/json_result")
-async def card_json_result(
+@router.post("/model_inference", description="内外框类型输入大图, 其他输入小图")
+async def card_model_inference(
         inference_type: InferenceType,
-        # 依赖注入保持不变
         service: CardInferenceService = Depends(lambda: card_service),
         file: UploadFile = File(...)
 ):
@@ -40,6 +46,31 @@ async def card_json_result(
         raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}")
 
 
+@router.post("/defect_inference",
+             description="环形光居中计算, 环形光正反边角缺陷, 同轴光正反表面缺陷")
+async def card_model_inference(
+        defect_type: DefectType,
+        service: DefectInferenceService = Depends(lambda: defect_service),
+        file: UploadFile = File(...),
+        is_draw_image: bool = False,
+):
+    image_bytes = await file.read()
+
+    try:
+        # 3. 传递参数时,使用 .value 获取 Enum 的字符串值
+        json_result = await run_in_threadpool(
+            service.defect_inference,
+            inference_type=defect_type.value,
+            image_bytes=image_bytes,
+            is_draw_image=is_draw_image
+        )
+        return json_result
+    except ValueError as e:
+        raise HTTPException(status_code=400, detail=str(e))
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}")
+
+
 @router.post("/mock_query")
 async def mock_query(img_id: int):
     # json_data = {"img_id": img_id}

+ 31 - 2
app/core/config.py

@@ -1,5 +1,6 @@
 from pathlib import Path
-from typing import Dict
+from typing import Dict, List
+from enum import Enum
 
 
 # 定义一个模型的配置结构
@@ -13,7 +14,11 @@ class CardModelConfig:
 
 class Settings:
     API_prefix: str = "/api/card_inference"
-    BASE_PATH = Path(__file__).parent.parent.absolute()
+    BASE_PATH = Path(__file__).parent.parent.parent.absolute()
+
+    TEMP_WORK_DIR = BASE_PATH / "_temp_work"
+
+    pixel_resolution = 24.54
 
     # 使用一个字典来管理所有卡片检测模型
     # key (如 'outer_box') 将成为 API 路径中的 {inference_type}
@@ -65,6 +70,30 @@ class Settings:
         },
     }
 
+    # 包含, 环形光居中计算, 环形光正反边角缺陷, 同轴光正反表面缺陷
+    # 里面存储需要用到的模型类型
+    DEFECT_TYPE: Dict[str, CardModelConfig] = {
+        "pokemon_card_center": {
+            "inner_box": "pokemon_inner_box",
+            "outer_box": "pokemon_outer_box",
+        },
+        "pokemon_back_corner_defect": {
+            'model_type': "pokemon_back_corner_defect"
+        },
+        "pokemon_front_corner_reflect_defect": {
+            "model_type": "pokemon_front_corner_reflect_defect"
+        },
+        "pokemon_front_corner_no_reflect_defect": {
+            "model_type": "pokemon_front_corner_no_reflect_defect",
+        },
+        "pokemon_front_face_no_reflect_defect": {
+            "model_type": "pokemon_front_face_no_reflect_defect",
+        }
+    }
+
 
 settings = Settings()
 print(settings.BASE_PATH)
+
+# DefectType = Enum("InferenceType", {name: name for name in settings.DEFECT_TYPE})
+# print()

+ 15 - 0
app/core/logger.py

@@ -0,0 +1,15 @@
+import logging
+
+
+logging.basicConfig(
+    level=logging.INFO,  # 生产环境通常设置为 INFO 或 WARNING
+    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+    # 可以添加 handlers,例如 FileHandler 将日志写入文件
+    handlers=[
+        logging.FileHandler("app.log"),
+        logging.StreamHandler()  # 默认会输出到控制台
+    ]
+)
+
+# 获取一个日志器实例,通常以模块名命名
+logger = logging.getLogger(__name__)

+ 3 - 1
app/main.py

@@ -2,13 +2,15 @@ from fastapi import FastAPI
 from contextlib import asynccontextmanager
 from .core.model_loader import load_models, unload_models
 from app.api.card_inference import router as card_inference_router
+import os
 
 from .core.config import settings
 
 
 @asynccontextmanager
-async def lifespan(app: FastAPI):
+async def lifespan(main_app: FastAPI):
     print("--- 应用启动 ---")
+    os.makedirs(settings.TEMP_WORK_DIR, exist_ok=True)
     load_models()
     yield
 

+ 79 - 0
app/services/defect_service.py

@@ -0,0 +1,79 @@
+import cv2
+import numpy as np
+from ..core.model_loader import get_predictor
+from app.utils.CardDefectAggregator import CardDefectAggregator
+from app.utils.arean_anylize_draw import DefectProcessor, DrawingParams
+from app.core.config import settings
+from app.core.logger import logger
+import json
+from typing import Tuple
+
+
+class DefectInferenceService:
+    def defect_inference(self, inference_type: str, image_bytes: bytes,
+                         is_draw_image=False) -> dict:
+        """
+        执行卡片识别推理。
+
+        Args:
+            inference_type: 模型类型 (e.g., 'outer_box').
+            image_bytes: 从API请求中获得的原始图像字节。
+
+        Returns:
+            一个包含推理结果的字典。
+        """
+        if inference_type == "pokemon_front_face_no_reflect_defect":
+            # 1. 获取对应的预测器实例
+            predictor = get_predictor(inference_type)
+
+            # 2. 将字节流解码为OpenCV图像
+            # 将字节数据转换为numpy数组
+            np_arr = np.frombuffer(image_bytes, np.uint8)
+            # 从numpy数组中解码图像
+            img_bgr = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
+
+            if img_bgr is None:
+                raise ValueError("无法解码图像,请确保上传的是有效的图片格式 (JPG, PNG, etc.)")
+
+            # 3. 调用我们新加的 predict_from_image 方法进行推理
+            # result = predictor.predict_from_image(img_bgr)
+
+            # 3. 实例化我们聚合器,传入预测器
+            aggregator = CardDefectAggregator(
+                predictor=predictor,
+                tile_size=512,
+                overlap_ratio=0.1,  # 10% 重叠
+            )
+
+            json_data = aggregator.process_image(
+                image=img_bgr,
+                mode='face'
+            )
+            # merge_json_path = settings.TEMP_WORK_DIR / 'merge.json'
+            # with open(merge_json_path, 'w', encoding='utf-8') as f:
+            #     json.dump(json_data, f, ensure_ascii=False, indent=4)
+            # logger.info(f"合并结束")
+
+            processor = DefectProcessor(pixel_resolution=settings.pixel_resolution)
+            if is_draw_image:
+                drawing_params_with_rect = DrawingParams(draw_min_rect=True)
+                drawn_image_rect, result_rect = processor.analyze_and_draw(img_bgr, json_data,
+                                                                           drawing_params_with_rect)
+                temp_img_path = settings.TEMP_WORK_DIR / 'temp_area_result.jpg'
+                cv2.imwrite(temp_img_path, drawn_image_rect)
+
+                return result_rect
+            else:
+                result = processor.analyze_from_json(json_data)
+
+                area_json_path = settings.TEMP_WORK_DIR / 'area.json'
+                with open(area_json_path, 'w', encoding='utf-8') as f:
+                    json.dump(result, f, ensure_ascii=False, indent=4)
+                logger.info("面积计算结束")
+                return result
+        else:
+            return {}
+
+
+# 创建一个单例服务
+defect_service = DefectInferenceService()

+ 8 - 10
app/utils/CardDefectAggregator.py

@@ -237,24 +237,22 @@ class CardDefectAggregator:
         log_print("成功", f"蒙版合并完成,最终得到 {len(final_merged_defects)} 个独立缺陷。")
         return final_merged_defects
 
-    # =========================================================
-    # ==================== 修改后的主流程 =====================
-    # =========================================================
-    def process_image(self, image_path: str, output_json_path: str=None, mode: str = 'face'):
+    def process_image(self, image: str|np.ndarray, output_json_path: str=None, mode: str = 'face'):
         """
         处理单张大图的完整流程:分块、预测、蒙版合并、保存结果。
 
         Args:
-            image_path (str): 输入大图的路径。
+            image : 输入大图的路径或bgr图片
             output_json_path (str): 输出合并后JSON文件的路径。
             mode (str): 处理模式,'face' (全图) 或 'edge' (仅边缘)。
         """
-        log_print("组开始", f"开始处理图片: {image_path},模式: {mode}")
+        log_print("组开始", f"开始处理图片: {image},模式: {mode}")
 
-        image = fry_cv2_imread(image_path)
-        if image is None:
-            log_print("错误", f"无法读取图片: {image_path}")
-            return
+        if isinstance(image, str):
+            image = fry_cv2_imread(image)
+            if image is None:
+                log_print("错误", f"无法读取图片: {image}")
+                return
 
         # 1. 计算图块位置
         if mode == 'face':

+ 28 - 17
app/utils/arean_anylize_draw.py

@@ -60,7 +60,7 @@ def to_json_serializable(obj):
     if isinstance(obj, (np.floating,)): return float(obj)
     if hasattr(obj, 'to_dict'): return obj.to_dict()
     try:
-        return json.dumps(obj)
+        return json.dumps(obj, indent=2)
     except TypeError:
         return str(obj)
 
@@ -82,7 +82,7 @@ class DefectInfo:
             "actual_area": self.actual_area,
             "width": self.width,
             "height": self.height,
-            "contour": self.contour,
+            "points": self.contour,
             "min_rect": self.min_rect
         }
 
@@ -92,7 +92,7 @@ class AnalysisResult:
     """封装单次分析的所有结果,包括缺陷列表和统计信息"""
     defects: List[DefectInfo] = field(default_factory=list)
     total_defect_count: int = 0
-    total_pixel_area = float = 0.0
+    total_pixel_area: float = 0.0
     total_defect_area: float = 0.0  # 所有缺陷的总面积 (mm^2)
     area_by_label: Dict[str, float] = field(default_factory=lambda: defaultdict(float))
     count_by_label: Dict[str, int] = field(default_factory=lambda: defaultdict(int))
@@ -144,8 +144,9 @@ class DefectVisualizer:
             box = np.intp(cv2.boxPoints(defect.min_rect))
             cv2.drawContours(image, [box], 0, self.params.rect_color, self.params.rect_thickness)
         info_text = [
-            f"L: {defect.label}", f"A: {defect.actual_area:.3f} mm2",
-            f"W: {defect.width:.3f} mm", f"H: {defect.height:.3f} mm"
+            # f"L: {defect.label}",
+            f"A: {defect.actual_area:.3f} mm2",
+            # f"W: {defect.width:.3f} mm", f"H: {defect.height:.3f} mm"
         ]
         M = cv2.moments(contour)
         cx = int(M["m10"] / M["m00"]) if M["m00"] != 0 else contour[0][0][0]
@@ -199,7 +200,8 @@ class DefectProcessor:
         width, height = width_pixels * pixel_to_mm, height_pixels * pixel_to_mm
         return pixel_area, actual_area, width, height, min_rect
 
-    def analyze_from_json(self, json_data: Dict[str, Any]) -> AnalysisResult:
+    def analyze_from_json(self, json_data: Dict[str, Any],
+                          is_return_obj = False) -> dict|AnalysisResult:
         """
         [需求 1] 仅根据JSON数据计算缺陷面积并统计,返回包含详细信息的JSON友好对象。
 
@@ -241,10 +243,16 @@ class DefectProcessor:
             result.count_by_label[label] += 1
             result.area_by_label[label] += actual_area
 
-        return result
+        if is_return_obj:
+            return result
+
+        result_json = to_json_serializable(result.to_dict())
+        result_json = json.loads(result_json)
+
+        return result_json
 
     def analyze_and_draw(self, image: np.ndarray, json_data: Dict[str, Any], drawing_params: DrawingParams) -> Tuple[
-        np.ndarray, AnalysisResult]:
+        np.ndarray, dict]:
         """
         [需求 2] 输入图片和JSON数据,返回绘制好的图片和分析结果。
 
@@ -259,7 +267,7 @@ class DefectProcessor:
                 - 包含所有缺陷信息和统计结果的数据对象。
         """
         # 1. 首先,执行纯JSON分析以获取所有计算结果
-        analysis_result = self.analyze_from_json(json_data)
+        analysis_result = self.analyze_from_json(json_data, is_return_obj=True)
 
         # 2. 如果没有缺陷,直接返回原图和分析结果
         if not analysis_result.defects:
@@ -269,7 +277,10 @@ class DefectProcessor:
         visualizer = DefectVisualizer(drawing_params)
         drawn_image = visualizer.draw_defects_on_image(image, analysis_result.defects)
 
-        return drawn_image, analysis_result
+        result_json = to_json_serializable(analysis_result.to_dict())
+        result_json = json.loads(result_json)
+
+        return drawn_image, result_json
 
 
 def run_json_only_analysis_example(json_path: str, output_json_path: str):
@@ -369,10 +380,10 @@ if __name__ == "__main__":
     # print("\n" + "=" * 50 + "\n")
 
     # 2. 图像和JSON结合分析
-    run_image_and_json_analysis_example(
-        image_path=image_file_path,
-        json_path=json_file_path,
-        output_dir=output_dir
-    )
-
-    fry_algo_print("重要", "所有示例运行完毕!")
+    # run_image_and_json_analysis_example(
+    #     image_path=image_file_path,
+    #     json_path=json_file_path,
+    #     output_dir=output_dir
+    # )
+    #
+    # fry_algo_print("重要", "所有示例运行完毕!")

+ 0 - 0
app/utils/card_inference/__init__.py


+ 2 - 1
app/utils/card_inference/fry_bisenetv2_predictor_V04_250819.py

@@ -19,7 +19,8 @@ logging.basicConfig(level=logging.INFO)
 
 
 def fry_algo_print(level_str: str, info_str: str):
-    logging.info(f"[{level_str}] : {info_str}")
+    # logging.info(f"[{level_str}] : {info_str}")
+    pass
 
 
 def fry_cv2_imread(filename, flags=cv2.IMREAD_COLOR):