这个毕业设计项目实现了一个基于深度学习的口罩佩戴检测系统,采用YOLOv3目标检测算法,能够准确识别图片或视频中人员是否规范佩戴口罩。在当前公共卫生安全备受关注的背景下,这类系统具有实际应用价值,也体现了AI技术解决现实问题的能力。
项目综合评分显示这是一个难度适中(3分)、工作量合理(3分)但创新性较好(4分)的毕业设计选题。系统核心功能包括:
提示:YOLO(You Only Look Once)是当前最流行的实时目标检测算法之一,其单阶段检测架构在速度和精度之间取得了良好平衡,特别适合毕业设计这类需要展示完整流程的项目。
YOLO系列算法的核心创新在于将目标检测重构为单阶段的回归问题。与传统RCNN系列的两阶段检测(先提候选框再分类)不同,YOLO直接在整张图片上预测边界框和类别概率。
关键特性包括:
在口罩检测场景中,这些特性尤为重要:
YOLOv3最重要的改进是引入了多尺度预测(Multi-Scale Detection):
对于口罩检测而言,多尺度机制特别关键:
python复制# YOLOv3模型定义中的多尺度处理部分
def forward(self, x):
# 获取三个尺度的特征图
features = []
for resnet_block in self.resnet_models:
x = resnet_block(x)
features.append(x)
# 多尺度预测
outputs = []
previous_upsampled = None
for idx, feature in enumerate(reversed(features)):
if previous_upsampled is not None:
feature = torch.cat([feature, previous_upsampled], dim=1)
hidden = self.yolo_detectors[idx][0](feature)
upsampled = self.yolo_detectors[idx][1](hidden)
output = self.yolo_detectors[idx][2](hidden)
previous_upsampled = upsampled
outputs.append(output)
return torch.cat([o.flatten(start_dim=2) for o in outputs], dim=2)
YOLOv3使用预定义的锚框来提升检测精度:
在本项目中,锚框设计考虑:
python复制# 锚框生成实现
@staticmethod
def _generate_anchors():
anchors = []
for span in [16, 32, 64]: # 三种尺度
for x in range(0, IMAGE_SIZE[0], span):
for y in range(0, IMAGE_SIZE[1], span):
# 两种长宽比:1:1和1.5:1.5
for ratio in [(1,1), (1.5,1.5)]:
w = span * ratio[0]
h = span * ratio[1]
anchors.append((
max(int(x + span/2 - w/2), 0),
max(int(y + span/2 - h/2), 0),
min(int(w), IMAGE_SIZE[0]),
min(int(h), IMAGE_SIZE[1])
))
return anchors
项目使用了两个数据源的组合数据集:
合并后共8535张图片,包含三类标注:
注意:由于"佩戴口罩不正确"的样本仅123个,数量太少难以有效学习,故将其合并到"佩戴口罩"类别。
为提高模型泛化能力,采用了以下增强方法:
python复制def resize_image(img):
"""保持长宽比的缩放并填充"""
sw, sh = img.size
# 计算新尺寸
if sw/sh < IMAGE_SIZE[0]/IMAGE_SIZE[1]:
new_w = int(IMAGE_SIZE[0]/IMAGE_SIZE[1] * sh)
pad_w = (new_w - sw) // 2
new_img = Image.new("RGB", (new_w, sh))
new_img.paste(img, (pad_w, 0))
else:
new_h = int(IMAGE_SIZE[1]/IMAGE_SIZE[0] * sw)
pad_h = (new_h - sh) // 2
new_img = Image.new("RGB", (sw, new_h))
new_img.paste(img, (0, pad_h))
return new_img.resize(IMAGE_SIZE)
将原始标注转换为YOLO训练格式:
python复制def prepare_annotations(xml_path):
tree = ET.parse(xml_path)
boxes = []
for obj in tree.findall("object"):
cls = obj.find("name").text
if cls == "mask_weared_incorrect":
cls = "with_mask"
x1 = int(obj.find("bndbox/xmin").text)
x2 = int(obj.find("bndbox/xmax").text)
y1 = int(obj.find("bndbox/ymin").text)
y2 = int(obj.find("bndbox/ymax").text)
boxes.append((x1, y1, x2-x1, y2-y1, CLASSES_MAPPING[cls]))
return boxes
YOLOv3使用多任务损失函数,包含三部分:
python复制def loss_function(predicted, actual):
result_tensor, pos_masks, neg_masks = actual
# 目标置信度损失
obj_loss_pos = F.mse_loss(predicted[pos_masks][:,0],
result_tensor[pos_masks][:,0])
obj_loss_neg = F.mse_loss(predicted[neg_masks][:,0],
result_tensor[neg_masks][:,0]) * 0.5
# 边界框回归损失
box_loss = F.mse_loss(predicted[pos_masks][:,1:5],
result_tensor[pos_masks][:,1:5])
# 分类损失
cls_loss_pos = F.binary_cross_entropy(predicted[pos_masks][:,5:],
result_tensor[pos_masks][:,5:])
cls_loss_neg = F.binary_cross_entropy(predicted[neg_masks][:,5:],
result_tensor[neg_masks][:,5:]) * 0.5
return (obj_loss_pos + obj_loss_neg +
box_loss + cls_loss_pos + cls_loss_neg)
关键训练配置:
训练过程中的关键观察点:
采用两种评估指标:
python复制@staticmethod
def calc_accuracy(actual, predicted):
result_tensor, pos_masks, neg_masks = actual
# 检测准确率
obj_acc_pos = ((result_tensor[pos_masks][:,0] > 0.5) &
(predicted[pos_masks][:,0] > 0.5)).float().mean()
obj_acc_neg = ((result_tensor[neg_masks][:,0] <= 0.5) &
(predicted[neg_masks][:,0] <= 0.5)).float().mean()
obj_acc = (obj_acc_pos + obj_acc_neg) / 2
# 分类准确率
cls_pred = predicted[:,5:].argmax(dim=1)
cls_true = result_tensor[:,5:].argmax(dim=1)
cls_acc = (cls_pred == cls_true).float().mean()
return obj_acc.item(), cls_acc.item()
完整的检测流程包括:
python复制def detect_image(model, image_path):
# 1. 预处理
img = Image.open(image_path)
img_tensor = image_to_tensor(resize_image(img))
# 2. 模型推理
with torch.no_grad():
outputs = model(img_tensor.unsqueeze(0).to(device))
# 3. 后处理
boxes = []
for anchor, pred in zip(MyModel.Anchors, outputs[0]):
if pred[0] < 0.5: # 置信度阈值
continue
box = adjust_box_by_offset(anchor, pred[1:5])
label = CLASSES[pred[5:].argmax()]
boxes.append((label, box, pred[0].item()))
# NMS处理
boxes = non_max_suppression(boxes)
# 4. 可视化
draw = ImageDraw.Draw(img)
for label, box, score in boxes:
x,y,w,h = map_box_to_original_image(box, *img.size)
draw.rectangle([x,y,x+w,y+h], outline="red", width=2)
draw.text((x,y-10), f"{label} {score:.2f}", fill="red")
return img
NMS算法流程:
python复制def non_max_suppression(boxes, iou_thresh=0.3):
# 按置信度降序排序
boxes.sort(key=lambda x: x[2], reverse=True)
keep = []
while boxes:
current = boxes.pop(0)
keep.append(current)
boxes = [
box for box in boxes
if calc_iou(current[1], box[1]) < iou_thresh
or current[0] != box[0] # 不同类别不抑制
]
return keep
视频检测通过逐帧处理实现:
python复制def detect_video(model, video_path, output_path=None):
cap = cv2.VideoCapture(video_path)
if output_path:
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, 30.0,
(int(cap.get(3)), int(cap.get(4))))
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
# 转换颜色空间并检测
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(rgb)
result = detect_image(model, img)
# 显示或保存结果
if output_path:
out.write(cv2.cvtColor(np.array(result), cv2.COLOR_RGB2BGR))
else:
cv2.imshow('Detection', cv2.cvtColor(np.array(result), cv2.COLOR_RGB2BGR))
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
if output_path:
out.release()
cv2.destroyAllWindows()
实际部署时可考虑以下优化:
项目可进一步扩展:
实际开发中遇到的典型问题:
训练过程中如果出现损失震荡,可以尝试:
这个项目完整展示了从算法选型、数据处理、模型训练到应用实现的完整流程,既有理论深度又具备实践价值,是一个非常适合本科毕业设计的AI应用案例。通过调整模型结构和训练策略,还可以进一步提升检测精度和速度,满足不同场景的需求。