Browse Source

json缺陷加上置信度, 进一步计算卡片图片分数

AnlaAnla 1 tháng trước cách đây
mục cha
commit
7fcd1a3833

+ 14 - 0
app/core/scoring_config.json

@@ -351,5 +351,19 @@
       "front": 0.75,
       "back": 0.25
     }
+  },
+  "card": {
+    "PSA": {
+      "face": 0.35,
+      "corner": 0.3,
+      "edge": 0.1,
+      "center": 0.25
+    },
+    "BGS": {
+      "face": 0.3,
+      "corner": 0.25,
+      "edge": 0.1,
+      "center": 0.25
+    }
   }
 }

+ 16 - 16
app/services/score_service.py

@@ -5,7 +5,6 @@ from app.core.logger import get_logger
 from app.services.defect_service import DefectInferenceService
 from app.services.card_rectify_and_center import CardRectifyAndCenter
 from app.utils.score_inference.CardScorer import CardScorer
-from app.utils.json_data_formate import formate_one_card_result
 import numpy as np
 import json
 
@@ -25,7 +24,6 @@ class ScoreService:
         logger.info("开始进行卡片居中和转正")
         img_bgr = rectify_center_service.rectify_and_center(img_bgr)
 
-
         logger.info("开始进行卡片分数推理")
         if score_type == 'front_corner_edge' or score_type == 'front_face':
             center_data = defect_service.defect_inference("pokemon_front_card_center", img_bgr.copy())
@@ -57,27 +55,29 @@ class ScoreService:
                 return {}
 
         logger.info("模型推理结束, 开始计算分数")
-
         if score_type == 'front_corner_edge' or score_type == 'front_face':
-            center_score_data = card_scorer.calculate_centering_score('front', center_data, True)
+            card_aspect = "front"
         else:
-            center_score_data = card_scorer.calculate_centering_score('back', center_data, True)
+            card_aspect = "back"
+        if score_type == 'front_corner_edge' or score_type == 'back_corner_edge':
+            card_defect_type = "corner_edge"
+        else:
+            card_defect_type = "face"
+
+        center_score_data = card_scorer.calculate_centering_score(card_aspect, center_data, True)
 
-        if score_type == 'front_corner_edge':
+        if card_defect_type == 'corner_edge':
             # 先计算角的分数, 会把分数写入json, 然后传入刚写好的, 继续写边的分数
-            corner_score_data = card_scorer.calculate_defect_score('corner', 'front', defect_data, True)
-            defect_score_data = card_scorer.calculate_defect_score('edge', 'front', corner_score_data, True)
-        elif score_type == 'front_face':
-            defect_score_data = card_scorer.calculate_defect_score('face', 'front', defect_data, True)
-        elif score_type == 'back_corner_edge':
-            corner_score_data = card_scorer.calculate_defect_score('corner', 'back', defect_data, True)
-            defect_score_data = card_scorer.calculate_defect_score('edge', 'back', corner_score_data, True)
-        elif score_type == 'back_face':
-            defect_score_data = card_scorer.calculate_defect_score('face', 'back', defect_data, True)
+            corner_score_data = card_scorer.calculate_defect_score('corner', card_aspect, defect_data, True)
+            defect_score_data = card_scorer.calculate_defect_score('edge', card_aspect, corner_score_data, True)
+        elif card_defect_type == 'face':
+            defect_score_data = card_scorer.calculate_defect_score('face', card_aspect, defect_data, True)
         else:
             return {}
 
-        result_json = formate_one_card_result(center_score_data, defect_score_data)
+        result_json = card_scorer.formate_one_card_result(center_score_data, defect_score_data,
+                                                          card_defect_type=card_defect_type,
+                                                          card_aspect=card_aspect)
 
         temp_score_json_path = settings.TEMP_WORK_DIR / f'{score_type}_score.json'
         with open(temp_score_json_path, 'w', encoding='utf-8') as f:

+ 36 - 16
app/utils/defect_inference/CardDefectAggregator.py

