DAMO-YOLO实战教程:RESTful API封装与Postman接口测试用例
你是不是已经体验过DAMO-YOLO那个酷炫的赛博朋克界面,上传图片就能看到霓虹绿的识别框?但如果你想把这种强大的目标检测能力集成到自己的应用里,比如做个智能安防系统、商品识别小程序,或者批量处理几千张图片,总不能每次都手动上传吧?
这时候,你就需要一个程序能调用的接口——RESTful API。今天,我就带你一步步把DAMO-YOLO封装成标准的API服务,并且用Postman这个工具,像点外卖一样测试我们的接口好不好用。整个过程就像给一个强大的引擎装上标准化的方向盘和仪表盘,让它能轻松接入任何车辆。
1. 为什么需要API封装?
在开始动手之前,我们先聊聊为什么要把DAMO-YOLO封装成API。
想象一下,你开发了一个很棒的手机App,用户拍张照,App就能识别出照片里有什么。如果每次识别都要用户打开浏览器,访问DAMO-YOLO的网页,上传图片,再等结果,最后还得手动把结果复制到App里——这体验简直糟透了。
API封装就是为了解决这个问题。它把DAMO-YOLO的核心检测能力打包成一个标准的服务接口:
- 任何程序都能调用:你的Python脚本、Java后台、手机App、网页前端,只要会发HTTP请求,就能用上目标检测
- 批量处理变得简单:写个循环,就能自动处理整个文件夹的图片
- 集成到工作流:可以和其他系统对接,比如检测到异常物体就自动报警
- 隐藏复杂细节:调用者不需要关心模型怎么加载、图片怎么预处理,只管传图拿结果
我们今天的任务,就是基于DAMO-YOLO现有的Flask后端,设计并实现一套清晰、易用的RESTful API。
2. 环境准备与代码结构
在开始写API之前,我们先看看DAMO-YOLO现有的代码结构。根据提供的系统信息,后端是基于Flask的,模型路径在 /root/ai-models/iic/cv_tinynas_object-detection_damoyolo/。
2.1 理解现有代码
通常,一个Flask的视觉检测应用会有这样的核心文件:
# app.py 或类似的主文件
from flask import Flask, request, render_template, jsonify
import cv2
import torch
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
app = Flask(__name__)
# 加载DAMO-YOLO模型
model_path = '/root/ai-models/iic/cv_tinynas_object-detection_damoyolo/'
object_detect = pipeline(Tasks.image_object_detection, model=model_path)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/upload', methods=['POST'])
def upload_image():
# 处理图片上传和检测的逻辑
file = request.files['image']
# ... 图片处理代码 ...
results = object_detect(image_path)
# ... 结果处理代码 ...
return jsonify(results)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
我们的目标是在这个基础上,设计一套更规范、功能更完整的API接口。
2.2 创建API专用文件
为了保持代码清晰,我建议新建一个专门处理API的文件:
# 在项目根目录创建
touch api_handler.py
touch test_api.py
api_handler.py 将包含所有API相关的业务逻辑,test_api.py 用于本地测试我们的API。
3. 设计RESTful API接口
好的API设计就像设计一个好的用户界面——要直观、一致、易用。我们为DAMO-YOLO设计以下三个核心接口:
3.1 接口设计规范
RESTful API有几个基本原则:
- 使用HTTP方法表示操作类型(GET获取、POST创建、PUT更新、DELETE删除)
- 使用名词表示资源,而不是动词
- 返回标准化的JSON数据
- 使用合适的HTTP状态码
基于这些原则,我们设计以下接口:
| 接口路径 | HTTP方法 | 功能描述 | 适用场景 |
|---|---|---|---|
/api/v1/health | GET | 服务健康检查 | 监控服务是否正常运行 |
/api/v1/detect | POST | 单张图片检测 | 上传一张图片进行目标检测 |
/api/v1/batch-detect | POST | 批量图片检测 | 一次上传多张图片进行检测 |
3.2 详细接口定义
接口1:健康检查接口
GET /api/v1/health
请求参数:无
响应示例:
{
"status": "healthy",
"version": "2.0_pro",
"model": "DAMO-YOLO",
"timestamp": "2024-01-26T13:45:00Z"
}
接口2:单张图片检测接口
POST /api/v1/detect
请求参数:
image: 图片文件(表单数据)confidence(可选): 置信度阈值,默认0.5return_image(可选): 是否返回标注后的图片,默认false
响应示例:
{
"success": true,
"image_id": "img_123456",
"detections": [
{
"class": "person",
"confidence": 0.89,
"bbox": [100, 150, 200, 300], // [x1, y1, x2, y2]
"color": "#00ff7f"
},
{
"class": "car",
"confidence": 0.76,
"bbox": [300, 200, 450, 350],
"color": "#00ff7f"
}
],
"stats": {
"total_objects": 2,
"processing_time": 0.015
}
}
接口3:批量图片检测接口
POST /api/v1/batch-detect
请求参数:
images[]: 多个图片文件(表单数据)confidence(可选): 置信度阈值,默认0.5
响应示例:
{
"success": true,
"batch_id": "batch_789012",
"results": [
{
"image_name": "photo1.jpg",
"detections": [...],
"stats": {...}
},
{
"image_name": "photo2.jpg",
"detections": [...],
"stats": {...}
}
],
"summary": {
"total_images": 2,
"total_objects": 15,
"total_time": 0.235
}
}
4. 实现API处理逻辑
现在我们来编写实际的API处理代码。在 api_handler.py 中:
4.1 基础配置和工具函数
# api_handler.py
import os
import time
import uuid
import json
from datetime import datetime
from flask import Flask, request, jsonify, send_file
import cv2
import numpy as np
from PIL import Image
import io
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
class DAMOYOLOAPI:
def __init__(self, model_path=None):
"""初始化DAMO-YOLO API处理器"""
if model_path is None:
model_path = '/root/ai-models/iic/cv_tinynas_object-detection_damoyolo/'
self.model_path = model_path
self.object_detect = None
self.load_model()
# COCO 80个类别名称
self.coco_classes = [
'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train',
'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign',
'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep',
'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella',
'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard',
'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard',
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork',
'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange',
'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair',
'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv',
'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave',
'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase',
'scissors', 'teddy bear', 'hair drier', 'toothbrush'
]
def load_model(self):
"""加载DAMO-YOLO模型"""
print(f"正在加载DAMO-YOLO模型,路径: {self.model_path}")
try:
self.object_detect = pipeline(
Tasks.image_object_detection,
model=self.model_path
)
print("模型加载成功!")
except Exception as e:
print(f"模型加载失败: {e}")
raise
def process_image(self, image_file, confidence_threshold=0.5):
"""处理单张图片"""
try:
# 将上传的文件转换为OpenCV格式
image_bytes = image_file.read()
nparr = np.frombuffer(image_bytes, np.uint8)
image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if image is None:
return None, "无法解码图片"
# 保存临时文件供模型处理
temp_path = f"/tmp/temp_{int(time.time())}.jpg"
cv2.imwrite(temp_path, image)
# 使用模型进行检测
start_time = time.time()
result = self.object_detect(temp_path)
processing_time = time.time() - start_time
# 清理临时文件
if os.path.exists(temp_path):
os.remove(temp_path)
# 解析检测结果
detections = []
if 'scores' in result:
for i in range(len(result['scores'])):
score = result['scores'][i]
if score >= confidence_threshold:
bbox = result['boxes'][i]
class_id = int(result['labels'][i])
detection = {
'class': self.coco_classes[class_id] if class_id < len(self.coco_classes) else f'class_{class_id}',
'confidence': float(score),
'bbox': [float(bbox[0]), float(bbox[1]), float(bbox[2]), float(bbox[3])],
'color': '#00ff7f' # 赛博朋克绿色
}
detections.append(detection)
return {
'detections': detections,
'processing_time': processing_time,
'image_shape': image.shape
}, None
except Exception as e:
return None, str(e)
def draw_detections(self, image, detections):
"""在图片上绘制检测框"""
for det in detections:
bbox = det['bbox']
x1, y1, x2, y2 = map(int, bbox)
# 将十六进制颜色转换为BGR
color_hex = det['color'].lstrip('#')
color_rgb = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
color_bgr = (color_rgb[2], color_rgb[1], color_rgb[0])
# 绘制矩形框
cv2.rectangle(image, (x1, y1), (x2, y2), color_bgr, 2)
# 添加标签
label = f"{det['class']}: {det['confidence']:.2f}"
cv2.putText(image, label, (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color_bgr, 2)
return image
4.2 集成到Flask应用
现在我们把API处理器集成到Flask应用中。修改或创建 app_api.py:
# app_api.py
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import time
from api_handler import DAMOYOLOAPI
import io
import cv2
import numpy as np
app = Flask(__name__)
CORS(app) # 允许跨域请求
# 初始化API处理器
api_handler = DAMOYOLOAPI()
@app.route('/api/v1/health', methods=['GET'])
def health_check():
"""健康检查接口"""
return jsonify({
'status': 'healthy',
'version': '2.0_pro',
'model': 'DAMO-YOLO',
'timestamp': datetime.now().isoformat(),
'endpoints': {
'single_detection': '/api/v1/detect',
'batch_detection': '/api/v1/batch-detect'
}
})
@app.route('/api/v1/detect', methods=['POST'])
def detect_objects():
"""单张图片检测接口"""
# 检查是否有文件上传
if 'image' not in request.files:
return jsonify({
'success': False,
'error': '没有上传图片文件',
'message': '请使用表单字段名"image"上传图片'
}), 400
image_file = request.files['image']
# 检查文件是否为空
if image_file.filename == '':
return jsonify({
'success': False,
'error': '没有选择文件'
}), 400
# 获取可选参数
confidence = request.form.get('confidence', 0.5, type=float)
return_image = request.form.get('return_image', 'false').lower() == 'true'
# 处理图片
result, error = api_handler.process_image(image_file, confidence)
if error:
return jsonify({
'success': False,
'error': error
}), 500
# 生成响应
response = {
'success': True,
'image_id': f'img_{int(time.time())}',
'detections': result['detections'],
'stats': {
'total_objects': len(result['detections']),
'processing_time': result['processing_time'],
'image_size': result['image_shape']
}
}
# 如果需要返回标注后的图片
if return_image:
# 重新读取图片并绘制检测框
image_file.seek(0) # 重置文件指针
image_bytes = image_file.read()
nparr = np.frombuffer(image_bytes, np.uint8)
image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
# 绘制检测框
annotated_image = api_handler.draw_detections(image, result['detections'])
# 转换为字节流
_, buffer = cv2.imencode('.jpg', annotated_image)
io_buf = io.BytesIO(buffer)
# 保存到临时变量供后续使用
response['annotated_image'] = 'available'
# 在实际应用中,这里可以返回图片文件或Base64编码
return jsonify(response)
@app.route('/api/v1/batch-detect', methods=['POST'])
def batch_detect():
"""批量图片检测接口"""
if 'images' not in request.files:
return jsonify({
'success': False,
'error': '没有上传图片文件',
'message': '请使用表单字段名"images"上传多个图片文件'
}), 400
image_files = request.files.getlist('images')
if not image_files or len(image_files) == 0:
return jsonify({
'success': False,
'error': '没有选择文件'
}), 400
# 限制批量处理的数量
max_files = 10
if len(image_files) > max_files:
return jsonify({
'success': False,
'error': f'一次最多处理{max_files}张图片',
'message': f'当前上传了{len(image_files)}张,请减少数量'
}), 400
confidence = request.form.get('confidence', 0.5, type=float)
batch_id = f'batch_{int(time.time())}'
results = []
total_objects = 0
total_time = 0
for i, image_file in enumerate(image_files):
if image_file.filename == '':
continue
# 处理单张图片
start_time = time.time()
result, error = api_handler.process_image(image_file, confidence)
processing_time = time.time() - start_time
if error:
results.append({
'image_name': image_file.filename,
'success': False,
'error': error
})
else:
results.append({
'image_name': image_file.filename,
'success': True,
'detections': result['detections'],
'stats': {
'objects_count': len(result['detections']),
'processing_time': processing_time
}
})
total_objects += len(result['detections'])
total_time += processing_time
return jsonify({
'success': True,
'batch_id': batch_id,
'results': results,
'summary': {
'total_images': len(results),
'successful': len([r for r in results if r.get('success', False)]),
'failed': len([r for r in results if not r.get('success', False)]),
'total_objects': total_objects,
'total_time': total_time,
'avg_time_per_image': total_time / len(results) if results else 0
}
})
if __name__ == '__main__':
print("启动DAMO-YOLO API服务...")
print("健康检查: GET http://localhost:5000/api/v1/health")
print("单图检测: POST http://localhost:5000/api/v1/detect")
print("批量检测: POST http://localhost:5000/api/v1/batch-detect")
app.run(host='0.0.0.0', port=5000, debug=True)
5. 启动API服务
现在我们可以启动API服务了。首先确保你的DAMO-YOLO环境已经设置好,然后:
# 启动API服务(而不是原来的Web界面)
python app_api.py
你会看到类似这样的输出:
启动DAMO-YOLO API服务...
健康检查: GET http://localhost:5000/api/v1/health
单图检测: POST http://localhost:5000/api/v1/detect
批量检测: POST http://localhost:5000/api/v1/batch-detect
正在加载DAMO-YOLO模型,路径: /root/ai-models/iic/cv_tinynas_object-detection_damoyolo/
模型加载成功!
* Serving Flask app 'app_api'
* Debug mode: on
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
服务启动后,你可以先测试健康检查接口:
curl http://localhost:5000/api/v1/health
应该会返回:
{
"status": "healthy",
"version": "2.0_pro",
"model": "DAMO-YOLO",
"timestamp": "2024-01-26T13:45:00Z",
"endpoints": {
"single_detection": "/api/v1/detect",
"batch_detection": "/api/v1/batch-detect"
}
}
6. 使用Postman测试API
Postman是API开发的瑞士军刀,它让我们可以像在网页上点按钮一样测试API。下面我带你一步步创建完整的测试用例。
6.1 安装和设置Postman
如果你还没有Postman,可以去官网下载安装。安装好后:
- 打开Postman,点击左上角的"New"按钮
- 选择"Collection"创建一个新的测试集合,命名为"DAMO-YOLO API测试"
- 在集合里,我们可以添加多个请求,分别测试不同的接口
6.2 创建健康检查测试用例
首先测试最简单的健康检查接口:
- 在"DAMO-YOLO API测试"集合上右键,选择"Add Request"
- 命名为"健康检查"
- 请求方法选择"GET"
- 输入URL:
http://localhost:5000/api/v1/health - 点击"Send"按钮
你应该能看到类似这样的响应:
{
"status": "healthy",
"version": "2.0_pro",
"model": "DAMO-YOLO",
"timestamp": "2024-01-26T13:45:00Z",
"endpoints": {
"single_detection": "/api/v1/detect",
"batch_detection": "/api/v1/batch-detect"
}
}
6.3 创建单张图片检测测试用例
这是最常用的接口,我们来详细设置:
- 新建一个请求,命名为"单张图片检测"
- 请求方法选择"POST"
- 输入URL:
http://localhost:5000/api/v1/detect
设置请求头(Headers):
在"Headers"标签页,添加:
- Key:
Content-Type, Value:multipart/form-data
设置请求体(Body):
- 选择"form-data"格式
- 添加以下字段:
| Key | Value | 说明 |
|---|---|---|
| image | (选择文件) | 点击选择文件按钮,上传一张测试图片 |
| confidence | 0.5 | 置信度阈值,可以调整 |
| return_image | false | 是否返回标注后的图片 |
添加测试脚本(Tests):
Postman的强大之处在于可以自动验证响应。点击"Tests"标签页,添加以下JavaScript代码:
// 测试1:检查响应状态码
pm.test("状态码是200", function () {
pm.response.to.have.status(200);
});
// 测试2:检查响应包含success字段
pm.test("响应包含success字段", function () {
pm.response.to.have.jsonBody('success');
});
// 测试3:检查success为true
pm.test("检测成功", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.success).to.be.true;
});
// 测试4:检查响应时间在合理范围内
pm.test("响应时间小于5秒", function () {
pm.expect(pm.response.responseTime).to.be.below(5000);
});
// 测试5:保存检测结果到环境变量(可选)
var jsonData = pm.response.json();
if (jsonData.success) {
pm.environment.set("last_image_id", jsonData.image_id);
pm.environment.set("detection_count", jsonData.stats.total_objects);
}
发送请求并查看结果:
点击"Send"按钮,你会在下方看到:
- 响应状态码(应该是200)
- 响应时间
- 响应体(JSON格式的检测结果)
- 测试结果(所有测试应该通过)
响应体示例:
{
"success": true,
"image_id": "img_1706258700",
"detections": [
{
"class": "person",
"confidence": 0.89,
"bbox": [120, 80, 350, 450],
"color": "#00ff7f"
},
{
"class": "car",
"confidence": 0.76,
"bbox": [400, 200, 600, 350],
"color": "#00ff7f"
}
],
"stats": {
"total_objects": 2,
"processing_time": 0.023,
"image_size": [640, 480, 3]
}
}
6.4 创建批量图片检测测试用例
批量检测接口的测试稍微复杂一些:
- 新建请求,命名为"批量图片检测"
- 请求方法:POST
- URL:
http://localhost:5000/api/v1/batch-detect
设置请求体:
- 选择"form-data"格式
- 添加字段:
| Key | Value | 说明 |
|---|---|---|
| images | (选择文件) | 点击选择多个文件(按住Ctrl多选) |
| confidence | 0.5 | 置信度阈值 |
注意:这里的关键是字段名要叫images,并且是复数,这样Flask才能正确获取文件列表。
添加测试脚本:
// 批量检测测试脚本
pm.test("批量检测状态码为200", function () {
pm.response.to.have.status(200);
});
pm.test("批量检测成功", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.success).to.be.true;
});
pm.test("返回批量ID", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.batch_id).to.exist;
});
pm.test("有结果汇总信息", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.summary).to.exist;
pm.expect(jsonData.summary.total_images).to.be.a('number');
});
// 如果有失败的图片,记录日志
var jsonData = pm.response.json();
if (jsonData.summary.failed > 0) {
console.log("批量检测中有失败的图片:");
jsonData.results.forEach(function(result, index) {
if (!result.success) {
console.log(`图片 ${result.image_name}: ${result.error}`);
}
});
}
6.5 创建完整的测试集合
现在我们可以把这三个测试用例组织起来,创建一个完整的测试流程:
-
在Postman中,选择"DAMO-YOLO API测试"集合
-
点击"Run"按钮
-
在运行器中,你可以:
- 选择要运行的请求(全选)
- 设置迭代次数(比如跑3遍)
- 设置延迟(请求之间的等待时间)
- 添加数据文件(用于参数化测试)
-
点击"Run DAMO-YOLO API测试"开始运行
你会看到每个请求的执行情况:
- ✅ 绿色表示测试通过
- ❌ 红色表示测试失败
- 可以看到每个请求的响应时间和状态
6.6 高级测试技巧
环境变量和全局变量
Postman支持环境变量,这在测试不同环境时特别有用:
- 点击右上角的眼睛图标,选择"Environments"
- 点击"Add"创建新环境,命名为"本地开发"
- 添加变量:
base_url:http://localhost:5000
- 在请求URL中使用变量:
{{base_url}}/api/v1/health
预请求脚本(Pre-request Script)
你可以在发送请求前执行一些操作:
// 生成随机置信度阈值
var randomConfidence = (Math.random() * 0.5 + 0.3).toFixed(2);
pm.environment.set("random_confidence", randomConfidence);
console.log("使用置信度阈值: " + randomConfidence);
然后在请求体的confidence字段中引用:{{random_confidence}}
自动化测试工作流
你可以创建一个完整的测试工作流:
// 在集合的Tests标签页添加
pm.test("整个工作流测试", function () {
// 1. 先测试健康检查
pm.sendRequest({
url: pm.environment.get("base_url") + "/api/v1/health",
method: 'GET'
}, function (err, response) {
pm.test("健康检查通过", function () {
pm.expect(response.code).to.equal(200);
});
// 2. 然后测试单张图片检测
var formData = new FormData();
// ... 添加文件和数据 ...
pm.sendRequest({
url: pm.environment.get("base_url") + "/api/v1/detect",
method: 'POST',
body: formData
}, function (err, response) {
pm.test("单图检测通过", function () {
pm.expect(response.code).to.equal(200);
});
});
});
});
7. 常见问题与调试技巧
在API开发和测试过程中,你可能会遇到一些问题。这里是一些常见问题的解决方法:
7.1 文件上传问题
问题:上传图片时收到"没有上传图片文件"的错误。
解决:
- 检查Postman中字段名是否正确:单张检测用
image,批量检测用images - 确保选择了文件(Value列显示文件名而不是"未选择文件")
- 检查文件大小,如果太大可能需要调整Flask配置:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB限制
7.2 跨域问题(CORS)
问题:从网页调用API时出现CORS错误。
解决:我们已经使用了flask_cors,但如果你需要更精细的控制:
from flask_cors import CORS
# 允许所有来源
CORS(app)
# 或者只允许特定来源
CORS(app, resources={r"/api/*": {"origins": "http://localhost:3000"}})
7.3 性能优化
如果API响应慢,可以考虑:
-
启用GPU加速:确保PyTorch使用了CUDA
import torch print(f"CUDA可用: {torch.cuda.is_available()}") print(f"GPU数量: {torch.cuda.device_count()}") -
模型预热:在服务启动时先处理一张图片
# 在初始化后添加 def warm_up_model(self): """预热模型,避免第一次请求慢""" print("预热模型中...") test_image = np.zeros((640, 480, 3), dtype=np.uint8) cv2.imwrite("/tmp/warmup.jpg", test_image) _ = self.object_detect("/tmp/warmup.jpg") print("模型预热完成") -
添加缓存:对相同图片的重复请求返回缓存结果
import hashlib from functools import lru_cache @lru_cache(maxsize=100) def detect_cached(self, image_hash, confidence): """带缓存的检测""" # ... 检测逻辑 ...
7.4 错误处理增强
在生产环境中,需要更完善的错误处理:
@app.errorhandler(413)
def too_large(e):
return jsonify({
'success': False,
'error': '文件太大',
'message': '图片大小不能超过10MB'
}), 413
@app.errorhandler(500)
def internal_error(e):
return jsonify({
'success': False,
'error': '服务器内部错误',
'message': str(e) if app.debug else '请稍后重试'
}), 500
8. 实际应用示例
现在我们的API已经可以正常工作了,来看看怎么在实际项目中使用它。
8.1 Python客户端调用示例
# client_example.py
import requests
import json
from PIL import Image
import io
class DAMOYOLOClient:
def __init__(self, base_url="http://localhost:5000"):
self.base_url = base_url
def health_check(self):
"""检查服务状态"""
response = requests.get(f"{self.base_url}/api/v1/health")
return response.json()
def detect_image(self, image_path, confidence=0.5):
"""检测单张图片"""
with open(image_path, 'rb') as f:
files = {'image': f}
data = {'confidence': confidence}
response = requests.post(
f"{self.base_url}/api/v1/detect",
files=files,
data=data
)
return response.json()
def detect_image_bytes(self, image_bytes, confidence=0.5):
"""从字节流检测图片"""
files = {'image': ('image.jpg', image_bytes, 'image/jpeg')}
data = {'confidence': confidence}
response = requests.post(
f"{self.base_url}/api/v1/detect",
files=files,
data=data
)
return response.json()
def batch_detect(self, image_paths, confidence=0.5):
"""批量检测图片"""
files = []
for path in image_paths:
files.append(('images', open(path, 'rb')))
data = {'confidence': confidence}
response = requests.post(
f"{self.base_url}/api/v1/batch-detect",
files=files,
data=data
)
# 关闭所有文件
for _, f in files:
f.close()
return response.json()
# 使用示例
if __name__ == "__main__":
client = DAMOYOLOClient()
# 1. 检查服务状态
health = client.health_check()
print(f"服务状态: {health['status']}")
# 2. 检测单张图片
result = client.detect_image("test.jpg", confidence=0.6)
if result['success']:
print(f"检测到 {result['stats']['total_objects']} 个物体")
for obj in result['detections']:
print(f" - {obj['class']}: {obj['confidence']:.2f}")
# 3. 批量检测
image_list = ["image1.jpg", "image2.jpg", "image3.jpg"]
batch_result = client.batch_detect(image_list)
print(f"批量处理完成,总计检测到 {batch_result['summary']['total_objects']} 个物体")
8.2 网页前端调用示例
<!-- web_example.html -->
<!DOCTYPE html>
<html>
<head>
<title>DAMO-YOLO Web客户端</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.upload-area {
border: 2px dashed #ccc;
padding: 40px;
text-align: center;
margin: 20px 0;
cursor: pointer;
}
.upload-area:hover { border-color: #00ff7f; }
.result { margin-top: 20px; padding: 15px; background: #f5f5f5; }
.object { margin: 5px 0; padding: 5px; background: white; }
.progress { width: 100%; height: 20px; background: #eee; }
.progress-bar { height: 100%; background: #00ff7f; width: 0%; }
</style>
</head>
<body>
<h1>DAMO-YOLO 目标检测</h1>
<div class="upload-area" id="dropArea">
<p>点击或拖拽图片到这里</p>
<input type="file" id="fileInput" accept="image/*" multiple style="display: none;">
</div>
<div>
<label>置信度阈值: </label>
<input type="range" id="confidence" min="0.1" max="0.9" step="0.1" value="0.5">
<span id="confidenceValue">0.5</span>
</div>
<button onclick="detectImage()">开始检测</button>
<button onclick="batchDetect()" id="batchBtn" style="display: none;">批量检测</button>
<div class="progress" id="progressBar" style="display: none;">
<div class="progress-bar" id="progressFill"></div>
</div>
<div id="results"></div>
<script>
const API_BASE = 'http://localhost:5000';
let selectedFiles = [];
// 更新置信度显示
document.getElementById('confidence').oninput = function() {
document.getElementById('confidenceValue').textContent = this.value;
};
// 文件选择处理
document.getElementById('dropArea').addEventListener('click', () => {
document.getElementById('fileInput').click();
});
document.getElementById('fileInput').addEventListener('change', (e) => {
selectedFiles = Array.from(e.target.files);
updateUI();
});
// 拖拽支持
document.getElementById('dropArea').addEventListener('dragover', (e) => {
e.preventDefault();
e.currentTarget.style.borderColor = '#00ff7f';
});
document.getElementById('dropArea').addEventListener('drop', (e) => {
e.preventDefault();
selectedFiles = Array.from(e.dataTransfer.files);
updateUI();
});
function updateUI() {
const dropArea = document.getElementById('dropArea');
const batchBtn = document.getElementById('batchBtn');
if (selectedFiles.length === 0) {
dropArea.innerHTML = '<p>点击或拖拽图片到这里</p>';
batchBtn.style.display = 'none';
} else if (selectedFiles.length === 1) {
dropArea.innerHTML = `<p>已选择: ${selectedFiles[0].name}</p>`;
batchBtn.style.display = 'none';
} else {
dropArea.innerHTML = `<p>已选择 ${selectedFiles.length} 张图片</p>`;
batchBtn.style.display = 'inline-block';
}
}
async function detectImage() {
if (selectedFiles.length === 0) {
alert('请先选择图片');
return;
}
const confidence = document.getElementById('confidence').value;
const formData = new FormData();
formData.append('image', selectedFiles[0]);
formData.append('confidence', confidence);
showProgress();
try {
const response = await fetch(`${API_BASE}/api/v1/detect`, {
method: 'POST',
body: formData
});
const result = await response.json();
displayResult(result);
} catch (error) {
alert('检测失败: ' + error.message);
} finally {
hideProgress();
}
}
async function batchDetect() {
if (selectedFiles.length < 2) {
alert('请选择多张图片进行批量检测');
return;
}
const confidence = document.getElementById('confidence').value;
const formData = new FormData();
selectedFiles.forEach(file => {
formData.append('images', file);
});
formData.append('confidence', confidence);
showProgress();
try {
const response = await fetch(`${API_BASE}/api/v1/batch-detect`, {
method: 'POST',
body: formData
});
const result = await response.json();
displayBatchResult(result);
} catch (error) {
alert('批量检测失败: ' + error.message);
} finally {
hideProgress();
}
}
function displayResult(result) {
const resultsDiv = document.getElementById('results');
if (!result.success) {
resultsDiv.innerHTML = `<div class="result error">错误: ${result.error}</div>`;
return;
}
let html = `<div class="result">
<h3>检测结果 (${result.image_id})</h3>
<p>检测到 ${result.stats.total_objects} 个物体,耗时 ${result.stats.processing_time.toFixed(3)} 秒</p>`;
if (result.detections.length > 0) {
html += '<div class="objects">';
result.detections.forEach(obj => {
html += `<div class="object">
<strong>${obj.class}</strong> (${obj.confidence.toFixed(2)})
<br>位置: [${obj.bbox.map(x => x.toFixed(0)).join(', ')}]
</div>`;
});
html += '</div>';
} else {
html += '<p>未检测到物体</p>';
}
html += '</div>';
resultsDiv.innerHTML = html;
}
function displayBatchResult(result) {
const resultsDiv = document.getElementById('results');
if (!result.success) {
resultsDiv.innerHTML = `<div class="result error">错误: ${result.error}</div>`;
return;
}
let html = `<div class="result">
<h3>批量检测结果 (${result.batch_id})</h3>
<p>处理 ${result.summary.total_images} 张图片,成功 ${result.summary.successful} 张,失败 ${result.summary.failed} 张</p>
<p>总计检测到 ${result.summary.total_objects} 个物体,总耗时 ${result.summary.total_time.toFixed(3)} 秒</p>`;
result.results.forEach((imgResult, index) => {
html += `<div style="margin-top: 15px; padding: 10px; border: 1px solid #ddd;">
<strong>${imgResult.image_name}</strong>`;
if (imgResult.success) {
html += `<br>检测到 ${imgResult.stats.objects_count} 个物体`;
if (imgResult.detections && imgResult.detections.length > 0) {
imgResult.detections.slice(0, 3).forEach(obj => {
html += `<br> - ${obj.class} (${obj.confidence.toFixed(2)})`;
});
if (imgResult.detections.length > 3) {
html += `<br> ... 还有 ${imgResult.detections.length - 3} 个`;
}
}
} else {
html += `<br>失败: ${imgResult.error}`;
}
html += '</div>';
});
html += '</div>';
resultsDiv.innerHTML = html;
}
function showProgress() {
document.getElementById('progressBar').style.display = 'block';
document.getElementById('progressFill').style.width = '0%';
// 模拟进度
let width = 0;
const interval = setInterval(() => {
if (width >= 90) {
clearInterval(interval);
} else {
width += 10;
document.getElementById('progressFill').style.width = width + '%';
}
}, 200);
}
function hideProgress() {
document.getElementById('progressFill').style.width = '100%';
setTimeout(() => {
document.getElementById('progressBar').style.display = 'none';
}, 500);
}
</script>
</body>
</html>
9. 总结
通过这篇教程,我们完成了DAMO-YOLO的RESTful API封装和Postman测试用例的创建。让我们回顾一下关键点:
9.1 我们做了什么
- 设计了清晰的API接口:包括健康检查、单图检测、批量检测三个核心接口
- 实现了完整的后端逻辑:处理图片上传、调用DAMO-YOLO模型、格式化返回结果
- 创建了Postman测试集合:包含完整的测试用例和自动化测试脚本
- 提供了客户端示例:Python和JavaScript的调用示例
9.2 关键收获
- API设计原则:遵循RESTful规范,使用合适的HTTP方法和状态码
- 错误处理:提供清晰的错误信息和适当的HTTP状态码
- 性能考虑:支持批量处理,添加了模型预热和缓存机制
- 测试驱动:使用Postman创建了完整的测试用例,确保API质量
- 易用性:提供了多种语言的客户端示例,方便集成
9.3 下一步建议
现在你的DAMO-YOLO已经具备了API能力,可以考虑:
- 添加身份验证:如果需要保护API,可以添加API Key或JWT认证
- 实现异步处理:对于大量图片,可以提供异步接口,先返回任务ID,稍后查询结果
- 添加限流:防止API被滥用,可以添加请求频率限制
- 部署到生产环境:使用Gunicorn、Nginx等工具部署到生产服务器
- 添加监控:记录API调用日志,监控性能和错误率
9.4 最后的话
API封装让DAMO-YOLO从一个独立的Web应用变成了一个可编程的服务。现在你可以:
- 在Python脚本中批量处理图片
- 在网页应用中集成目标检测功能
- 在移动App中调用检测服务
- 与其他系统(如监控系统、内容审核系统)集成
最重要的是,通过Postman的测试用例,你可以确保API的稳定性和正确性,这在团队协作和持续集成中特别有价值。
希望这篇教程能帮助你更好地使用DAMO-YOLO。如果在使用过程中遇到问题,或者有新的需求,欢迎继续探索和优化这个API服务。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

345


被折叠的 条评论
为什么被折叠?



