去年接手了一个射击训练馆的智能化改造项目,客户需要一套能够自动识别靶纸上弹着点位置并计算环数的管理系统。这个看似简单的需求背后,实际上涉及了前端交互、后端逻辑和AI模型开发三个技术领域的深度整合。经过两个月的开发迭代,最终交付的系统实现了以下核心功能:
这个项目最有趣的地方在于,它完美展示了如何将深度学习技术落地到传统行业的具体场景中。下面我就从技术选型、开发过程和实战经验三个维度,详细拆解这个项目的实现细节。
考虑到项目预算和开发效率,最终确定的技术方案如下:
前端层:
后端服务:
AI模型层:
这个架构的特别之处在于:
提示:在小规模AI应用中,将模型直接集成到Web框架中可以避免复杂的服务化部署,特别适合初期验证阶段。
主要实体关系如下表所示:
| 表名 | 关键字段 | 关联关系 |
|---|---|---|
| shooter | id, name, level | 一对多score |
| device | id, location, status | 一对多image |
| image | id, path, upload_time | 多对一device |
| score | id, points, position | 多对一shooter |
使用Django ORM定义模型的技巧:
python复制class Score(models.Model):
shooter = models.ForeignKey(Shooter, on_delete=models.CASCADE)
session = models.ForeignKey(TrainingSession, on_delete=models.CASCADE)
x_coord = models.FloatField() # 弹孔X坐标
y_coord = models.FloatField() # 弹孔Y坐标
ring_value = models.IntegerField() # 环数值
class Meta:
indexes = [
models.Index(fields=['shooter', 'session']),
]
射击结果的采集界面需要解决两个关键问题:
核心JavaScript代码结构:
javascript复制// 初始化摄像头
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
const video = $('#camera-view')[0];
video.srcObject = stream;
// 自动拍照定时器
setInterval(() => {
if (autoMode) captureFrame();
}, 3000);
});
function captureFrame() {
const canvas = $('#snapshot')[0];
const ctx = canvas.getContext('2d');
ctx.drawImage($('#camera-view')[0], 0, 0, 640, 480);
// 发送到后端识别
const imageData = canvas.toDataURL('image/jpeg');
$.post('/api/recognize', { image: imageData }, handleResult);
}
由于真实射击数据获取困难,我们采用合成数据+真实标注的方案:
python复制def generate_target(base_img):
img = cv2.imread(base_img)
# 添加同心圆
for r in range(10, 0, -1):
cv2.circle(img, (320,320), r*30, (0,0,255), 2)
return img
python复制def add_bullet_hole(img):
# 极坐标随机生成位置
angle = random.uniform(0, 2*math.pi)
distance = random.gauss(0, 100) # 正态分布
x = int(320 + distance * math.cos(angle))
y = int(320 + distance * math.sin(angle))
# 绘制弹孔
cv2.circle(img, (x,y), 15, (0,255,0), -1)
return img, (x,y)
经过多次调优后的最终模型结构:
python复制class TargetNet(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Dropout(0.25)
)
self.regressor = nn.Sequential(
nn.Linear(64*160*160, 128),
nn.ReLU(),
nn.Linear(128, 2) # 输出x,y坐标
)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
return self.regressor(x)
关键训练参数:
在Django中安全加载模型的正确方式:
python复制# models.py
import torch
from django.conf import settings
class RecognitionService:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
cls._instance.model = torch.load(
settings.MODEL_PATH,
map_location='cpu'
)
cls._instance.model.eval()
return cls._instance
# views.py
def recognize(request):
image = preprocess_image(request.FILES['image'])
with torch.no_grad():
coords = RecognitionService().model(image)
return JsonResponse({'x': coords[0], 'y': coords[1]})
python复制# 使用OpenCV DNN模块加速
def preprocess_image(image):
blob = cv2.dnn.blobFromImage(
image,
scalefactor=1/255.0,
size=(640, 480),
mean=[0.485, 0.456, 0.406],
swapRB=True
)
return torch.from_numpy(blob).float()
python复制# 糟糕的做法
for score in scores:
Score.objects.create(**score)
# 优化后的做法
Score.objects.bulk_create([
Score(**data) for data in scores
])
实际部署时发现,不同角度的摄像头拍摄会导致靶纸圆形变形。解决方案是增加透视变换预处理:
python复制def correct_perspective(img):
# 检测靶纸外轮廓
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 1000,
param1=50, param2=30, minRadius=200, maxRadius=300)
# 计算变换矩阵
src_points = get_circle_points(circles)
dst_points = np.array([[0,0], [640,0], [640,640], [0,640]], dtype='float32')
M = cv2.getPerspectiveTransform(src_points, dst_points)
return cv2.warpPerspective(img, M, (640, 640))
初期模型在真实场景准确率只有70%,通过以下改进提升到92%:
python复制# 加载预训练权重
pretrained = torchvision.models.resnet18(pretrained=True)
model.features[0] = pretrained.conv1
model.features[3] = pretrained.layer1
当前系统已经稳定运行半年,后续计划从三个方向进行升级:
在开发过程中最大的体会是:AI项目的成功不仅取决于模型精度,更需要考虑整个系统的工程实现。比如我们最初花费大量时间提升模型准确率,后来发现图像预处理的质量对最终结果的影响更大。这也印证了业界常说的"Garbage in, garbage out"原则。