目录
拓展版效果演示
风格变换
🎯 普通版效果演示:
-
实时检测人脸
-
对人脸区域进行像素化处理
-
其他部分不变,像戴了一个像素面具
pixel_size = 6
pixel_size = 10
pixel_size = 20
pixel_size = 50
代码分析
一、项目背景与目标
在计算机视觉与图像处理的学习过程中,实时视频处理是非常有趣而实用的一个应用场景。本项目旨在利用 OpenCV 实现一个基于摄像头的实时人脸检测系统,并对检测到的人脸进行像素化处理,模拟出类似复古游戏中的像素人物效果。同时,系统提供实时 FPS(帧率)显示,用于监测系统性能。
二、关键技术与模块划分
本项目主要包括以下核心模块:
-
摄像头视频流读取
-
人脸检测(使用 Haar 特征分类器,在之前的文章中有详细的说过底层原理:人脸识别鼻祖!!——级联分类器(Cascade Classifier)详解_haar级联分类器(cv2.cascadeclassifier)-CSDN博客)
-
图像像素化处理
-
网格线叠加(增强像素感)
-
FPS 实时统计与显示
三、代码实现思路
1. 初始化模块
import cv2
import numpy as np
import time
引入所需模块:
-
cv2
是 OpenCV 的核心模块; -
numpy
用于图像矩阵处理; -
time
用于帧率统计。
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
cap = cv2.VideoCapture(0)
pixel_size = 10
-
加载人脸检测器(Haar Cascade);
-
打开默认摄像头;
-
设置像素块大小
pixel_size
。
2. FPS 统计初始化
prev_time = time.time()
frame_count = 0
fps = 0
使用时间差计算当前帧率:记录上一秒时间、当前帧计数。
3. 主循环结构
while True:
ret, frame = cap.read()
if not ret:
break
-
读取摄像头一帧图像;
-
如果读取失败,则终止程序。
4. 图像预处理与人脸检测
frame = cv2.flip(frame, 1)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
-
将图像进行镜像处理(更符合用户习惯);
-
转换为灰度图像,提高检测速度;
-
使用多尺度人脸检测方法,返回所有人脸位置。
5. 人脸像素化与网格叠加
for (x, y, w, h) in faces:
face = frame[y:y+h, x:x+w]
small = cv2.resize(face, (w // pixel_size, h // pixel_size), interpolation=cv2.INTER_LINEAR)
pixel_face = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
for i in range(0, h, pixel_size):
cv2.line(pixel_face, (0, i), (w, i), (0, 0, 0), 1)
for j in range(0, w, pixel_size):
cv2.line(pixel_face, (j, 0), (j, h), (0, 0, 0), 1)
frame[y:y+h, x:x+w] = pixel_face
-
提取人脸区域;
-
缩小再放大实现像素风格;
-
使用
cv2.line()
添加黑色网格线; -
将像素化图像替换回原图。
6. 实时帧率统计与显示
frame_count += 1
current_time = time.time()
elapsed_time = current_time - prev_time
if elapsed_time >= 1.0:
fps = frame_count / elapsed_time
frame_count = 0
prev_time = current_time
-
每一帧累加计数;
-
每秒更新一次 FPS 值,保证数字稳定;
cv2.putText(frame, f"FPS: {fps:.2f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
-
将 FPS 以绿色字体叠加到图像左上角。
7. 显示图像与结束控制
cv2.imshow("像素脸 with FPS", frame)
if cv2.waitKey(1) == 27: # ESC 键
break
cap.release()
cv2.destroyAllWindows()
-
显示处理后的视频流;
-
当用户按下 ESC 键(ASCII码27)时退出;
-
最后释放资源,关闭窗口。
四、运行效果展示
运行后,系统将:
-
实时捕捉用户的脸部区域;
-
将人脸转换为像素块风格,叠加网格线;
-
实时在左上角显示当前 FPS 值;
-
用户按下 ESC 键后退出程序。
你们最喜欢的部分来啦——整体代码
import cv2
import numpy as np
import time # 用于计算时间,进而计算 FPS
# 加载人脸检测模型
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
# 打开摄像头
cap = cv2.VideoCapture(0)
# 越小像素越小,像素块越多
pixel_size = 10
# 初始化时间和帧数,用于 FPS 计算
prev_time = time.time()
frame_count = 0
fps = 0
while True:
ret, frame = cap.read()
if not ret:
break
frame = cv2.flip(frame, 1) # 镜像翻转
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 转为灰度图像
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
for (x, y, w, h) in faces:
face = frame[y:y+h, x:x+w]
# 缩小再放大,像素化
small = cv2.resize(face, (w // pixel_size, h // pixel_size), interpolation=cv2.INTER_LINEAR)
pixel_face = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
# 添加像素网格线
for i in range(0, h, pixel_size):
cv2.line(pixel_face, (0, i), (w, i), (0, 0, 0), 1)
for j in range(0, w, pixel_size):
cv2.line(pixel_face, (j, 0), (j, h), (0, 0, 0), 1)
frame[y:y+h, x:x+w] = pixel_face
# ======================
# FPS 计算与显示部分👇
# ======================
frame_count += 1
current_time = time.time()
elapsed_time = current_time - prev_time
if elapsed_time >= 1.0: # 每秒更新一次 FPS 显示
fps = frame_count / elapsed_time
frame_count = 0
prev_time = current_time
# 在左上角绘制 FPS 文本
cv2.putText(frame, f"FPS: {fps:.2f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
cv2.imshow("像素脸 with FPS", frame)
if cv2.waitKey(1) == 27: # ESC 退出
break
cap.release()
cv2.destroyAllWindows()
拓展方向
-
支持视频录制、截图;
-
增加像素风特效(例如低饱和度、复古色调);
-
用 DNN 替换 Haar,提升检测效果;
-
制作“像素化 GIF 表情包”功能;
-
添加语音播报或趣味提示文字。
方向2拓展
完整代码
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
# 特效模式设置(保持三通道输出)
def apply_effect(frame, mode):
# 确保输入为三通道
if len(frame.shape) == 2:
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
if mode == 'normal':
return frame
elif mode == 'desaturate': # 灰度模式
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
return cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
elif mode == 'sepia': # 复古色调
sepia_filter = np.array([
[0.393, 0.769, 0.189],
[0.349, 0.686, 0.168],
[0.272, 0.534, 0.131]
])
sepia_img = cv2.transform(frame, sepia_filter)
return np.clip(sepia_img, 0, 255).astype(np.uint8)
elif mode == 'invert': # 反色
return cv2.bitwise_not(frame)
elif mode == 'redblue': # 红蓝分离
frame_rb = frame.copy()
frame_rb[:, :, 1] = 0 # 去除绿色通道
return frame_rb
elif mode == 'cartoon': # 卡通效果
# 转换为灰度并模糊
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.medianBlur(gray, 5)
# 边缘检测
edges = cv2.adaptiveThreshold(gray, 255,
cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY, 9, 9)
# 颜色处理
color = cv2.bilateralFilter(frame, 9, 300, 300)
# 合并效果
cartoon = cv2.bitwise_and(color, color, mask=edges)
return cartoon
# 绘制中文文本函数
def draw_text(frame, text, position, font_size=30, color=(255, 255, 255), max_width=100):
# 确保输入为三通道BGR格式
if len(frame.shape) == 2:
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
# 转换为PIL格式(RGB)
pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
try:
font = ImageFont.truetype("simhei.ttf", font_size)
except:
font = ImageFont.load_default()
# 自动调整字体大小
text_bbox = draw.textbbox((0, 0), text, font=font)
text_width = text_bbox[2] - text_bbox[0]
if text_width > max_width:
font_size = int(font_size * max_width / text_width)
font = ImageFont.truetype("simhei.ttf", font_size)
# 计算居中位置
text_bbox = draw.textbbox((0, 0), text, font=font)
x = position[0] + (max_width - (text_bbox[2] - text_bbox[0])) // 2
y = position[1] + (40 - (text_bbox[3] - text_bbox[1])) // 2
draw.text((x, y), text, font=font, fill=color)
# 转换回OpenCV格式
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
# 初始化摄像头
cap = cv2.VideoCapture(0)
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
# 界面设置
mode = 'normal'
buttons = {
"原始像素": (10, 50, 110, 90),
"灰度像素": (10, 100, 110, 140),
"复古像素": (10, 150, 110, 190),
"反色像素": (10, 200, 110, 240),
"红蓝分离": (10, 250, 110, 290),
"漫画像素": (10, 300, 110, 340),
}
name_to_mode = {k: v for k, v in zip(
buttons.keys(), ['normal', 'desaturate', 'sepia', 'invert', 'redblue', 'cartoon'])}
# 鼠标回调函数
def mouse_callback(event, x, y, flags, param):
global mode
if event == cv2.EVENT_LBUTTONDOWN:
for name, (x1, y1, x2, y2) in buttons.items():
if x1 <= x <= x2 and y1 <= y <= y2:
mode = name_to_mode[name]
cv2.namedWindow("Pixel Effect")
cv2.setMouseCallback("Pixel Effect", mouse_callback)
while True:
ret, frame = cap.read()
if not ret:
break
# 确保三通道输入
if frame.shape[2] == 1:
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
frame = cv2.flip(frame, 1)
processed = apply_effect(frame.copy(), mode)
# 人脸检测和像素化
gray = cv2.cvtColor(processed, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5)
pixel_size = 10
for (x, y, w, h) in faces:
# 像素化处理
face_roi = processed[y:y + h, x:x + w]
small = cv2.resize(face_roi, (w // pixel_size, h // pixel_size))
pixelized = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
# 添加网格线
for i in range(0, h, pixel_size):
cv2.line(pixelized, (0, i), (w, i), (0, 0, 0), 1)
for j in range(0, w, pixel_size):
cv2.line(pixelized, (j, 0), (j, h), (0, 0, 0), 1)
processed[y:y + h, x:x + w] = pixelized
# 绘制按钮
for name, (x1, y1, x2, y2) in buttons.items():
color = (0, 255, 0) if name_to_mode[name] == mode else (100, 100, 100)
cv2.rectangle(processed, (x1, y1), (x2, y2), color, -1)
processed = draw_text(processed, name, (x1, y1),
font_size=20, max_width=100)
cv2.imshow("Pixel Effect", processed)
if cv2.waitKey(1) == 27:
break
cap.release()
cv2.destroyAllWindows()