import cv2 from app.core.config import settings 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 import numpy as np import json logger = get_logger(__name__) class ScoreService: def __init__(self): self.scoring_config_path = settings.SCORE_CONFIG_PATH self.card_scorer = CardScorer(config_path=self.scoring_config_path) self.defect_service = DefectInferenceService() self.rectify_center_service = CardRectifyAndCenter() def score_inference(self, score_type: str, is_reflect_card: bool, img_bgr: np.ndarray) -> dict: # 解包返回值,获取转正后的外框数据 img_bgr, transformed_outer_json = self.rectify_center_service.rectify_and_center(img_bgr) if img_bgr is None: raise ValueError("图像转正处理失败") imageHeight, imageWidth = img_bgr.shape[:2] logger.info("开始进行卡片分数推理, 使用变换后的外框") # 定义通用参数,传入 pre_calculated_outer_result defect_kwargs = { "img_bgr": img_bgr.copy(), "pre_calculated_outer_result": transformed_outer_json } if score_type == 'front_ring' or score_type == 'front_coaxial': center_data = self.defect_service.defect_inference("pokemon_front_card_center", **defect_kwargs) else: center_data = self.defect_service.defect_inference("pokemon_back_card_center", **defect_kwargs) if is_reflect_card: if score_type == 'front_ring': defect_data = self.defect_service.defect_inference('pokemon_front_face_reflect_ring_light_defect', **defect_kwargs) elif score_type == 'front_coaxial': defect_data = self.defect_service.defect_inference('pokemon_front_face_reflect_coaxial_light_defect', **defect_kwargs) elif score_type == 'back_ring': defect_data = self.defect_service.defect_inference('pokemon_back_face_ring_light_defect', **defect_kwargs) elif score_type == 'back_coaxial': defect_data = self.defect_service.defect_inference('pokemon_back_face_coaxial_light_defect', **defect_kwargs) else: return {} else: if score_type == 'front_ring': defect_data = self.defect_service.defect_inference('pokemon_front_face_no_reflect_ring_light_defect', **defect_kwargs) elif score_type == 'front_coaxial': defect_data = self.defect_service.defect_inference('pokemon_front_face_no_reflect_coaxial_light_defect', **defect_kwargs) elif score_type == 'back_ring': defect_data = self.defect_service.defect_inference('pokemon_back_face_ring_light_defect', **defect_kwargs) elif score_type == 'back_coaxial': defect_data = self.defect_service.defect_inference('pokemon_back_face_coaxial_light_defect', **defect_kwargs) else: return {} logger.info("模型推理结束, 开始计算分数") if score_type == 'front_ring' or score_type == 'front_coaxial': card_aspect = "front" else: card_aspect = "back" if score_type == 'front_ring' or score_type == 'back_ring': card_light_type = "ring" else: card_light_type = "coaxial" if card_light_type == 'ring': center_score_data = self.card_scorer.calculate_centering_score(card_aspect, center_data, True) # 计算角, 边, 面的分数, 会把分数写入json, 然后传入刚写好的, 继续写边的分数 defect_data = self.card_scorer.calculate_defect_score('corner', card_aspect, card_light_type, defect_data, True) defect_data = self.card_scorer.calculate_defect_score('edge', card_aspect, card_light_type, defect_data, True) defect_score_data = self.card_scorer.calculate_defect_score('face', card_aspect, card_light_type, defect_data, True) elif card_light_type == 'coaxial': # 居中 center_score_data = {} # 同轴光照片只计算面缺陷 defect_score_data = self.card_scorer.calculate_defect_score('face', card_aspect, card_light_type, defect_data, True) else: return {} result_json = self.card_scorer.formate_one_card_result(center_score_data, defect_score_data, card_light_type=card_light_type, card_aspect=card_aspect, imageHeight=imageHeight, imageWidth=imageWidth) 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: json.dump(result_json, f, ensure_ascii=False, indent=2) logger.info("分数推理完成 ") return result_json @staticmethod def _has_valid_center_box(center_json: dict) -> bool: """判断 center_result 是否包含可用于居中重算的内外框 shapes。 StitchFusion(同轴光)输出的 center_result 里 inner_box/outer_box 的 shapes 为空, 无法做居中重算; 此时应降级为只算面缺陷, 避免索引空列表报错。 """ if not center_json: return False box_result = center_json.get('box_result') or {} inner_shapes = (box_result.get('inner_box') or {}).get('shapes') or [] outer_shapes = (box_result.get('outer_box') or {}).get('shapes') or [] return len(inner_shapes) > 0 and len(outer_shapes) > 0 def recalculate_defect_score(self, score_type: str, json_data: dict): center_json_data = json_data["result"]['center_result'] defect_json_data = json_data["result"]['defect_result'] imageHeight = json_data["result"].get('imageHeight', 0) imageWidth = json_data["result"].get('imageWidth', 0) # 正反面类型分类 if score_type == 'front_ring' or score_type == 'front_coaxial': card_aspect = "front" else: card_aspect = "back" if score_type == 'front_ring' or score_type == 'back_ring': card_light_type = "ring" else: card_light_type = "coaxial" # 环光重算依赖 center_result 的内外框; 若缺失(如同轴光/StitchFusion 数据), # 自动降级为同轴光面缺陷重算, 避免 list index out of range。 if card_light_type == 'ring' and not self._has_valid_center_box(center_json_data): logger.warning("score_type 为环光但 center_result 无有效内外框, 自动降级为同轴光(coaxial)面缺陷重算") card_light_type = "coaxial" logger.info("开始进行缺陷信息重计算") defect_data = self.defect_service.re_inference_from_json(card_light_type=card_light_type, center_json=center_json_data, defect_json=defect_json_data) logger.info("开始重新计算分数") if card_light_type == 'ring': logger.info("开始进行居中信息重计算") center_data = self.defect_service.re_inference_from_json(card_light_type="center", center_json=center_json_data, defect_json=defect_json_data) center_score_data = self.card_scorer.calculate_centering_score(card_aspect, center_data, True) # 计算角, 边, 面的分数, 会把分数写入json, 然后传入刚写好的, 继续写边的分数 logger.info("开始重新计算:边角面") defect_data = self.card_scorer.calculate_defect_score('corner', card_aspect, card_light_type, defect_data, True) defect_data = self.card_scorer.calculate_defect_score('edge', card_aspect, card_light_type, defect_data, True) defect_score_data = self.card_scorer.calculate_defect_score('face', card_aspect, card_light_type, defect_data, True) elif card_light_type == 'coaxial': # 居中 center_score_data = {} # 同轴光照片只计算面缺陷 logger.info("开始重新计算:面") defect_score_data = self.card_scorer.calculate_defect_score('face', card_aspect, card_light_type, defect_data, True) else: return {} result_json = self.card_scorer.formate_one_card_result(center_score_data, defect_score_data, card_light_type=card_light_type, card_aspect=card_aspect, imageHeight=imageHeight, imageWidth=imageWidth) temp_score_json_path = settings.TEMP_WORK_DIR / f're_{score_type}_score.json' with open(temp_score_json_path, 'w', encoding='utf-8') as f: json.dump(result_json, f, ensure_ascii=False, indent=2) logger.info("分数推理完成 ") return result_json