@@ -207,31 +207,51 @@ class CardDefectAggregator:
             # cv2.RETR_EXTERNAL 只提取最外层的轮廓
             contours, _ = cv2.findContours(class_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
 
-            # 4. 将提取出的轮廓转换回JSON格式
+            # 4. 为每个合并后的轮廓, 精确计算其置信度
             for contour in contours:
-                # 过滤掉太小的噪点轮廓 (可选,但推荐)
-                if cv2.contourArea(contour) < 5:  # 面积小于5像素的轮廓可能是噪点
+                # 过滤掉太小的噪点轮廓
+                if cv2.contourArea(contour) < 5:
                     continue
 
-                # 找到原始检测中与当前合并轮廓重叠度最高的那个,以继承其属性
-                # 这是一个可选的优化,也可以简单地取第一个或平均值
-                max_prob = 0
-                avg_prob = []
-                class_num = -1
+                # 将当前合并后的轮廓转换为 Shapely 多边形, 以便进行几何相交测试
+                try:
+                    # 确保轮廓点数足够构成多边形
+                    if len(contour.squeeze()) < 3: continue
+                    merged_poly = Polygon(contour.squeeze())
+                    # 如果合并后的多边形本身就无效, 也尝试修复它
+                    if not merged_poly.is_valid:
+                        merged_poly = merged_poly.buffer(0)
+                except Exception as e:
+                    log_print("警告", f"无法为合并轮廓创建多边形: {e}")
+                    continue
 
+                # 找出所有与这个合并轮廓相交的原始检测, 并收集它们的置信度
+                contributing_probs = []
                 for det in dets:
-                    avg_prob.append(det['probability'])
-                    if det['probability'] > max_prob:
-                        max_prob = det['probability']
-                        class_num = det['class_num']
+                    try:
+                        if len(det['points']) < 3: continue
+                        original_poly = Polygon(det['points']).buffer(0)
+                        # 判断原始多边形是否与合并后的多边形相交
+                        if merged_poly.intersects(original_poly):
+                            contributing_probs.append(det['probability'])
+                    except Exception as e:
+                        log_print("警告", f"无法为原始检测创建多边形进行相交测试: {e}")
+                        continue
+
+                # 如果没有找到任何贡献的原始检测 (理论上不应发生), 则跳过
+                if not contributing_probs:
+                    continue
 
-                if not avg_prob: continue
+                # 从贡献的检测中获取 class_num (它们对于同一类别是相同的, 取第一个即可)
+                # 并且计算这些特定检测的平均置信度
+                class_num = dets[0]['class_num']
+                avg_prob = np.mean(contributing_probs)
 
                 final_merged_defects.append({
                     "class_num": class_num,
                     "label": class_label,
-                    "probability": np.mean(avg_prob),  # 使用平均置信度
-                    "points": contour.squeeze().tolist()  # 将轮廓点转换为[[x1,y1], [x2,y2]...]格式
+                    "confidence": avg_prob,
+                    "points": contour.squeeze().tolist()
                 })
 
         log_print("成功", f"蒙版合并完成,最终得到 {len(final_merged_defects)} 个独立缺陷。")
@@ -274,7 +294,7 @@ class CardDefectAggregator:
             "num": len(final_defects),
             "cls": [d['class_num'] for d in final_defects],
             "names": [d['label'] for d in final_defects],
-            "conf": np.mean([d['probability'] for d in final_defects]) if final_defects else 0.0,
+            "confidence": np.mean([d['confidence'] for d in final_defects]) if final_defects else 0.0,
             "shapes": final_defects
         }
 

+ 7 - 1
app/utils/defect_inference/arean_anylize_draw.py

@@ -73,6 +73,7 @@ def to_json_serializable(obj):
 class DefectInfo:
     """单个缺陷的详细信息"""
     label: str
+    confidence: float
     pixel_area: float
     actual_area: float  # 平方毫米
     width: float  # 毫米
