浏览代码

图像的记录, 数据库, 增删查改

AnlaAnla 3 月之前
父节点
当前提交
92c4dc2224

二进制
Model/pokemon_back_corner_defect.pth


+ 16 - 9
Test/model_test01.py

@@ -55,13 +55,20 @@ def predict_single_image(config_params: dict,
 
 
 if __name__ == '__main__':
-    config = settings.CARD_MODELS_CONFIG
-    predict_single_image(config['pokemon_front_inner_box'],
-                         img_path=r"C:\Users\wow38\Downloads\_250829_1656_最新模型汇总\_250829_1814_宝可梦背面内框模型\pth_and_images\images\Pokémon_back_0805_0001.jpg",
-                         output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\inner")
+    big_img_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg"
 
+    config = settings.CARD_MODELS_CONFIG
+    # predict_single_image(config['pokemon_front_inner_box'],
+    #                      img_path=big_img_path,
+    #                      output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\inner")
+    #
+    # predict_single_image(config['pokemon_back_inner_box'],
+    #                      img_path=r"C:\Users\wow38\Downloads\_250829_1656_最新模型汇总\_250829_1814_宝可梦背面内框模型\pth_and_images\images\Pokémon_back_0805_0001.jpg",
+    #                      output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\back_inner")
+    #
+    #
     # predict_single_image(config['outer_box'],
-    #                      img_path=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg",
+    #                      big_img_path,
     #                      output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\outer")
 
     # predict_single_image(config['pokemon_back_corner_defect'],
@@ -69,12 +76,12 @@ if __name__ == '__main__':
     #                      output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\back_corner")
 
     # predict_single_image(config['pokemon_front_face_no_reflect_defect'],
-    #                      img_path=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg",
+    #                      img_path=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001_grid_r3_c4.jpg",
     #                      output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\face_no_reflect")
 
-    # result = predict_single_image(config['pokemon_front_corner_no_reflect_defect'],
-    #                               img_path=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\00006_250805_pokemon_0001_bottom_grid_r0_c5.jpg",
-    #                               output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\corner_no_reflect")
+    result = predict_single_image(config['pokemon_front_corner_no_reflect_defect'],
+                                  img_path=r"C:\Users\wow38\Downloads\_250829_1656_最新模型汇总\_250829_1817_宝可梦非闪光卡正面边角模型\pth_and_images\images\250730_pokemon_0031_bottom_grid_r0_c5.jpg",
+                                  output_dir=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\corner_no_reflect")
 
     # result = predict_single_image(config['pokemon_front_face_no_reflect_defect'],
     #                      img_path=r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001_grid_r3_c4.jpg",

+ 33 - 4
Test/test01.py

@@ -1,6 +1,35 @@
-import json
+from multiprocessing import Process, Queue, Lock
+import time
 
-text = '''{"asda": 666, "abb": 656, "gsd": 123}'''
-data = json.loads(text)
+lock = Lock()
 
-print(data)
+
+def f(q, a):
+    lock.acquire()
+    if a == 3:
+        time.sleep(2)
+
+        q.put(666)
+        print('input')
+    if a == 4:
+        data = q.get()
+        print(data)
+    print(a)
+
+    lock.release()
+
+
+if __name__ == '__main__':
+    q = Queue()
+    p_list = []
+    for i in range(8):
+        p = Process(target=f, args=(q, i,))
+        p_list.append(p)
+
+    for p in p_list:
+        p.start()
+
+    print('-=------')
+
+    for p in p_list:
+        p.join()

+ 0 - 0
Test/test02.py


+ 68 - 0
Test/数据库测试.py

@@ -0,0 +1,68 @@
+import mysql.connector
+from mysql.connector import errorcode
+
+# 你的数据库连接配置
+config = {
+    'user': 'root',
+    'password': '123456',
+    'host': '127.0.0.1',
+    'database': 'card_score_database'
+}
+TABLES = {}
+TABLES['employees'] = (
+    "CREATE TABLE `employees` ("
+    "  `id` int(11) NOT NULL AUTO_INCREMENT,"
+    "  `name` varchar(50) NOT NULL,"
+    "  `position` varchar(50) NOT NULL,"
+    "  `hire_date` date NOT NULL,"
+    "  PRIMARY KEY (`id`)"
+    ") ENGINE=InnoDB")
+
+connection = None
+cursor = None
+database_name = "card_score_database"
+
+
+def connect_mysql():
+    # 尝试连接
+    connection = mysql.connector.connect(**config)
+    print("成功连接到 MySQL 服务器!")
+
+    # 获取一个游标对象
+    cursor = connection.cursor()
+
+    # 打印服务器版本信息
+    cursor.execute("SELECT VERSION()")
+    version = cursor.fetchone()
+    print(f"数据库版本: {version[0]}")
+    return connection, cursor
+
+
+def creat_database(cursor, database_name):
+    cursor.execute(f"CREATE DATABASE IF NOT EXISTS {database_name} DEFAULT CHARACTER SET 'utf8mb4'")
+
+
+if __name__ == '__main__':
+    connection, cursor = connect_mysql()
+
+    # creat_database(cursor, database_name)
+    # 创建 employees 表
+    # cursor.execute(TABLES['employees'])
+
+    # add_employee_sql = ("INSERT INTO employees "
+    #                     "(name, position, hire_date) "
+    #                     "VALUES (%s, %s, %s)")
+    # employee_data = ('张三', '软件工程师', '2023-01-15')
+    # cursor.execute(add_employee_sql, employee_data)
+    # employee_id = cursor.lastrowid  # 获取刚插入行的ID
+    # print(f"成功插入一条记录, ID: {employee_id}")
+    #
+    # connection.commit()
+
+    # 1. 查询所有员工
+    query_all = "SELECT id, name, position, hire_date FROM employees"
+    cursor.execute(query_all)
+
+    print("\n--- 所有员工信息 ---")
+    for row in cursor.fetchall():
+        print(f"ID: {row['id']}, 姓名: {row['name']}, 职位: {row['position']}, 入职日期: {row['hire_date']}")

+ 244 - 0
app/api/image_data.py

@@ -0,0 +1,244 @@
+# app/api/image_data.py
+
+import os
+import uuid
+import json  # <-- 确保导入了json库
+from typing import Optional, Dict, Any, List
+from fastapi import APIRouter, File, UploadFile, Depends, HTTPException, Form, Query
+from fastapi.responses import JSONResponse
+from fastapi.concurrency import run_in_threadpool
+from pydantic import BaseModel, field_validator
+import mysql.connector
+
+from app.core.config import settings
+from app.core.logger import get_logger
+from app.core.database_loader import get_db_connection
+from app.services.score_service import ScoreService
+from app.api.score_inference import ScoreType
+
+logger = get_logger(__name__)
+router = APIRouter()
+
+
+# --- Pydantic 模型定义 ---
+class ImageDataUpdate(BaseModel):
+    img_result_json: Dict[str, Any]
+
+
+class ImageDataResponse(BaseModel):
+    img_id: int
+    img_name: Optional[str] = None
+    img_path: str
+    img_result_json: Dict[str, Any]
+
+    # 添加一个验证器来自动处理从数据库来的字符串
+    @field_validator('img_result_json', mode='before')
+    @classmethod
+    def parse_json_string(cls, value):
+        if isinstance(value, str):
+            try:
+                return json.loads(value)
+            except json.JSONDecodeError:
+                raise ValueError("Invalid JSON string provided")
+        return value
+
+
+# --- API 端点实现 ---
+
+@router.post("/", response_model=ImageDataResponse, summary="创建新的图片记录")
+async def create_image_data(
+        score_type: ScoreType = Form(...),
+        is_reflect_card: bool = Form(False),
+        img_name: Optional[str] = Form(None),
+        file: UploadFile = File(...),
+        db_conn: mysql.connector.connection.MySQLConnection = Depends(get_db_connection)
+):
+    # 1. 保存图片到本地
+    file_extension = os.path.splitext(file.filename)[1]
+    unique_filename = f"{uuid.uuid4()}{file_extension}"
+    img_path = settings.DATA_DIR / unique_filename
+
+    try:
+        image_bytes = await file.read()
+        with open(img_path, "wb") as f:
+            f.write(image_bytes)
+    except Exception as e:
+        logger.error(f"保存图片失败: {e}")
+        raise HTTPException(status_code=500, detail="无法保存图片文件")
+
+    # 2. 调用 ScoreService 生成 JSON 数据
+    try:
+        service = ScoreService()
+        json_result = await run_in_threadpool(
+            service.score_inference,
+            score_type=score_type.value,
+            is_reflect_card=is_reflect_card,
+            image_bytes=image_bytes
+        )
+    except Exception as e:
+        # 如果推理失败,删除已保存的图片
+        os.remove(img_path)
+        logger.error(f"分数推理失败: {e}")
+        raise HTTPException(status_code=500, detail=f"分数推理时发生错误: {e}")
+
+    # 3. 存入数据库
+    cursor = None
+    try:
+        cursor = db_conn.cursor(dictionary=True)
+        insert_query = (
+            f"INSERT INTO {settings.DB_TABLE_NAME} (img_name, img_path, img_result_json) "
+            "VALUES (%s, %s, %s)"
+        )
+        json_string = json.dumps(json_result, ensure_ascii=False)
+        cursor.execute(insert_query, (img_name, str(img_path), json_string))
+        new_id = cursor.lastrowid
+        db_conn.commit()
+
+        logger.info(f"成功创建记录, ID: {new_id}")
+        return {
+            "img_id": new_id,
+            "img_name": img_name,
+            "img_path": str(img_path),
+            "img_result_json": json_result
+        }
+
+    except mysql.connector.Error as err:
+        db_conn.rollback()
+        os.remove(img_path)
+        logger.error(f"数据库插入失败: {err}")
+        raise HTTPException(status_code=500, detail="数据库操作失败")
+    finally:
+        if cursor:
+            cursor.close()
+
+
+@router.get("/{img_id}", response_model=ImageDataResponse, summary="根据ID查询记录")
+def get_image_data_by_id(
+        img_id: int,
+        db_conn: mysql.connector.connection.MySQLConnection = Depends(get_db_connection)
+):
+    cursor = None
+    try:
+        cursor = db_conn.cursor(dictionary=True)
+        query = f"SELECT * FROM {settings.DB_TABLE_NAME} WHERE img_id = %s"
+        cursor.execute(query, (img_id,))
+        record = cursor.fetchone()
+
+        if record is None:
+            raise HTTPException(status_code=404, detail="记录未找到")
+
+        # Pydantic 验证器会自动处理转换,这里不再需要手动转换
+        return record
+
+    except mysql.connector.Error as err:
+        logger.error(f"数据库查询失败: {err}")
+        raise HTTPException(status_code=500, detail="数据库查询失败")
+    finally:
+        if cursor:
+            cursor.close()
+
+
+@router.get("/", response_model=List[ImageDataResponse], summary="根据名称查询记录")
+def get_image_data_by_name(
+        img_name: str,
+        db_conn: mysql.connector.connection.MySQLConnection = Depends(get_db_connection)
+):
+    cursor = None
+    try:
+        cursor = db_conn.cursor(dictionary=True)
+        query = f"SELECT * FROM {settings.DB_TABLE_NAME} WHERE img_name = %s"
+        cursor.execute(query, (img_name,))
+        records = cursor.fetchall()
+
+        # Pydantic 验证器会自动处理转换,这里不再需要手动转换
+        return records
+
+    except mysql.connector.Error as err:
+        logger.error(f"数据库查询失败: {err}")
+        raise HTTPException(status_code=500, detail="数据库查询失败")
+    finally:
+        if cursor:
+            cursor.close()
+
+
+@router.put("/{img_id}", response_model=ImageDataResponse, summary="更新记录的JSON数据")
+def update_image_data_json(
+        img_id: int,
+        data: ImageDataUpdate,
+        db_conn: mysql.connector.connection.MySQLConnection = Depends(get_db_connection)
+):
+    # ... (这个函数逻辑保持不变)
+    cursor = None
+    try:
+        cursor = db_conn.cursor(dictionary=True)
+        check_query = f"SELECT img_path FROM {settings.DB_TABLE_NAME} WHERE img_id = %s"
+        cursor.execute(check_query, (img_id,))
+        record = cursor.fetchone()
+        if not record:
+            raise HTTPException(status_code=404, detail="记录未找到")
+
+        update_query = (
+            f"UPDATE {settings.DB_TABLE_NAME} SET img_result_json = %s WHERE img_id = %s"
+        )
+        json_string = json.dumps(data.img_result_json, ensure_ascii=False)
+        cursor.execute(update_query, (json_string, img_id))
+        db_conn.commit()
+
+        if cursor.rowcount == 0:
+            raise HTTPException(status_code=404, detail="记录未找到或数据未改变")
+
+        logger.info(f"成功更新记录, ID: {img_id}")
+        return get_image_data_by_id(img_id, db_conn)
+
+    except mysql.connector.Error as err:
+        db_conn.rollback()
+        logger.error(f"数据库更新失败: {err}")
+        raise HTTPException(status_code=500, detail="数据库更新失败")
+    finally:
+        if cursor:
+            cursor.close()
+
+
+@router.delete("/{img_id}", summary="删除记录和对应的图片文件")
+def delete_image_data(
+        img_id: int,
+        db_conn: mysql.connector.connection.MySQLConnection = Depends(get_db_connection)
+):
+    # ... (这个函数无需修改)
+    cursor = None
+    try:
+        cursor = db_conn.cursor(dictionary=True)
+
+        query = f"SELECT img_path FROM {settings.DB_TABLE_NAME} WHERE img_id = %s"
+        cursor.execute(query, (img_id,))
+        record = cursor.fetchone()
+
+        if not record:
+            raise HTTPException(status_code=404, detail="记录未找到")
+
+        img_path = record['img_path']
+
+        delete_query = f"DELETE FROM {settings.DB_TABLE_NAME} WHERE img_id = %s"
+        cursor.execute(delete_query, (img_id,))
+        db_conn.commit()
+
+        if cursor.rowcount == 0:
+            raise HTTPException(status_code=404, detail="记录删除失败,可能已被删除")
+
+        try:
+            if os.path.exists(img_path):
+                os.remove(img_path)
+                logger.info(f"成功删除图片文件: {img_path}")
+        except Exception as e:
+            logger.error(f"删除图片文件失败: {img_path}, 错误: {e}")
+
+        logger.info(f"成功删除记录, ID: {img_id}")
+        return JSONResponse(content={"message": f"记录 {img_id} 已成功删除"}, status_code=200)
+
+    except mysql.connector.Error as err:
+        db_conn.rollback()
+        logger.error(f"数据库删除失败: {err}")
+        raise HTTPException(status_code=500, detail="数据库删除失败")
+    finally:
+        if cursor:
+            cursor.close()

+ 32 - 5
app/core/config.py

@@ -15,18 +15,43 @@ class CardModelConfig:
 class Settings:
     API_Inference_prefix: str = "/api/card_inference"
     API_Score_prefix: str = "/api/card_score"
+    API_ImageData_prefix: str = "/api/image_data"
 
     BASE_PATH = Path(__file__).parent.parent.parent.absolute()
 
     TEMP_WORK_DIR = BASE_PATH / "_temp_work"
+    DATA_DIR = BASE_PATH / "Data"
     SCORE_CONFIG_PATH = BASE_PATH / "app/core/scoring_config.json"
 
     # 图片像素与真实图片缩放比例
     PIXEL_RESOLUTION = 24.54
     CORNER_SIZE_MM = 3.0
 
+    # --- 数据库配置 ---
+    DB_NAME = 'card_score_database'
+    DB_TABLE_NAME = 'image_records'
+    DATABASE_CONFIG: Dict[str, str] = {
+        'user': 'root',
+        'password': '123456',
+        'host': '127.0.0.1',
+    }
+    # 连接到指定数据库的配置
+    DATABASE_CONFIG_WITH_DB: Dict[str, str] = {
+        **DATABASE_CONFIG,
+        'database': DB_NAME
+    }
+
     # 使用一个字典来管理所有卡片检测模型
     # key (如 'outer_box') 将成为 API 路径中的 {inference_type}
+    '''
+    face: "1": "wear", "2": "scratch", "3": "stain",
+        "4": "scuff", "5": "impact", "6": "damaged",
+        "7": "wear_and_impact"
+        "8": "stain", "9": "pit"
+    
+    corner: "1": "wear", "2": "wear_and_impact", "3": "damaged",
+                           "4": "impact", "5": "wear_and_stain"
+    '''
     CARD_MODELS_CONFIG: Dict[str, CardModelConfig] = {
         "outer_box": {
             "pth_path": "Model/outer_box.pth",
@@ -51,10 +76,8 @@ class Settings:
         },
         "pokemon_back_corner_defect": {
             "pth_path": "Model/pokemon_back_corner_defect.pth",
-            "class_dict": {
-                1: 'wear', 2: 'wear_and_impact', 3: 'impact',
-                4: 'damaged', 5: 'wear_and_stain',
-            },
+            "class_dict": {"1": "wear", "2": "wear_and_impact", "3": "damaged",
+                           "4": "impact", "5": "wear_and_stain"},
             "img_size": {'width': 512, 'height': 512},
             "confidence": 0.5,
             "input_channels": 3,
@@ -121,6 +144,9 @@ class Settings:
         "pokemon_front_corner_reflect_defect": {
             "model_type": "pokemon_front_corner_reflect_defect"
         },
+        "pokemon_front_face_reflect_defect": {
+            "model_type": "pokemon_front_face_reflect_defect"
+        },
         "pokemon_front_corner_no_reflect_defect": {
             "model_type": "pokemon_front_corner_no_reflect_defect",
         },
@@ -134,7 +160,8 @@ class Settings:
 
 
 settings = Settings()
-print(settings.BASE_PATH)
+print(f"项目根目录: {settings.BASE_PATH}")
+print(f"数据存储目录: {settings.DATA_DIR}")
 
 # DefectType = Enum("InferenceType", {name: name for name in settings.DEFECT_TYPE})
 # print()

+ 100 - 0
app/core/database_loader.py

@@ -0,0 +1,100 @@
+import mysql.connector
+from mysql.connector import errorcode
+from .config import settings
+from app.core.logger import get_logger
+
+logger = get_logger(__name__)
+
+# 全局的连接池
+db_connection_pool = None
+
+
+def init_database():
+    """
+    初始化数据库:如果数据库或表不存在,则创建它们。
+    """
+    logger.info("--- 开始初始化数据库 ---")
+
+    # 1. 尝试连接MySQL服务器(不指定数据库)
+    try:
+        cnx = mysql.connector.connect(**settings.DATABASE_CONFIG)
+        cursor = cnx.cursor()
+
+        # 2. 创建数据库(如果不存在)
+        cursor.execute(f"CREATE DATABASE IF NOT EXISTS {settings.DB_NAME} DEFAULT CHARACTER SET 'utf8mb4'")
+        logger.info(f"数据库 '{settings.DB_NAME}' 已准备就绪。")
+
+        # 3. 切换到目标数据库
+        cnx.database = settings.DB_NAME
+
+        # 4. 创建表(如果不存在)
+        table_description = (
+            f"CREATE TABLE IF NOT EXISTS `{settings.DB_TABLE_NAME}` ("
+            "  `img_id` INT AUTO_INCREMENT PRIMARY KEY,"
+            "  `img_name` VARCHAR(255) NULL,"
+            "  `img_path` VARCHAR(512) NOT NULL,"
+            "  `img_result_json` JSON NOT NULL,"
+            "  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
+            ") ENGINE=InnoDB"
+        )
+        cursor.execute(table_description)
+        logger.info(f"数据表 '{settings.DB_TABLE_NAME}' 已准备就绪。")
+
+    except mysql.connector.Error as err:
+        logger.error(f"数据库初始化失败: {err}")
+        exit(1)  # 初始化失败直接退出程序
+    finally:
+        if 'cursor' in locals() and cursor:
+            cursor.close()
+        if 'cnx' in locals() and cnx.is_connected():
+            cnx.close()
+
+    logger.info("--- 数据库初始化完成 ---")
+
+
+def load_database_pool():
+    """
+    在应用启动时创建数据库连接池。
+    """
+    global db_connection_pool
+    if db_connection_pool is None:
+        logger.info("--- 创建数据库连接池 ---")
+        try:
+            db_connection_pool = mysql.connector.pooling.MySQLConnectionPool(
+                pool_name="mypool",
+                pool_size=5,  # 池中保持的连接数
+                **settings.DATABASE_CONFIG_WITH_DB
+            )
+            logger.info("--- 数据库连接池创建成功 ---")
+        except mysql.connector.Error as err:
+            logger.error(f"创建数据库连接池失败: {err}")
+            exit(1)
+
+
+def close_database_pool():
+    """
+    在应用关闭时,不需要手动关闭连接池,连接器会自动处理。
+    这个函数留作备用。
+    """
+    logger.info("--- 数据库连接池将自动关闭 ---")
+
+
+# --- FastAPI 依赖注入 ---
+def get_db_connection():
+    """
+    一个FastAPI依赖项,用于从池中获取数据库连接。
+    它确保连接在使用后返回到池中。
+    """
+    if db_connection_pool is None:
+        raise RuntimeError("数据库连接池未初始化")
+
+    db_conn = None
+    try:
+        db_conn = db_connection_pool.get_connection()
+        yield db_conn
+    except mysql.connector.Error as err:
+        logger.error(f"获取数据库连接失败: {err}")
+        # 这里可以根据需要抛出HTTPException
+    finally:
+        if db_conn and db_conn.is_connected():
+            db_conn.close()

+ 8 - 3
app/core/logger.py

@@ -1,10 +1,15 @@
 import logging
 
-
 logging.basicConfig(
     level=logging.INFO,  # 生产环境通常设置为 INFO 或 WARNING
     format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+    filename='app.log',  # 指定日志输出到文件 app.log
+    filemode='w'
 )
-
-# 获取一个日志器实例,通常以模块名命名
 logger = logging.getLogger(__name__)
+
+
+def get_logger(name):
+    # 获取一个日志器实例,通常以模块名命名
+    logger = logging.getLogger(name)
+    return logger

+ 26 - 3
app/core/scoring_config.json

@@ -122,6 +122,28 @@
   },
   "face": {
     "rules": {
+      "wear_area": [
+        [
+          0.05,
+          -0.1
+        ],
+        [
+          0.1,
+          -0.5
+        ],
+        [
+          0.25,
+          -1.5
+        ],
+        [
+          0.5,
+          -3.0
+        ],
+        [
+          "inf",
+          -5.0
+        ]
+      ],
       "pit_area": [
         [
           0.05,
@@ -198,9 +220,10 @@
       ]
     },
     "coefficients": {
-      "scratch_length": 1.0,
-      "dent_area": 1.0,
-      "stain_area": 1.0
+      "wear_area": 0.25,
+      "scratch_length": 0.25,
+      "dent_area": 0.25,
+      "stain_area": 0.25
     },
     "final_weights": {
       "front": 0.75,

+ 13 - 0
app/main.py

@@ -1,8 +1,11 @@
 from fastapi import FastAPI
 from contextlib import asynccontextmanager
+
+from .core.database_loader import init_database, load_database_pool, close_database_pool
 from .core.model_loader import load_models, unload_models
 from app.api.card_inference import router as card_inference_router
 from app.api.score_inference import router as score_inference_router
+from app.api.image_data import router as image_data_router
 import os
 
 from .core.config import settings
@@ -11,11 +14,20 @@ from .core.config import settings
 @asynccontextmanager
 async def lifespan(main_app: FastAPI):
     print("--- 应用启动 ---")
+    # --- 文件和目录准备 ---
     os.makedirs(settings.TEMP_WORK_DIR, exist_ok=True)
+    os.makedirs(settings.DATA_DIR, exist_ok=True)
+
+    # --- 数据库初始化 ---
+    init_database()
+    load_database_pool()
+
+    # --- 模型加载 ---
     load_models()
     yield
 
     print("--- 应用关闭 ---")
+    close_database_pool()
     unload_models()
 
 
@@ -23,3 +35,4 @@ app = FastAPI(title="卡片框和缺陷检测服务", lifespan=lifespan)
 
 app.include_router(card_inference_router, prefix=settings.API_Inference_prefix)
 app.include_router(score_inference_router, prefix=settings.API_Score_prefix)
+app.include_router(image_data_router, prefix=settings.API_ImageData_prefix, tags=["Image Data"])

+ 5 - 3
app/services/defect_service.py

@@ -7,12 +7,13 @@ from app.utils.defect_inference.AnalyzeCenter import analyze_centering_rotated
 from app.utils.defect_inference.ClassifyEdgeCorner import ClassifyEdgeCorner
 from app.utils.json_data_formate import formate_center_data, formate_face_data
 from app.core.config import settings
-from app.core.logger import logger
+from app.core.logger import get_logger
 import json
 
+logger = get_logger(__name__)
 
 class DefectInferenceService:
-    def defect_inference(self, inference_type: str, image_bytes: bytes,
+    def defect_inference(self, inference_type: str , image_bytes: bytes,
                          is_draw_image=False) -> dict:
         """
         执行卡片识别推理。
@@ -110,7 +111,8 @@ class DefectInferenceService:
                 temp_img_path = settings.TEMP_WORK_DIR / f'{inference_type}-corner_result.jpg'
                 cv2.imwrite(temp_img_path, drawn_image)
             else:
-                area_json = processor.analyze_from_json(json_data)
+                area_json: dict = processor.analyze_from_json(json_data)
+            logger.info("边角缺陷面积计算结束")
 
             # 推理外框
             predictor_outer = get_predictor("outer_box")

+ 7 - 4
app/services/score_service.py

@@ -1,10 +1,12 @@
 from app.core.config import settings
-from app.core.logger import logger
+from app.core.logger import get_logger
 from app.services.defect_service import DefectInferenceService
 from app.utils.score_inference.CardScorer import CardScorer
 from app.utils.json_data_formate import formate_one_card_result
 import json
 
+logger = get_logger(__name__)
+
 
 class ScoreService:
     def __init__(self):
@@ -25,11 +27,11 @@ class ScoreService:
             if score_type == 'front_corner_edge':
                 defect_data = defect_service.defect_inference('pokemon_front_corner_reflect_defect', image_bytes)
             elif score_type == 'front_face':
-                return {"result": "目前缺少该模型"}
+                defect_data = defect_service.defect_inference('pokemon_front_face_reflect_defect', image_bytes)
             elif score_type == 'back_corner_edge':
                 defect_data = defect_service.defect_inference('pokemon_back_corner_defect', image_bytes)
             elif score_type == 'back_face':
-                return {"result": "目前缺少该模型"}
+                defect_data = defect_service.defect_inference('pokemon_back_face_defect', image_bytes)
             else:
                 return {}
         else:
@@ -40,7 +42,8 @@ class ScoreService:
             elif score_type == 'back_corner_edge':
                 defect_data = defect_service.defect_inference('pokemon_back_corner_defect', image_bytes)
             elif score_type == 'back_face':
-                return {"result": "目前缺少该模型"}
+                defect_data = defect_service.defect_inference('pokemon_back_face_defect', image_bytes)
+
             else:
                 return {}
 

+ 12 - 6
app/utils/defect_inference/ClassifyEdgeCorner.py

@@ -2,6 +2,8 @@ import json
 import cv2
 import numpy as np
 import math
+import logging
+logger = logging.getLogger('ClassifyEdgeCorner')
 
 
 class ClassifyEdgeCorner:
@@ -75,31 +77,35 @@ class ClassifyEdgeCorner:
         主函数,对缺陷数据进行分类并添加 "defect_type" 标签。
         """
         if not defect_data or 'defects' not in defect_data:
-            print("警告: 缺陷数据为空或格式不正确,跳过处理。")
+            logger.warn("警告: 缺陷数据为空或格式不正确,跳过处理。")
+            return defect_data
+
+        if not defect_data['defects']:
+            logger.warn("没有缺陷数据")
             return defect_data
 
         # 1. 单位转换
         pixel_to_mm = self.PIXEL_RESOLUTION_UM / 1000.0
         corner_size_px = self.CORNER_SIZE_MM / pixel_to_mm
-        print(f"3mm角区尺寸已转换为 {corner_size_px:.2f} 像素。")
+        logger.info(f"3mm角区尺寸已转换为 {corner_size_px:.2f} 像素。")
 
         # 2. 获取外框几何信息
         try:
             outer_box_corners = self.get_outer_box_corners(outer_box_data)
         except ValueError as e:
-            print(f"错误: {e}")
+            logger.info(f"错误: {e}")
             return None
 
         # 3. 定义四个角区
         corner_regions = self.create_corner_regions(outer_box_corners, corner_size_px)
-        print(f"已成功定义 {len(corner_regions)} 个角区。")
+        logger.info(f"已成功定义 {len(corner_regions)} 个角区。")
 
         # 4. 遍历并分类所有缺陷
         processed_count = 0
         for defect in defect_data['defects']:
             # 使用JSON中预先计算好的min_rect中心点,更高效
             if 'min_rect' not in defect or not defect['min_rect']:
-                print(f"警告: 缺陷 '{defect.get('label')}' 缺少 'min_rect' 信息,跳过。")
+                logger.warn(f"警告: 缺陷 '{defect.get('label')}' 缺少 'min_rect' 信息,跳过。")
                 continue
 
             center_point = tuple(defect['min_rect'][0])
@@ -120,5 +126,5 @@ class ClassifyEdgeCorner:
 
             processed_count += 1
 
-        print(f"处理完成!共为 {processed_count} 个缺陷添加了 'defect_type' 标签。")
+        logger.info(f"处理完成!共为 {processed_count} 个缺陷添加了 'defect_type' 标签。")
         return defect_data

+ 26 - 16
app/utils/defect_inference/arean_anylize_draw.py

@@ -6,10 +6,12 @@ import random
 from dataclasses import dataclass, field
 from typing import Dict, List, Optional, Any, Tuple, Union
 from collections import defaultdict
+import logging
 
+logger = logging.getLogger('ClassifyEdgeCorner')
 
 def fry_algo_print(level_str: str, info_str: str):
-    print(f"[{level_str}] : {info_str}")
+    logger.info(f"[{level_str}] : {info_str}")
 
 
 def fry_cv2_imread(filename, flags=cv2.IMREAD_COLOR):
@@ -213,11 +215,16 @@ class DefectProcessor:
         Returns:
             AnalysisResult: 包含所有缺陷信息和统计结果的数据对象。
         """
-        if not json_data or 'shapes' not in json_data:
-            return AnalysisResult()
 
         result = AnalysisResult()
 
+        if not json_data or 'shapes' not in json_data:
+            if is_return_obj:
+                return result
+            result_json = to_json_serializable(result.to_dict())
+            result_json = json.loads(result_json)
+            return result_json
+
         for shape in json_data['shapes']:
             label = shape.get('label', 'unlabeled')
             points = shape.get('points')
@@ -273,7 +280,10 @@ class DefectProcessor:
 
         # 2. 如果没有缺陷,直接返回原图和分析结果
         if not analysis_result.defects:
-            return image, analysis_result
+            result_json = to_json_serializable(analysis_result.to_dict())
+            result_json = json.loads(result_json)
+
+            return image, result_json
 
         # 3. 使用DefectVisualizer进行绘图
         visualizer = DefectVisualizer(drawing_params)
@@ -303,12 +313,12 @@ def run_json_only_analysis_example(json_path: str, output_json_path: str):
 
     # 3. 打印统计结果
     fry_algo_print("信息", f"分析完成: {os.path.basename(json_path)}")
-    stats = analysis_result.to_dict()["statistics"]
+    stats = analysis_result["statistics"]
     print(json.dumps(stats, indent=2, ensure_ascii=False))
 
     # 4. 将完整结果保存为新的JSON文件
     with open(output_json_path, 'w', encoding='utf-8') as f:
-        json.dump(analysis_result.to_dict(), f, ensure_ascii=False, indent=2, default=to_json_serializable)
+        json.dump(analysis_result, f, ensure_ascii=False, indent=2, default=to_json_serializable)
     fry_algo_print("成功", f"详细分析结果已保存到: {output_json_path}")
 
 
@@ -367,10 +377,10 @@ def run_image_and_json_analysis_example(image_path: str, json_path: str, output_
 
 
 if __name__ == "__main__":
-    image_file_path = r"C:\Code\ML\Project\卡片缺陷检测项目组\计算边角缺陷大小\测试数据\250805_pokemon_0001.jpg"
-    json_file_path = r"C:\Code\ML\Project\卡片缺陷检测项目组\计算边角缺陷大小\测试数据\250805_pokemon_0001.json"
+    image_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\250805_pokemon_0001.jpg"
+    json_file_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\_temp_work\pokemon_front_corner_no_reflect_defect-merge.json"
 
-    output_dir = r"C:\Code\ML\Project\卡片缺陷检测项目组\计算边角缺陷大小\测试数据_my"
+    output_dir = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\temp\测试数据_my"
     os.makedirs(output_dir, exist_ok=True)
 
     # 1. 仅JSON分析
@@ -382,10 +392,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 - 2
app/utils/json_data_formate.py

@@ -27,8 +27,6 @@ def formate_face_data(area_json: dict):
 
 def formate_one_card_result(center_result: dict, defect_result: dict):
     data = {
-        "img_id": 2,
-        "img_url": "https://123.jpg",
         "result": {
             "center_result": center_result,
             "defect_result": defect_result

+ 11 - 5
app/utils/score_inference/CardScorer.py

@@ -1,5 +1,8 @@
 import json
 from typing import List, Dict, Any, Union
+from app.core.logger import get_logger
+
+logger = get_logger(__name__)
 
 
 class CardScorer:
@@ -58,15 +61,19 @@ class CardScorer:
                 elif defect['label'] in ['impact', 'damaged']:
                     defect_type = "loss_area"
                 else:
+                    logger.error(f"数据缺陷类型不存在: {defect['label']}")
                     raise TypeError(f"数据缺陷类型不存在: {defect['label']}")
             else:
-                if defect['label'] in ['scratch']:
+                if defect['label'] in ['wear', 'wear_and_impact', 'damaged']:
+                    defect_type = "wear_area"
+                elif defect['label'] in ['scratch', 'scuff']:
                     defect_type = "scratch_length"
-                elif defect['label'] in ['pit']:
+                elif defect['label'] in ['pit', 'impact']:
                     defect_type = "pit_area"
                 elif defect['label'] in ['stain']:
                     defect_type = "stain_area"
                 else:
+                    logger.error(f"数据缺陷类型不存在: {defect['label']}")
                     raise TypeError(f"数据缺陷类型不存在: {defect['label']}")
 
             rules = aspect_config['rules'][defect_type]
@@ -99,7 +106,7 @@ class CardScorer:
 
         final_weights = aspect_config["final_weights"][card_aspect]
         final_score = total_deduction * final_weights
-        print(f"final weights: {final_weights}, final score: {final_score}")
+        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
@@ -139,7 +146,7 @@ class CardScorer:
         final_weight = self.config['centering']["final_weights"][card_aspect]
         final_score = (h_deduction + v_deduction) * final_weight
 
-        print(f"final weight: {final_weight}, final score: {final_score}")
+        logger.info(f"final weight: {final_weight}, final score: {final_score}")
 
         if is_write_score:
             center_data[f'{card_aspect}_score'] = final_score
@@ -164,7 +171,6 @@ if __name__ == '__main__':
     center_data = scorer.calculate_centering_score("front", center_data, True)
     print(center_data)
 
-
     # 边角分数
     edge_corner_data_path = r"C:\Code\ML\Project\CheckCardBoxAndDefectServer\_temp_work\pokemon_front_corner_no_reflect_defect-corner_result.json"
     with open(edge_corner_data_path, 'r', encoding='utf-8') as f: