公司动态
opencv学习中——Caffe人脸检测算法原理
原先在学Haar级联法识别人脸发现效果很差可能是因为识别的是婴儿加上不是正脸大模型推荐了Caffe调用之后效果很好1 概念1.1.1 基础概念1算法、框架与文件的三个概念2OpenCV DNN模块支持多种框架的模型格式cv2.dnn.readNetFromCaffe(prototxt, caffemodel) # ← Caffe 格式 cv2.dnn.readNetFromTensorflow(pb, pbtxt) # ← TensorFlow 格式 cv2.dnn.readNetFromONNX(onnx_path) # ← ONNX 通用格式 cv2.dnn.readNetFromDarknet(cfg, weights) # ← YOLO/Darknet 格式 cv2.dnn.readNetFromTorch(t7_path) # ← Torch 格式3OpenCV DNN 模块最早就是为Caffe设计的所以大量经典 CV 模型都以 Caffe 格式分发形成了事实标准。即使今天 PyTorch 已经是训练主流推理部署时仍然经常转成 Caffe/ONNX 格式。4当我们说一个模型时完整的描述应该包含三层信息1.1.2 推理流程原始图片 → resize到300×300 → 均值减法 → 送入网络 → 前向传播 → 输出检测结果 → NMS去重 → 画框1.1.2.1 resize到300×3001这个 SSD 模型在训练时就固定了输入尺寸为 300×300。看下图 deploy.prototxt结构文件的第一行定义。网络内部所有层的权重矩阵形状都是基于 300×300 计算出来的。2300 是原论文作者实验后的结果对人脸这种中等大小目标足够用同时 CPU 上也能实时跑3resize会导致图片变形但 SSD训练时就是这么做的——所有训练图片都被暴力 resize 到 300×300。网络已经学会了在这种变形下依然检测人脸。推理时保持一致就行。有些改进版 SSD 会用 letterbox padding等比缩放填充黑边来避免变形但这个经典版本没有。1.1.2.2 均值减法1居然真的就是每个像素点减去一个固定常数。它们是ImageNet 数据集120 万张自然图片上所有图片的 BGR 通道平均值。这个人脸检测模型的 G 通道均值是177而不是标准的 117说明它是在特定人脸数据集上重新统计的均值。必须用模型配套的均值不能随便换。2原因有三个数据中心化神经网络本质是做矩阵乘法 非线性激活让网络别操心图片有多亮专心去认哪里像脸训练和推理必须一致1.1.2.3 送入网络1指的是ResNet-10 SSD网络。ResNet-10 有10层网络负责提取特征同时残差Res可以将前面几层信息一起叠加传递到当层称为快捷连接逐段跳跃每一层收到的是“前一个跳跃起点的输出 当前层的输出”。2SSD则是在图片上提前撒好各种大小与比例小框测小脸大框测大脸横框测侧脸等的框锚框将在整张图上找脸变成了调整几百个预设框速度极快。锚框本身不找脸网络对锚框做微调才找到脸。3ResNet-10 是看得清提取特征且不丢细节SSD 是找得快用预设框同时检测各种大小的脸。两者配合 又准又快地找到人脸。4这块的详细原理后面学到深度学习要再补充一下目前只是浅浅的了解。1.1.2.4 前向传播1一层层传递计算结果。先是卷积层内卷积核在输入图上与每个通道上的每个位置的像素值进行点积得到的标量值组成新的不同通道的特征图2之后BatchNorm 层对每个通道的特征图做归一化3然后引入非线性的激活函数ReLu负值为0正值保持不变4再到池化层比如2×2的池化窗口步长2滑动取最大值或平均值进行降采样减少计算量5最后进入DetectionOutput层合并特征图这个项目只有3层一遍通用的SSD有6层检测结果解码锚框偏移量NMS去除重叠框。骨干网络太浅 ResNet-10 总共才 10 层有效语义特征层只有 conv3、conv4、conv5 三个阶段性输出再往后就没有足够深度的特征了。人脸检测的特殊性 人脸是刚性物体尺度变化相对有限不像通用目标检测需要覆盖从猫到汽车再到人的巨大跨度3 个尺度对人脸已经够用。速度优先 Caffe 部署的人脸检测模型追求实时性减少检测层数直接降低计算量。1.1.2.5 输出检测结果1原始输出detections.shape (1, 1, N, 7)。N通常是200由prototxt中keep_top_k参数决定。即使图中只有1张脸也会输出200个候选框其中199个置信度接近0。第 1 个1 batch size推理时通常为1第 2 个1 固定占位符Caffe SSD 的历史设计这两维永远是 1没有实际意义所以开发者习惯直接用[0, 0]把它们剥掉拿到干净的(N, 7)矩阵方便后续处理。2‘7’是检测7个字段。1.1.2.6 NMS去重11张脸可能被好几个框覆盖需要最好的那一个NMS就是用来去重的。2NMS依赖IoU来衡量两个框的重叠程度。3具体算法步骤4局限性5改进方案Soft-NMS、DIoU-NMS、Adaptive-NMS 等但都不在这个经典模型中使用。回头可以试下这些方案。1.1.2.7 画框2 代码import cv2 import numpy as np import os # 路径配置 pic_dir rC:\D\picture img_path os.path.join(pic_dir, alex-005.jpg) proto_path os.path.join(pic_dir, deploy.prototxt) model_path os.path.join(pic_dir, res10_300x300_ssd_iter_140000.caffemodel) # 第一步加载 DNN 模型 net cv2.dnn.readNetFromCaffe(proto_path, model_path) print(Caffe模型加载成功) # 第二步读取图片 img cv2.imread(img_path) if img is None: raise IOError(f图片读取失败: {img_path}) h, w img.shape[:2] print(f图片尺寸: {w} x {h}) # 第三步预处理 # 将图片缩放到 300x300并做均值减法Caffe 模型要求的预处理 blob cv2.dnn.blobFromImage( cv2.resize(img, (300, 300)), # 模型输入尺寸固定 300x300 scalefactor1.0, size(300, 300), mean(104.0, 177.0, 123.0), # BGR 均值减法 swapRBFalse, cropFalse ) # 第四步前向推理 net.setInput(blob) detections net.forward() # detections shape: (1, 1, N, 7) # 每个检测: [batch_id, class_id, confidence, x1, y1, x2, y2] # 第五步解析结果并画框 confidence_threshold 0.5 # 置信度阈值可根据需要调整 face_count 0 for i in range(detections.shape[2]): confidence detections[0, 0, i, 2] if confidence confidence_threshold: face_count 1 # 坐标是归一化的 [0,1]需要映射回原图尺寸 box detections[0, 0, i, 3:7] * np.array([w, h, w, h]) (x1, y1, x2, y2) box.astype(int) # 画绿色矩形框 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 20) # 在框上方标注置信度 label f{confidence:.2f} label_size, _ cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 1) cv2.rectangle(img, (x1, y1 - label_size[1] - 6), (x1 label_size[0], y1), (0, 255, 0), -1) cv2.putText(img, label, (x1, y1 - 4), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1) print(f 人脸 #{face_count}: 置信度{confidence:.3f}, 位置({x1},{y1})-({x2},{y2})) print(f共检测到 {face_count} 张人脸) # 第六步显示结果 cv2.namedWindow(Face Detection (DNN), cv2.WINDOW_NORMAL) cv2.resizeWindow(Face Detection (DNN), 550, 850) while True: cv2.imshow(Face Detection (DNN), img) key cv2.waitKey(1) 0xFF if key ord(q): break cv2.destroyAllWindows() # 可选保存结果 # output_path os.path.join(pic_dir, result_dnn.jpg) # cv2.imwrite(output_path, img) # print(f结果已保存: {output_path})3 感想1最近蛮焦虑的和大模型聊天发现计算机视觉这块要学的还有好多去力扣上刷题刷的也好艰难。不过好在每天都在学一点也在坚持记录面试前还能临时抱下佛脚~