2. 基于HOG的人脸检测

任务说明

本案例使用方向梯度直方图(HOG)特征与线性SVM分类器,构建一个能够区分“人脸”与“非人脸”图像块的二分类检测器。训练完成后,在包含多张人脸的测试图像上,通过滑动窗口策略检测人脸位置,并以边界框形式输出检测结果。最终通过检测准确率与误检数量评估模型效果。

数据集准备

使用公开的olivetti_faces数据集(40人,每人10张,共400张64×64灰度人脸图)作为正样本。负样本从lfw_people数据集的非人脸区域随机裁剪生成,数量与正样本相同(400张)。所有样本统一缩放为64×64像素。

实现代码

import cv2
import numpy as np
from sklearn.svm import LinearSVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from skimage.feature import hog
from sklearn.datasets import fetch_olivetti_faces, fetch_lfw_people

# 1. 加载正样本(人脸)
faces = fetch_olivetti_faces(shuffle=True, random_state=42)
X_pos = faces.images  # 400张 64x64 人脸
y_pos = np.ones(len(X_pos))

# 2. 生成负样本(非人脸)
lfw = fetch_lfw_people(min_faces_per_person=100, resize=0.4, color=False)
# 从LFW图像中随机裁剪非人脸区域(避开中心区域)
rng = np.random.RandomState(42)
X_neg = []
target_size = 64
for img in lfw.images:
    h, w = img.shape
    if h < target_size or w < target_size:
        continue
    # 随机裁剪5个区域,取中心区域外(避免人脸)
    for _ in range(5):
        y = rng.randint(0, h - target_size)
        x = rng.randint(0, w - target_size)
        crop = img[y:y+target_size, x:x+target_size]
        # 简单启发式:若中心区域方差低(平坦),视为非人脸
        if crop.std() < 0.1:
            X_neg.append(crop)
            if len(X_neg) >= 400:
                break
    if len(X_neg) >= 400:
        break
X_neg = np.array(X_neg[:400])
y_neg = np.zeros(len(X_neg))

# 3. 合并数据集并提取HOG特征
X_all = np.vstack([X_pos, X_neg])
y_all = np.hstack([y_pos, y_neg])

def extract_hog_features(images):
    features = []
    for img in images:
        # 像素值归一化到[0,1]后转为uint8(HOG要求)
        img_uint8 = (img * 255).astype(np.uint8)
        fd = hog(img_uint8, orientations=9, pixels_per_cell=(8, 8),
                 cells_per_block=(2, 2), block_norm='L2-Hys')
        features.append(fd)
    return np.array(features)

print("提取HOG特征中...")
X_hog = extract_hog_features(X_all)
print(f"特征维度: {X_hog.shape[1]}")

# 4. 划分训练/测试集并训练SVM
X_train, X_test, y_train, y_test = train_test_split(
    X_hog, y_all, test_size=0.2, random_state=42, stratify=y_all)

svm = LinearSVC(C=1.0, max_iter=5000, random_state=42)
svm.fit(X_train, y_train)

# 5. 评估分类器
y_pred = svm.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"分类准确率: {acc:.4f}")

# 6. 滑动窗口检测函数
def sliding_window_detect(image, svm_model, window_size=(64, 64), stride=8, scale=1.0):
    detections = []
    img = image.copy()
    if scale != 1.0:
        img = cv2.resize(img, None, fx=scale, fy=scale, interpolation=cv2.INTER_LINEAR)
    h, w = img.shape
    win_h, win_w = window_size
    for y in range(0, h - win_h + 1, stride):
        for x in range(0, w - win_w + 1, stride):
            window = img[y:y+win_h, x:x+win_w]
            # 提取HOG特征并预测
            fd = hog(window, orientations=9, pixels_per_cell=(8, 8),
                     cells_per_block=(2, 2), block_norm='L2-Hys')
            fd = fd.reshape(1, -1)
            pred = svm_model.predict(fd)[0]
            if pred == 1:
                # 保存原始坐标(考虑缩放)
                orig_x = int(x / scale)
                orig_y = int(y / scale)
                orig_w = int(win_w / scale)
                orig_h = int(win_h / scale)
                detections.append((orig_x, orig_y, orig_w, orig_h))
    return detections

