CardScorer.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import json
  2. from typing import List, Dict, Any, Union
  3. from app.core.logger import get_logger
  4. logger = get_logger(__name__)
  5. class CardScorer:
  6. """
  7. 它从一个JSON配置文件加载评分规则,并根据输入的卡片数据计算分数。
  8. """
  9. def __init__(self, config_path: str):
  10. try:
  11. with open(config_path, 'r', encoding='utf-8') as f:
  12. self.config = json.load(f)
  13. self.base_score = self.config.get("base_score", 10.0)
  14. except FileNotFoundError:
  15. raise ValueError(f"配置文件未找到: {config_path}")
  16. except json.JSONDecodeError:
  17. raise ValueError(f"配置文件格式错误: {config_path}")
  18. def _get_score_from_tiers(self, value: float, rules: List[List[Any]]) -> float:
  19. """
  20. 一个通用的辅助函数,根据分层规则查找值对应的分数。
  21. """
  22. for tier in rules:
  23. threshold, score = tier
  24. if threshold == "inf" or value < threshold:
  25. return float(score)
  26. return 0.0 # 如果没有匹配的规则,不扣分
  27. def calculate_defect_score(self,
  28. card_defect_type: str,
  29. card_aspect: str,
  30. defect_data: Dict,
  31. is_write_score: bool = False) -> Union[float, dict]:
  32. """
  33. 一个通用的缺陷计分函数,用于计算角、边、表面的加权分数。
  34. card_defect_type: 'corner', 'edge', 'face'
  35. card_aspect: 为 front或 back
  36. is_write_score: 是否将分数写入json并返回
  37. """
  38. if card_defect_type != "corner" and card_defect_type != "edge" and card_defect_type != "face":
  39. raise TypeError("calculate_centering_score:card_type 只能为 'corner', 'edge', 'face'")
  40. if card_aspect != "front" and card_aspect != "back":
  41. raise TypeError("calculate_defect_score:card_type 只能为 front 或 back")
  42. aspect_config = self.config[card_defect_type]
  43. total_deduction = 0.0
  44. weighted_scores = {}
  45. # 1. 计算每种缺陷类型的总扣分
  46. for defect in defect_data['defects']:
  47. if defect['defect_type'] != card_defect_type:
  48. continue
  49. if card_defect_type == 'corner' or card_defect_type == 'edge':
  50. if defect['label'] in ['wear', 'wear_and_impact', 'wear_and_stain']:
  51. defect_type = "wear_area"
  52. elif defect['label'] in ['impact', 'damaged']:
  53. defect_type = "loss_area"
  54. else:
  55. logger.error(f"数据缺陷类型不存在: {defect['label']}")
  56. raise TypeError(f"数据缺陷类型不存在: {defect['label']}")
  57. else:
  58. if defect['label'] in ['wear', 'wear_and_impact', 'damaged']:
  59. defect_type = "wear_area"
  60. elif defect['label'] in ['scratch', 'scuff']:
  61. defect_type = "scratch_length"
  62. elif defect['label'] in ['pit', 'impact']:
  63. defect_type = "pit_area"
  64. elif defect['label'] in ['stain']:
  65. defect_type = "stain_area"
  66. else:
  67. logger.error(f"数据缺陷类型不存在: {defect['label']}")
  68. raise TypeError(f"数据缺陷类型不存在: {defect['label']}")
  69. rules = aspect_config['rules'][defect_type]
  70. # 对于划痕取长度, 其他取面积
  71. if defect_type == "scratch_length":
  72. area_mm = max(defect['width'], defect['height'])
  73. else:
  74. area_mm = defect['actual_area']
  75. # 累加所有同类型缺陷的扣分
  76. if defect_type not in weighted_scores.keys():
  77. weighted_scores[defect_type] = 0
  78. # 计算单个缺陷扣分
  79. the_score = self._get_score_from_tiers(area_mm, rules)
  80. print(f"[{card_defect_type}, {defect_type}]: {area_mm}, {the_score}")
  81. weighted_scores[defect_type] += the_score
  82. # 将分数写入json
  83. if is_write_score:
  84. defect['score'] = the_score
  85. # 2. 根据权重/系数计算最终扣分
  86. weights = aspect_config.get(f"{card_aspect}_weights") or aspect_config.get("coefficients")
  87. if not weights:
  88. raise ValueError(f"在配置中未找到 '{card_defect_type}' 的权重/系数")
  89. print(weighted_scores)
  90. for defect_type, score in weighted_scores.items():
  91. total_deduction += score * weights.get(defect_type, 1.0)
  92. final_weights = aspect_config["final_weights"][card_aspect]
  93. final_score = total_deduction * final_weights
  94. logger.info(f"final weights: {final_weights}, final score: {final_score}")
  95. if is_write_score:
  96. defect_data[f'{card_aspect}_{card_defect_type}_score'] = final_score
  97. return defect_data
  98. else:
  99. return final_score
  100. def calculate_centering_score(self,
  101. card_aspect: str,
  102. center_data: dict,
  103. is_write_score: bool = False) -> Union[float, dict]:
  104. """
  105. 计算居中度分数。
  106. card_type 为 front或 back
  107. is_write_score: 是否将分数写入json并返回
  108. """
  109. if card_aspect != "front" and card_aspect != "back":
  110. raise TypeError("calculate_centering_score:card_type 只能为 front 或 back")
  111. centering_config = self.config['centering'][card_aspect]
  112. rules = centering_config['rules']
  113. coefficients = centering_config['coefficients']
  114. center_left = center_data['box_result']['center_inference']['center_left']
  115. center_right = center_data['box_result']['center_inference']['center_right']
  116. center_top = center_data['box_result']['center_inference']['center_top']
  117. center_bottom = center_data['box_result']['center_inference']['center_bottom']
  118. # 将比例转换为用于查找规则的单个最大值
  119. h_lookup_val = max(center_left, center_right)
  120. v_lookup_val = max(center_top, center_bottom)
  121. h_deduction = self._get_score_from_tiers(h_lookup_val, rules) * coefficients['horizontal']
  122. v_deduction = self._get_score_from_tiers(v_lookup_val, rules) * coefficients['vertical']
  123. print(h_deduction, v_deduction)
  124. final_weight = self.config['centering']["final_weights"][card_aspect]
  125. final_score = (h_deduction + v_deduction) * final_weight
  126. logger.info(f"final weight: {final_weight}, final score: {final_score}")
  127. if is_write_score:
  128. center_data[f'{card_aspect}_score'] = final_score
  129. return center_data
  130. else:
  131. return final_score
  132. if __name__ == '__main__':
  133. # 1. 初始化评分器,加载规则
  134. scorer = CardScorer(r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\app\core\scoring_config.json")
  135. # rulers = scorer.config['corners']['rules']['wear_area']
  136. #
  137. # score = scorer._get_score_from_tiers(0.06, rulers)
  138. # print(score)
  139. # print()
  140. # 居中分数
  141. center_data_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\_temp_work\pokemon_card_center-center_result.json"
  142. with open(center_data_path, 'r', encoding='utf-8') as f:
  143. center_data = json.load(f)
  144. center_data = scorer.calculate_centering_score("front", center_data, True)
  145. print(center_data)
  146. # 边角分数
  147. edge_corner_data_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\_temp_work\pokemon_front_corner_no_reflect_defect-corner_result.json"
  148. with open(edge_corner_data_path, 'r', encoding='utf-8') as f:
  149. edge_corner_data = json.load(f)
  150. corner_data = scorer.calculate_defect_score("corner", 'front', edge_corner_data, True)
  151. print(corner_data)
  152. score = scorer.calculate_defect_score("edge", 'front', edge_corner_data, True)
  153. print(score)
  154. # 面分数
  155. face_data_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\_temp_work\pokemon_front_face_no_reflect_defect-face_result.json"
  156. with open(face_data_path, 'r', encoding='utf-8') as f:
  157. face_data = json.load(f)
  158. score = scorer.calculate_defect_score("face", 'front', face_data, True)
  159. print(score)