@@ -83,6 +84,7 @@ class DefectInfo:
     def to_dict(self) -> Dict[str, Any]:
         return {
             "label": self.label,
+            "confidence": self.confidence,
             "pixel_area": self.pixel_area,
             "actual_area": self.actual_area,
             "width": self.width,
@@ -150,8 +152,10 @@ class DefectVisualizer:
             cv2.drawContours(image, [box], 0, self.params.rect_color, self.params.rect_thickness)
         info_text = [
             f"L: {defect.label}",
+            f"CONF: {defect.confidence:.2f}",
             f"A: {defect.actual_area:.3f} mm2",
-            f"W: {defect.width:.3f} mm", f"H: {defect.height:.3f} mm"
+            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]
@@ -229,6 +233,7 @@ class DefectProcessor:
         for shape in json_data['shapes']:
             label = shape.get('label', 'unlabeled')
             points = shape.get('points')
+            confidence = shape.get("confidence")
             if not points:
                 continue
 
@@ -237,6 +242,7 @@ class DefectProcessor:
 
             defect = DefectInfo(
                 label=label,
+                confidence=confidence,
                 pixel_area=pixel_area,
                 actual_area=actual_area,
                 width=width,

+ 1 - 9
app/utils/json_data_formate.py

@@ -1,4 +1,5 @@
 import json
+import logging
 
 
 def formate_center_data(center_result, inner_data: dict, outer_data: dict):
@@ -25,15 +26,6 @@ def formate_face_data(area_json: dict):
     return area_json
 
 
-def formate_one_card_result(center_result: dict, defect_result: dict):
-    data = {
-        "result": {
-            "center_result": center_result,
-            "defect_result": defect_result
-        }
-    }
-    return data
-
 # if __name__ == '__main__':
 #     json_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\_temp_work\pokemon_front_face_no_reflect_defect-area_result.json"
 #     with open(json_path, 'r') as f:

+ 53 - 3
app/utils/score_inference/CardScorer.py

@@ -34,7 +34,7 @@ class CardScorer:
                                card_defect_type: str,
                                card_aspect: str,
                                defect_data: Dict,
-                               is_write_score: bool = False) -> Union[float, dict]:
+                               is_write_score: bool = True) -> Union[float, dict]:
         """
         一个通用的缺陷计分函数,用于计算角、边、表面的加权分数。
         card_defect_type: 'corner', 'edge', 'face'
@@ -109,7 +109,7 @@ class CardScorer:
         logger.info(f"final weights: {final_weights}, final score: {final_score}")
 
         if is_write_score:
-            defect_data[f'{card_aspect}_{card_defect_type}_score'] = final_score
+            defect_data[f"{card_aspect}_{card_defect_type}_deduct_score"] = final_score
             return defect_data
         else:
             return final_score
@@ -149,11 +149,61 @@ class CardScorer:
         logger.info(f"final weight: {final_weight}, final score: {final_score}")
 
         if is_write_score:
-            center_data[f'{card_aspect}_score'] = final_score
+            center_data['deduct_score'] = final_score
             return center_data
         else:
             return final_score
 
+    def formate_one_card_result(self, center_result: dict,
+                                defect_result: dict,
+                                card_defect_type: str,
+                                card_aspect: str):
+        try:
+            # 获取计算总分的权重
+            card_config = self.config['card']['PSA']
+
+            # 计算各部分的最后分数
+            # 计算居中
+            center_score = center_result['deduct_score']
+            center_weight = card_config['center']
+            final_center_score = center_score * center_weight
+
+            if card_defect_type == "corner_edge":
+                corner_score = defect_result[f"{card_aspect}_corner_deduct_score"]
+                edge_score = defect_result[f"{card_aspect}_edge_deduct_score"]
+
+                corner_weight = card_config['corner']
+                edge_weight = card_config['edge']
+
+                final_defect_score = corner_score * corner_weight + edge_score * edge_weight
+            else:
+                face_score = defect_result[f"{card_aspect}_face_deduct_score"]
+                face_weight = card_config['face']
+
+                final_defect_score = face_score * face_weight
+            # 添加一个用于计算4个卡总分的扣分分数, 根据环光的居中计算总分
+            if card_defect_type == "corner_edge":
+                _used_compute_deduct_score = final_center_score + final_defect_score
+            else:
+                _used_compute_deduct_score = final_defect_score
+
+            card_score = self.base_score + final_center_score + final_defect_score
+
+        except Exception as e:
+            logger.error(f"formate_one_card_result 从json获取分数失败: {e}")
+            raise e
+        data = {
+            "result": {
+                "center_result": center_result,
+                "defect_result": defect_result,
+                "card_center_deduct_score": final_center_score,
+                "card_defect_deduct_score": final_defect_score,
+                "_used_compute_deduct_score": _used_compute_deduct_score,
+                "card_score": card_score
+            }
+        }
+        return data
+
 
 if __name__ == '__main__':
     # 1. 初始化评分器,加载规则