# 7. 在测试图像上检测人脸
def draw_detections(image, detections):
    img_color = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    for (x, y, w, h) in detections:
        cv2.rectangle(img_color, (x, y), (x+w, y+h), (0, 255, 0), 2)
    return img_color

# 使用LFW中一张含多人脸图像作为测试(取第一张)
test_img = lfw.images[0]
# 确保图像足够大
if test_img.shape[0] > 128 and test_img.shape[1] > 128:
    # 多尺度检测
    all_dets = []
    for sc in [1.0, 0.8, 0.6]:
        dets = sliding_window_detect(test_img, svm, scale=sc)
        all_dets.extend(dets)
    # 简单非极大值抑制(NMS)
    def nms(dets, thresh=0.3):
        if len(dets) == 0:
            return []
        dets = np.array(dets, dtype=np.float32)
        x1 = dets[:, 0]; y1 = dets[:, 1]
        x2 = dets[:, 0] + dets[:, 2]; y2 = dets[:, 1] + dets[:, 3]
        areas = (x2 - x1 + 1) * (y2 - y1 + 1)
        order = areas.argsort()[::-1]
        keep = []
        while order.size > 0:
            i = order[0]
            keep.append(i)
            xx1 = np.maximum(x1[i], x1[order[1:]])
            yy1 = np.maximum(y1[i], y1[order[1:]])
            xx2 = np.minimum(x2[i], x2[order[1:]])
            yy2 = np.minimum(y2[i], y2[order[1:]])
            w = np.maximum(0.0, xx2 - xx1 + 1)
            h = np.maximum(0.0, yy2 - yy1 + 1)
            inter = w * h
            ovr = inter / (areas[i] + areas[order[1:]] - inter)
            inds = np.where(ovr <= thresh)[0]
            order = order[inds + 1]
        return dets[keep].astype(int)

    final_dets = nms(all_dets, thresh=0.3)
    result_img = draw_detections(test_img, final_dets)
    print(f"检测到 {len(final_dets)} 个人脸区域")
    # 保存结果(实际运行时取消注释)
    # cv2.imwrite('detection_result.jpg', result_img)
else:
    print("测试图像尺寸过小,跳过检测")
    final_dets = []

验证方法

运行上述代码后,观察以下输出以判定案例是否成功:

  • 分类准确率:训练集与测试集划分后,SVM在测试集上的准确率应高于0.9(预期约0.95),表明HOG特征能有效区分人脸与非人脸。
  • 检测结果:程序输出检测到的人脸数量。若测试图像包含已知人脸,检测框应大致覆盖面部区域。可通过保存的detection_result.jpg目视检查。
  • 误检控制:非人脸区域(如背景、衣物)不应出现过多绿色框。若误检严重,可调整SVM的C参数或增大负样本数量。
HOG人脸检测器训练与检测流程 正样本 Olivetti 400张人脸 负样本 LFW裁剪400张非人脸 HOG特征提取 9方向, 8×8 cell 线性SVM训练 C=1.0, 二分类 训练好的模型 SVM权重+偏置 滑动窗口扫描 64×64窗口, stride=8, 多尺度 NMS去重 重叠框合并 检测结果 边界框 评估指标 分类准确率 > 0.9 | 检测框覆盖人脸 | 误检数量少

判定标准

  • 通过:分类准确率≥0.90,且测试图像中检测到的人脸数量与图像中实际人脸数量基本一致(误差≤1),无明显误检。
  • 不通过:准确率低于0.85,或检测结果大量漏检/误检(超过实际人脸数2倍以上)。

若准确率不达标,可尝试增加负样本数量、调整HOG参数(如pixels_per_cell改为(6,6))或增大SVM的C值。若检测阶段误检过多,可提高SVM决策阈值或增加NMS的IoU阈值。