10 KiB
Bird Detect Cascade Service:YOLOv5 + ConvNeXt 鸟类级联识别服务
本项目用于将 YOLOv5 目标检测模型 和 ConvNeXt 鸟类细粒度分类模型 级联起来:
- 接收图片或视频;
- 使用 YOLOv5 检测鸟类目标位置;
- 对每个检测框进行目标裁剪;
- 将裁剪图送入 ConvNeXt 分类模型;
- 返回每个目标的位置、检测置信度、分类类别、分类置信度;
- 可选返回画框后的图片或视频。
项目默认使用 ONNX Runtime 推理,适合用 Docker 部署成 HTTP API 服务,方便网站后端调用。
1. 项目结构
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 项目目录下执行:
python export.py \
--weights /path/to/best.pt \
--img 960 960 \
--batch 1 \
--device cpu \
--include onnx \
--simplify
导出后把 ONNX 文件复制为:
models/yolov5.onnx
2.2 ConvNeXt 导出 ONNX
如果你已有 ConvNeXt 的 .onnx,直接复制为:
models/convnext.onnx
如果你只有 PyTorch 权重,需要用你训练时的模型定义导出,输入通常为:
1 × 3 × 224 × 224
如果你的 ConvNeXt 输入尺寸不是 224,需要修改 .env 或 docker-compose.yml 中的:
CLS_IMG_SIZE=224
3. 类别文件格式
3.1 YOLO 检测类别
models/det_classes.txt
如果 YOLO 只检测鸟,可以写:
bird
如果 YOLO 有多个检测类别,例如:
bird
person
cat
dog
每一行对应一个类别,行号从 0 开始。
3.2 ConvNeXt 分类类别
models/cls_classes.txt
每一行一个鸟类类别,顺序必须和训练时类别索引一致,例如:
Aix_galericulata
Anas_crecca
Anser_cygnoides
Cygnus_columbianus
4. 本地直接运行
安装依赖:
pip install -r requirements.txt
启动服务:
uvicorn app.main:app --host 0.0.0.0 --port 8000
访问接口文档:
http://127.0.0.1:8000/docs
5. Docker 构建与运行
5.1 构建镜像
docker build -t bird-detect-cascade-service:latest .
5.2 启动容器
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
docker compose up -d --build
查看日志:
docker logs -f bird-detect-cascade-service
停止服务:
docker compose down
6. API 说明
6.1 健康检查
curl http://127.0.0.1:8000/health
返回示例:
{
"status": "ok",
"det_model_loaded": true,
"cls_model_loaded": true
}
6.2 图片识别接口
接口:
POST /predict
参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| file | file | 必填 | 上传图片 |
| return_image | bool | false | 是否返回画框后的 base64 图片 |
| det_conf | float | 配置文件默认值 | YOLO 检测置信度阈值 |
| iou_thres | float | 配置文件默认值 | NMS 的 IoU 阈值 |
curl 示例:
curl -X POST "http://127.0.0.1:8000/predict?return_image=true" \
-F "file=@/path/to/test.jpg"
返回示例:
{
"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 测试调用
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 方式上传图片到:
POST http://服务器IP:8000/predict
前端或后端拿到 JSON 后,根据 results 字段展示识别结果即可。
如果网站只需要结果,不需要画框图:
return_image=false
如果网站需要展示画框图:
return_image=true
返回的 annotated_image_base64 是 JPEG 的 base64 字符串,前端可拼接为:
const imgSrc = "data:image/jpeg;base64," + annotated_image_base64;
10. 注意事项
- YOLO 的输入尺寸必须和导出 ONNX 时一致,例如导出时是
--img 960 960,配置里也建议用DET_IMG_SIZE=960。 - ConvNeXt 的类别文件顺序必须和训练时完全一致。
- 如果 YOLO 输出不是标准 YOLOv5 ONNX 格式
[1, num_boxes, 5 + num_classes],需要根据实际输出改app/inference/yolo_onnx.py。 - 如果 ConvNeXt 训练时使用了非 ImageNet 的 mean/std,需要修改
app/inference/convnext_onnx.py中的归一化参数。 - 如果部署机器没有 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 没检测到鸟怎么办?
返回:
{
"success": true,
"num_objects": 0,
"results": []
}
12. 视频识别接口
当前版本新增视频接口:
POST /predict_video
12.1 后台测试视频上传
启动服务后,在项目根目录执行:
bash backend_test_video.sh /path/to/test.mp4
等价于:
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 已经挂载:
volumes:
- ./models:/app/models
- ./runs:/app/runs
所以结果会同时出现在:
容器内:
/app/runs/video_时间_随机ID/
宿主机项目目录下:
./runs/video_时间_随机ID/
每次处理一个视频,会生成一个新的任务目录,例如:
runs/
└── video_20260518_123000_a1b2c3d4/
├── source_video.mp4 # 上传的原始视频副本
├── annotated.mp4 # 画框后的结果视频
├── summary.json # 本次视频任务摘要
├── frame_results.json # 每个抽帧的详细识别结果
└── frames/
├── frame_00000000.jpg # 有识别结果的关键帧截图
├── frame_00000025.jpg
└── ...
12.3 返回结果示例
{
"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 网站集成建议
网站后端上传视频到:
POST http://服务器IP:8000/predict_video
如果网站需要展示结果,可以让网站后端读取返回的:
annotated_video
frame_results_json
summary_json
正式部署时更推荐把 runs/ 目录映射到网站后端可访问的静态文件目录,或者在本服务里再增加 /files/... 静态下载接口。