456 lines
10 KiB
Markdown
456 lines
10 KiB
Markdown
# 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/...` 静态下载接口。
|