import glob
import os
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
class AdaboostFaceDetector:
def __init__(self):
# 使用sklearn的决策树作为基础分类器
self.boost_classifier = None
@staticmethod
def extract_haar_features(image, bbox):
"""提取类Haar特征"""
x, y, w, h = bbox
# 确保边界框在图像范围内
height, width = image.shape
x = max(0, min(x, width - 1))
y = max(0, min(y, height - 1))
w = min(w, width - x)
h = min(h, height - y)
face_roi = image[y:y + h, x:x + w]
if face_roi.size == 0:
return np.zeros(100)
# 调整大小为固定尺寸
face_roi_resized = np.array(Image.fromarray(face_roi).resize((20, 20)))
# 计算类Haar特征(简化版本)
features = []
for i in range(5, 20, 5):
for j in range(5, 20, 5):
# 确保索引不越界
if i + 5 <= 20 and j + 5 <= 20:
# 水平和垂直方向的矩形特征
region1 = face_roi_resized[j - 5:j, i - 5:i]
region2 = face_roi_resized[j - 5:j, i:i + 5]
region3 = face_roi_resized[j:j + 5, i - 5:i]
h_feature = np.sum(region1) - np.sum(region2)
v_feature = np.sum(region1) - np.sum(region3)
features.extend([h_feature, v_feature])
# 如果特征数量不足,用0填充
while len(features) < 100:
features.append(0.0)
return np.array(features[:100])
def prepare_training_data(self, image_paths, annotations):
"""准备训练数据"""
features = []
labels = []
for img_path, annotation in zip(image_paths, annotations):
try:
# 使用PIL加载图像
image = np.array(Image.open(img_path).convert('L')) # 转换为灰度
except Exception as e:
print(f"无法加载图像 {img_path}: {e}")
continue
height, width = image.shape
# 使用真实标注作为正样本
for (x, y, w, h) in annotation:
feature = self.extract_haar_features(image, (x, y, w, h))
features.append(feature)
labels.append(1) # 正样本
# 生成负样本(非人脸区域)
for _ in range(5): # 每个图像生成5个负样本
x = np.random.randint(0, width - 50)
y = np.random.randint(0, height - 50)
w = np.random.randint(20, min(100, width - x))
h = np.random.randint(20, min(100, height - y))
# 确保不是真实的人脸区域
is_face = False
for fx, fy, fw, fh in annotation:
if abs(x - fx) < 50 and abs(y - fy) < 50:
is_face = True
break
if not is_face:
feature = self.extract_haar_features(image, (x, y, w, h))
features.append(feature)
labels.append(0) # 负样本
return np.array(features), np.array(labels)
def train(self, image_paths, annotations):
"""训练Adaboost分类器"""
print("准备训练数据...")
features, labels = self.prepare_training_data(image_paths, annotations)
if len(features) == 0:
print("未提取到有效特征,请检查数据路径")
return
print(f"训练数据形状: {features.shape}")
print(f"标签分布: {np.bincount(labels)}")
# 使用Adaboost分类器
self.boost_classifier = AdaBoostClassifier(
DecisionTreeClassifier(max_depth=3),
n_estimators=50,
learning_rate=1.0,
random_state=42
)
print("开始训练Adaboost分类器...")
self.boost_classifier.fit(features, labels)
print("训练完成!")
def detect_faces(self, image):
"""检测图像中的人脸"""
if self.boost_classifier is None:
print("请先训练分类器!")
return []
# 转换为灰度图像
if len(image.shape) == 3:
gray = np.array(Image.fromarray(image).convert('L'))
else:
gray = image
height, width = gray.shape
detected_faces = []
# 多尺度滑动窗口检测
scales = [0.5, 0.75, 1.0, 1.25, 1.5]
for scale in scales:
w_size = int(50 * scale)
h_size = int(50 * scale)
step_size = max(10, int(20 * scale)) # 动态步长
for y in range(0, height - h_size, step_size):
for x in range(0, width - w_size, step_size):
bbox = (x, y, w_size, h_size)
feature = self.extract_haar_features(gray, bbox)
# 使用Adaboost分类器预测
try:
prediction = self.boost_classifier.predict([feature])[0]
confidence = np.max(self.boost_classifier.predict_proba([feature]))
if prediction == 1 and confidence > 0.7:
detected_faces.append((x, y, w_size, h_size, confidence))
except Exception:
continue # 跳过预测失败的情况
# 非极大值抑制
return self.non_max_suppression(detected_faces)
def non_max_suppression(self, faces):
"""非极大值抑制"""
if len(faces) == 0:
return []
# 按置信度排序
faces_sorted = sorted(faces, key=lambda x: x[4], reverse=True)
keep = []
while faces_sorted:
current = faces_sorted.pop(0)
keep.append(current)
faces_sorted = [face for face in faces_sorted if self.iou(current[:4], face[:4]) < 0.3]
return keep
@staticmethod
def iou(box1, box2):
"""计算交并比"""
x1, y1, w1, h1 = box1
x2, y2, w2, h2 = box2
xi1 = max(x1, x2)
yi1 = max(y1, y2)
xi2 = min(x1 + w1, x2 + w2)
yi2 = min(y1 + h1, y2 + h2)
inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)
box1_area = w1 * h1
box2_area = w2 * h2
union_area = box1_area + box2_area - inter_area
return inter_area / union_area if union_area > 0 else 0
class TemplateMatchingDetector:
"""模板匹配人脸检测器"""
def __init__(self):
self.templates = self.generate_face_templates()
@staticmethod
def generate_face_templates():
"""生成简单的人脸模板"""
templates = []
# 创建椭圆模板(模拟人脸形状)
for size in [20, 30, 40]:
template = np.zeros((size, size))
center_x, center_y = size // 2, size // 2
radius_x, radius_y = size // 3, size // 2
for i in range(size):
for j in range(size):
if ((i - center_x) / radius_x) ** 2 + ((j - center_y) / radius_y) ** 2 <= 1:
template[i, j] = 1.0
templates.append(template)
return templates
def detect_faces(self, image):
"""使用模板匹配检测人脸"""
if len(image.shape) == 3:
gray = np.array(Image.fromarray(image).convert('L'))
else:
gray = image
detected_faces = []
height, width = gray.shape
# 归一化图像
gray_normalized = (gray - np.mean(gray)) / (np.std(gray) + 1e-8)
for template in self.templates:
t_height, t_width = template.shape
# 滑动窗口
for y in range(0, height - t_height, 10):
for x in range(0, width - t_width, 10):
# 提取窗口
window = gray_normalized[y:y + t_height, x:x + t_width]
if window.shape != template.shape:
continue
# 计算相关性
correlation = np.sum(window * template)
if correlation > 5.0: # 经验阈值
detected_faces.append((x, y, t_width, t_height, correlation))
# 非极大值抑制
return self.non_max_suppression(detected_faces)
@staticmethod
def non_max_suppression(faces):
"""非极大值抑制"""
if len(faces) == 0:
return []
faces_sorted = sorted(faces, key=lambda x: x[4], reverse=True)
keep = []
while faces_sorted:
current = faces_sorted.pop(0)
keep.append(current)
# 计算IoU并过滤重叠的检测框
faces_sorted = [face for face in faces_sorted if
TemplateMatchingDetector.iou(current[:4], face[:4]) < 0.3]
return keep
@staticmethod
def iou(box1, box2):
"""计算交并比"""
x1, y1, w1, h1 = box1
x2, y2, w2, h2 = box2
xi1 = max(x1, x2)
yi1 = max(y1, y2)
xi2 = min(x1 + w1, x2 + w2)
yi2 = min(y1 + h1, y2 + h2)
inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)
box1_area = w1 * h1
box2_area = w2 * h2
union_area = box1_area + box2_area - inter_area
return inter_area / union_area if union_area > 0 else 0
class SkinColorDetector:
"""基于肤色的人脸检测器"""
def __init__(self):
# 定义肤色范围(HSV空间)
self.lower_skin = np.array([0, 20, 70], dtype=np.uint8)
self.upper_skin = np.array([20, 255, 255], dtype=np.uint8)
def detect_faces(self, image):
"""使用肤色信息检测人脸"""
if len(image.shape) == 2:
# 如果是灰度图,转换为RGB
rgb_image = np.stack([image] * 3, axis=-1)
else:
rgb_image = image
# 转换为HSV颜色空间
hsv_image = np.array(Image.fromarray(rgb_image).convert('HSV'))
# 创建肤色掩码
skin_mask = np.zeros(hsv_image.shape[:2], dtype=np.uint8)
for i in range(3):
channel_mask = (hsv_image[:, :, 0] >= self.lower_skin[0]) & \
(hsv_image[:, :, 0] <= self.upper_skin[0]) & \
(hsv_image[:, :, 1] >= self.lower_skin[1]) & \
(hsv_image[:, :, 1] <= self.upper_skin[1]) & \
(hsv_image[:, :, 2] >= self.lower_skin[2]) & \
(hsv_image[:, :, 2] <= self.upper_skin[2])
skin_mask = skin_mask | channel_mask
# 寻找连通区域
from scipy import ndimage
labeled_array, num_features = ndimage.label(skin_mask)
detected_faces = []
for i in range(1, num_features + 1):
# 获取当前区域的坐标
points = np.where(labeled_array == i)
if len(points[0]) > 0:
y_min, y_max = np.min(points[0]), np.max(points[0])
x_min, x_max = np.min(points[1]), np.max(points[1])
w = x_max - x_min
h = y_max - y_min
# 过滤太小或太大的区域
if 20 < w < 200 and 20 < h < 200 and 0.5 < w / h < 2.0:
confidence = len(points[0]) / (w * h) # 密度作为置信度
detected_faces.append((x_min, y_min, w, h, confidence))
return detected_faces
class FaceDetectionEvaluator:
"""人脸检测评估器"""
def __init__(self):
self.detectors = {
'Adaboost': AdaboostFaceDetector(),
'Template_Matching': TemplateMatchingDetector(),
'Skin_Color': SkinColorDetector()
}
def load_wider_face_dataset(self, data_path, num_images=20):
"""加载WIDER FACE数据集(简化版本)"""
image_paths = []
annotations = []
# 假设数据目录结构
images_dir = os.path.join(data_path, 'images')
annotations_dir = os.path.join(data_path, 'annotations')
if os.path.exists(images_dir):
# 获取图像文件
image_files = glob.glob(os.path.join(images_dir, '*.jpg'))[:num_images]
image_files.extend(glob.glob(os.path.join(images_dir, '*.png'))[:num_images])
for img_file in image_files:
image_paths.append(img_file)
# 加载对应的标注文件
base_name = os.path.splitext(os.path.basename(img_file))[0]
ann_file = os.path.join(annotations_dir, base_name + '.txt')
if os.path.exists(ann_file):
annotations.append(self.load_annotation(ann_file))
else:
annotations.append([])
else:
# 生成模拟数据用于演示
print("使用模拟数据进行演示...")
for i in range(num_images):
# 创建模拟图像路径
image_paths.append(f'simulated_image_{i}.jpg')
# 生成随机的人脸标注
annotations.append(self.generate_random_annotations())
return image_paths, annotations
@staticmethod
def load_annotation(ann_file):
"""加载标注文件"""
annotations = []
try:
with open(ann_file, 'r') as f:
for line in f:
parts = line.strip().split()
if len(parts) >= 4:
x, y, w, h = map(int, parts[:4])
annotations.append((x, y, w, h))
except Exception as e:
print(f"无法加载标注文件 {ann_file}: {e}")
return annotations
@staticmethod
def generate_random_annotations():
"""生成随机的人脸标注(用于演示)"""
num_faces = np.random.randint(1, 5)
annotations = []
for _ in range(num_faces):
x = np.random.randint(0, 400)
y = np.random.randint(0, 400)
w = np.random.randint(50, 150)
h = np.random.randint(50, 150)
annotations.append((x, y, w, h))
return annotations
@staticmethod
def calculate_iou(box1, box2):
"""计算两个边界框的IoU"""
x1, y1, w1, h1 = box1
x2, y2, w2, h2 = box2
xi1 = max(x1, x2)
yi1 = max(y1, y2)
xi2 = min(x1 + w1, x2 + w2)
yi2 = min(y1 + h1, y2 + h2)
inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)
box1_area = w1 * h1
box2_area = w2 * h2
union_area = box1_area + box2_area - inter_area
return inter_area / union_area if union_area > 0 else 0
def evaluate_detector(self, detector, image_paths, ground_truths, iou_threshold=0.5):
"""评估检测器性能"""
all_precisions = []
all_recalls = []
for img_path, gt_boxes in zip(image_paths, ground_truths):
if not os.path.exists(img_path) and img_path.startswith('simulated'):
# 对于模拟图像,创建一个随机图像
image = np.random.randint(0, 255, (500, 500, 3), dtype=np.uint8)
else:
try:
image = np.array(Image.open(img_path))
except Exception as e:
print(f"无法加载图像 {img_path}: {e}")
continue
# 检测人脸
detected_faces = detector.detect_faces(image)
# 计算精度和召回率
if len(gt_boxes) == 0 and len(detected_faces) == 0:
precision = 1.0
recall = 1.0
elif len(gt_boxes) == 0:
precision = 0.0
recall = 0.0
elif len(detected_faces) == 0:
precision = 0.0
recall = 0.0
else:
# 匹配检测结果和真实标注
true_positives = 0
used_gt = set()
for det_face in detected_faces:
det_box = det_face[:4]
max_iou = 0
best_gt_idx = -1
for i, gt_box in enumerate(gt_boxes):
if i in used_gt:
continue
iou_val = self.calculate_iou(det_box, gt_box)
if iou_val > max_iou:
max_iou = iou_val
best_gt_idx = i
if max_iou >= iou_threshold and best_gt_idx != -1:
true_positives += 1
used_gt.add(best_gt_idx)
precision = true_positives / len(detected_faces) if len(detected_faces) > 0 else 0
recall = true_positives / len(gt_boxes) if len(gt_boxes) > 0 else 0
all_precisions.append(precision)
all_recalls.append(recall)
# 计算平均精度
mean_precision = np.mean(all_precisions)
mean_recall = np.mean(all_recalls)
ap_score = np.mean(all_precisions) # 简化的AP计算
return mean_precision, mean_recall, ap_score, all_precisions, all_recalls
@staticmethod
def plot_pr_curves(results):
"""绘制PR曲线"""
plt.figure(figsize=(10, 8))
for detector_name, result in results.items():
precisions = result['precisions']
recalls = result['recalls']
# 排序以便绘制PR曲线
sorted_indices = np.argsort(recalls)
sorted_recalls = np.array(recalls)[sorted_indices]
sorted_precisions = np.array(precisions)[sorted_indices]
plt.plot(sorted_recalls, sorted_precisions, marker='.',
label=f'{detector_name} (AP={result["ap"]:.3f})')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve - Face Detection Algorithms')
plt.legend()
plt.grid(True)
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.savefig('pr_curve_comparison.png', dpi=300, bbox_inches='tight')
plt.show()
def visualize_detections(self, image_paths, ground_truths, max_images=5):
"""可视化检测结果"""
num_detectors = len(self.detectors)
fig, axes = plt.subplots(num_detectors, max_images,
figsize=(15, 3 * num_detectors))
if num_detectors == 1:
axes = [axes]
for i, (detector_name, detector) in enumerate(self.detectors.items()):
for j in range(min(max_images, len(image_paths))):
img_path = image_paths[j]
gt_boxes = ground_truths[j]
if not os.path.exists(img_path) and img_path.startswith('simulated'):
# 创建模拟图像
image = np.random.randint(0, 255, (500, 500, 3), dtype=np.uint8)
else:
try:
image = np.array(Image.open(img_path))
except Exception:
continue
# 检测人脸
detected_faces = detector.detect_faces(image)
# 绘制图像
if num_detectors > 1:
ax = axes[i][j]
else:
ax = axes[j]
ax.imshow(image)
# 绘制真实标注(绿色)
for x, y, w, h in gt_boxes:
rect = plt.Rectangle((x, y), w, h, fill=False,
edgecolor='g', linewidth=2, label='Ground Truth')
ax.add_patch(rect)
# 绘制检测结果(红色)
for x, y, w, h, confidence in detected_faces:
rect = plt.Rectangle((x, y), w, h, fill=False,
edgecolor='r', linewidth=2, label='Detection')
ax.add_patch(rect)
ax.set_title(f'{detector_name}\nGT: {len(gt_boxes)}, Det: {len(detected_faces)}')
ax.axis('off')
# 只在第一列添加标签
if num_detectors > 1:
axes[i][0].set_ylabel(detector_name, rotation=90, size='large')
plt.tight_layout()
plt.savefig('detection_visualization.png', dpi=300, bbox_inches='tight')
plt.show()
def main():
"""主函数"""
print("Lab_1: 基于Adaboost组合分类器的人脸检测")
# 初始化评估器
evaluator = FaceDetectionEvaluator()
# 加载数据
print("加载WIDER FACE数据集...")
data_path = './WIDER_FACE' # 修正拼写
image_paths, annotations = evaluator.load_wider_face_dataset(data_path, num_images=20)
print(f"成功加载 {len(image_paths)} 张图像")
# 训练Adaboost分类器
print("\n训练Adaboost分类器...")
adaboost_detector = evaluator.detectors['Adaboost']
adaboost_detector.train(image_paths, annotations)
# 评估所有检测器
print("\n评估人脸检测算法...")
results = {}
for detector_name, detector in evaluator.detectors.items():
print(f"\n评估 {detector_name}...")
precision, recall, ap, precisions, recalls = evaluator.evaluate_detector(
detector, image_paths, annotations
)
results[detector_name] = {
'precision': precision,
'recall': recall,
'ap': ap,
'precisions': precisions,
'recalls': recalls
}
print(f"{detector_name} 结果:")
print(f" 平均精度: {precision:.3f}")
print(f" 平均召回率: {recall:.3f}")
print(f" AP: {ap:.3f}")
# 绘制PR曲线
print("\n绘制PR曲线...")
evaluator.plot_pr_curves(results)
# 可视化检测结果
print("\n可视化检测结果...")
evaluator.visualize_detections(image_paths, annotations, max_images=5)
# 输出优化建议
print_optimization_suggestions(results)
def print_optimization_suggestions(results):
"""输出优化建议"""
print("\n" + "=" * 50)
print("优化和方法改进思路")
print("=" * 50)
print("\n1. 基于评估结果的改进方向:")
best_ap = max(results.values(), key=lambda x: x['ap'])
best_detector = [name for name, res in results.items() if res['ap'] == best_ap['ap']][0]
print(f" 当前最佳检测器: {best_detector} (AP: {best_ap['ap']:.3f})")
for name, res in results.items():
if name != best_detector:
improvement = best_ap['ap'] - res['ap']
print(f" {name} 需要提升 {improvement:.3f} 来达到最佳性能")
print("\n2. 技术优化建议:")
print(" a. 特征提取优化:")
print(" - 实现更复杂的Haar-like特征")
print(" - 添加LBP特征或HOG特征")
print(" b. 分类器优化:")
print(" - 调整Adaboost参数(学习率、弱分类器数量)")
print(" - 尝试不同的基础分类器")
print(" c. 检测策略优化:")
print(" - 实现更高效的多尺度检测")
print(" - 改进非极大值抑制算法")
print(" d. 数据增强:")
print(" - 增加旋转、缩放、亮度变换")
print(" - 生成更多困难负样本")
print("\n3. 扩展功能:")
print(" - 添加人脸关键点检测")
print(" - 实现人脸识别功能")
print(" - 添加实时视频检测功能")
if __name__ == "__main__":
main()上述代码中读取WIDER FACE数据集的片段是哪一个
最新发布