Files
Bird_detect_cascade_service_V2/README?.md
T
2026-08-21 14:19:14 +08:00

456 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/...` 静态下载接口。