# Bird Detect Cascade Service:YOLOv5 + ConvNeXt 鸟类级联识别服务 本项目用于将 **YOLOv5 目标检测模型** 和 **ConvNeXt 鸟类细粒度分类模型** 级联起来: 1. 接收图片或视频; 2. 使用 YOLOv5 检测鸟类目标位置; 3. 对每个检测框进行目标裁剪; 4. 将裁剪图送入 ConvNeXt 分类模型; 5. 返回每个目标的位置、检测置信度、分类类别、分类置信度; 6. 可选返回画框后的图片或视频。 项目默认使用 **ONNX Runtime** 推理,适合用 Docker 部署成 HTTP API 服务,方便网站后端调用。 --- ## 1. 项目结构 ```text bird_detect_cascade_service/ ├── app/ │ ├── main.py # FastAPI 接口入口 │ ├── config.py # 配置读取 │ ├── schemas.py # 返回结构 │ ├── pipeline.py # YOLO + 裁剪 + ConvNeXt 级联流程 │ ├── utils.py # 图像工具函数 │ └── inference/ │ ├── yolo_onnx.py # YOLOv5 ONNX 推理与后处理 │ └── convnext_onnx.py # ConvNeXt ONNX 推理 ├── models/ │ ├── yolov5.onnx # 你的 YOLOv5 ONNX 模型,需自行放入 │ ├── convnext.onnx # 你的 ConvNeXt ONNX 模型,需自行放入 │ ├── det_classes.txt # YOLO 类别名 │ └── cls_classes.txt # ConvNeXt 类别名 ├── test_client.py # 本地接口测试脚本 ├── requirements.txt ├── Dockerfile ├── docker-compose.yml └── README.md ``` --- ## 2. 模型准备 ### 2.1 YOLOv5 导出 ONNX 如果你的 YOLOv5 是 `.pt` 权重,一般可在 YOLOv5 项目目录下执行: ```bash python export.py \ --weights /path/to/best.pt \ --img 960 960 \ --batch 1 \ --device cpu \ --include onnx \ --simplify ``` 导出后把 ONNX 文件复制为: ```bash models/yolov5.onnx ``` ### 2.2 ConvNeXt 导出 ONNX 如果你已有 ConvNeXt 的 `.onnx`,直接复制为: ```bash models/convnext.onnx ``` 如果你只有 PyTorch 权重,需要用你训练时的模型定义导出,输入通常为: ```text 1 × 3 × 224 × 224 ``` 如果你的 ConvNeXt 输入尺寸不是 224,需要修改 `.env` 或 `docker-compose.yml` 中的: ```bash CLS_IMG_SIZE=224 ``` --- ## 3. 类别文件格式 ### 3.1 YOLO 检测类别 `models/det_classes.txt` 如果 YOLO 只检测鸟,可以写: ```text bird ``` 如果 YOLO 有多个检测类别,例如: ```text bird person cat dog ``` 每一行对应一个类别,行号从 0 开始。 ### 3.2 ConvNeXt 分类类别 `models/cls_classes.txt` 每一行一个鸟类类别,顺序必须和训练时类别索引一致,例如: ```text Aix_galericulata Anas_crecca Anser_cygnoides Cygnus_columbianus ``` --- ## 4. 本地直接运行 安装依赖: ```bash pip install -r requirements.txt ``` 启动服务: ```bash uvicorn app.main:app --host 0.0.0.0 --port 8000 ``` 访问接口文档: ```text http://127.0.0.1:8000/docs ``` --- ## 5. Docker 构建与运行 ### 5.1 构建镜像 ```bash docker build -t bird-detect-cascade-service:latest . ``` ### 5.2 启动容器 ```bash docker run -d \ --name bird-detect-cascade-service \ -p 8000:8000 \ -v $(pwd)/models:/app/models \ bird-detect-cascade-service:latest ``` ### 5.3 使用 docker compose ```bash docker compose up -d --build ``` 查看日志: ```bash docker logs -f bird-detect-cascade-service ``` 停止服务: ```bash docker compose down ``` --- ## 6. API 说明 ### 6.1 健康检查 ```bash curl http://127.0.0.1:8000/health ``` 返回示例: ```json { "status": "ok", "det_model_loaded": true, "cls_model_loaded": true } ``` ### 6.2 图片识别接口 接口: ```text POST /predict ``` 参数: | 参数 | 类型 | 默认值 | 说明 | |---|---|---:|---| | file | file | 必填 | 上传图片 | | return_image | bool | false | 是否返回画框后的 base64 图片 | | det_conf | float | 配置文件默认值 | YOLO 检测置信度阈值 | | iou_thres | float | 配置文件默认值 | NMS 的 IoU 阈值 | curl 示例: ```bash curl -X POST "http://127.0.0.1:8000/predict?return_image=true" \ -F "file=@/path/to/test.jpg" ``` 返回示例: ```json { "success": true, "image_width": 1920, "image_height": 1080, "num_objects": 1, "results": [ { "bbox_xyxy": [500.2, 130.7, 820.9, 600.4], "det_confidence": 0.91, "det_class_id": 0, "det_class_name": "bird", "cls_class_id": 12, "cls_class_name": "Aix_galericulata", "cls_confidence": 0.87 } ], "annotated_image_base64": "..." } ``` --- ## 7. Python 测试调用 ```bash python test_client.py \ --url http://127.0.0.1:8000/predict \ --image /path/to/test.jpg \ --save result.jpg ``` --- ## 8. 关键环境变量 这些配置可以在 `docker-compose.yml` 中修改。 | 变量 | 默认值 | 说明 | |---|---:|---| | DET_MODEL_PATH | /app/models/yolov5.onnx | YOLOv5 ONNX 路径 | | CLS_MODEL_PATH | /app/models/convnext.onnx | ConvNeXt ONNX 路径 | | DET_CLASSES_PATH | /app/models/det_classes.txt | 检测类别文件 | | CLS_CLASSES_PATH | /app/models/cls_classes.txt | 分类类别文件 | | DET_IMG_SIZE | 960 | YOLO 输入尺寸 | | CLS_IMG_SIZE | 224 | ConvNeXt 输入尺寸 | | DET_CONF_THRES | 0.25 | YOLO 默认置信度阈值 | | DET_IOU_THRES | 0.45 | NMS 默认 IoU 阈值 | | CROP_PADDING_RATIO | 0.08 | 裁剪框外扩比例 | | MAX_DETECTIONS | 50 | 单张图片最多返回目标数 | | DEVICE | cpu | onnxruntime 设备,默认 cpu | --- ## 9. 给网站同事的集成方式 网站后端只需要以 `multipart/form-data` 方式上传图片到: ```text POST http://服务器IP:8000/predict ``` 前端或后端拿到 JSON 后,根据 `results` 字段展示识别结果即可。 如果网站只需要结果,不需要画框图: ```text return_image=false ``` 如果网站需要展示画框图: ```text return_image=true ``` 返回的 `annotated_image_base64` 是 JPEG 的 base64 字符串,前端可拼接为: ```javascript const imgSrc = "data:image/jpeg;base64," + annotated_image_base64; ``` --- ## 10. 注意事项 1. YOLO 的输入尺寸必须和导出 ONNX 时一致,例如导出时是 `--img 960 960`,配置里也建议用 `DET_IMG_SIZE=960`。 2. ConvNeXt 的类别文件顺序必须和训练时完全一致。 3. 如果 YOLO 输出不是标准 YOLOv5 ONNX 格式 `[1, num_boxes, 5 + num_classes]`,需要根据实际输出改 `app/inference/yolo_onnx.py`。 4. 如果 ConvNeXt 训练时使用了非 ImageNet 的 mean/std,需要修改 `app/inference/convnext_onnx.py` 中的归一化参数。 5. 如果部署机器没有 GPU,默认 CPU 推理即可;如果需要 GPU 版本,需要替换 `onnxruntime` 为 `onnxruntime-gpu`,并使用支持 CUDA 的基础镜像。 --- ## 11. 常见问题 ### 11.1 为什么要用 HTTP API? 因为最终要集成到网站中,HTTP API 是最直接的对接方式。网站后端不需要关心模型细节,只需要上传图片并接收 JSON。 ### 11.2 为什么先检测再分类? YOLOv5 负责解决“鸟在哪里”,ConvNeXt 负责解决“这只鸟是什么种类”。先裁剪目标区域可以减少背景干扰,更适合细粒度分类。 ### 11.3 多只鸟怎么处理? YOLO 检测到几个目标,就裁剪几个目标,并分别送入 ConvNeXt 分类,最终返回一个结果列表。 ### 11.4 没检测到鸟怎么办? 返回: ```json { "success": true, "num_objects": 0, "results": [] } ``` --- ## 12. 视频识别接口 当前版本新增视频接口: ```text POST /predict_video ``` ### 12.1 后台测试视频上传 启动服务后,在项目根目录执行: ```bash bash backend_test_video.sh /path/to/test.mp4 ``` 等价于: ```bash curl -X POST "http://127.0.0.1:8000/predict_video?sample_interval=25&save_annotated_video=true" \ -F "file=@/path/to/test.mp4" ``` 其中: - `sample_interval=25` 表示每 25 帧处理 1 帧; - `save_annotated_video=true` 表示保存画框后视频; - 如果视频很长,可以把 `sample_interval` 调大,例如 50、100,减少计算量。 ### 12.2 视频运行结果保存位置 如果使用 `docker compose up -d --build` 启动,`docker-compose.yml` 已经挂载: ```yaml volumes: - ./models:/app/models - ./runs:/app/runs ``` 所以结果会同时出现在: 容器内: ```text /app/runs/video_时间_随机ID/ ``` 宿主机项目目录下: ```text ./runs/video_时间_随机ID/ ``` 每次处理一个视频,会生成一个新的任务目录,例如: ```text runs/ └── video_20260518_123000_a1b2c3d4/ ├── source_video.mp4 # 上传的原始视频副本 ├── annotated.mp4 # 画框后的结果视频 ├── summary.json # 本次视频任务摘要 ├── frame_results.json # 每个抽帧的详细识别结果 └── frames/ ├── frame_00000000.jpg # 有识别结果的关键帧截图 ├── frame_00000025.jpg └── ... ``` ### 12.3 返回结果示例 ```json { "success": true, "task_dir": "/app/runs/video_20260518_123000_a1b2c3d4", "source_video": "/app/runs/video_20260518_123000_a1b2c3d4/source_video.mp4", "annotated_video": "/app/runs/video_20260518_123000_a1b2c3d4/annotated.mp4", "frames_dir": "/app/runs/video_20260518_123000_a1b2c3d4/frames", "frame_results_json": "/app/runs/video_20260518_123000_a1b2c3d4/frame_results.json", "summary_json": "/app/runs/video_20260518_123000_a1b2c3d4/summary.json", "sample_interval": 25, "processed_frames": 120, "detected_frames": 87, "elapsed_sec": 35.6 } ``` ### 12.4 网站集成建议 网站后端上传视频到: ```text POST http://服务器IP:8000/predict_video ``` 如果网站需要展示结果,可以让网站后端读取返回的: ```text annotated_video frame_results_json summary_json ``` 正式部署时更推荐把 `runs/` 目录映射到网站后端可访问的静态文件目录,或者在本服务里再增加 `/files/...` 静态下载接口。