4. OpenCV基础(二)
上一章我们把图像读进来了,也看了像素是怎么回事。但说实话,在门禁系统里,摄像头抓到的原始画面很少能直接用的——人脸可能太小、角度不对、或者画面里全是背景。所以这一章,我们就来搞定图像的几何变换和绘制。这些都是我每天写代码时几乎必用的操作。
4.1 图像缩放 — 让画面大小随心
门禁摄像头抓到的图,动不动就是 1920×1080。直接拿这么大一张图去做人脸检测?太慢了。我一般会先缩放到 640×480 甚至更小,检测速度能快好几倍。
OpenCV 里用 cv2.resize() 搞定缩放。关键参数就两个:目标尺寸和插值方法。
import cv2
img = cv2.imread('face.jpg')
# 方式一:指定宽高(宽640,高480)
resized = cv2.resize(img, (640, 480))
# 方式二:按比例缩放(宽高各缩小一半)
scale_percent = 50
width = int(img.shape[1] * scale_percent / 100)
height = int(img.shape[0] * scale_percent / 100)
resized = cv2.resize(img, (width, height))
# 方式三:使用fx,fy缩放因子(推荐)
resized = cv2.resize(img, None, fx=0.5, fy=0.5)
fx=0.5, fy=0.5。这样代码简洁,而且不用手动算宽高。但要注意,缩小后的人脸如果太小(比如小于 80×80),检测器可能就认不出来了。
插值方法怎么选?
| 插值方法 | 适用场景 | 速度 |
|---|---|---|
cv2.INTER_NEAREST |
放大像素风格图像(如二维码) | 最快 |
cv2.INTER_LINEAR |
日常缩放(默认值) | 快 |
cv2.INTER_CUBIC |
高质量放大 | 慢 |
cv2.INTER_AREA |
缩小图像(推荐) | 中等 |
INTER_LINEAR,结果人脸边缘出现锯齿状的摩尔纹,人脸检测器死活认不出来。后来换成 INTER_AREA,问题立刻解决。记住:缩小用 AREA,放大用 LINEAR 或 CUBIC。
4.2 图像裁剪 — 只取我想要的区域
裁剪其实比你想的简单。OpenCV 里图像就是 NumPy 数组,裁剪就是数组切片。说白了就是:img[y1:y2, x1:x2]。
# 裁剪人脸区域(假设人脸在 (100,50) 到 (300,250) 之间)
face_roi = img[50:250, 100:300]
cv2.imshow('Cropped Face', face_roi)
cv2.waitKey(0)
这里有个容易搞混的地方:先写行(y),再写列(x)。因为 NumPy 数组的索引顺序是 (rows, cols),对应图像就是 (高度, 宽度)。
img[0:1000, 0:1000],结果原图只有 800×600,程序直接崩了。安全写法是先判断边界:
h, w = img.shape[:2]
x1, y1, x2, y2 = 100, 50, 300, 250
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(w, x2)
y2 = min(h, y2)
face_roi = img[y1:y2, x1:x2]
4.3 图像旋转 — 把歪脸摆正
门禁系统里,人脸很少是正对着摄像头的。歪着头、侧着脸,检测器就罢工了。所以旋转是家常便饭。
OpenCV 的旋转需要两步走:先用 cv2.getRotationMatrix2D() 拿到旋转矩阵,再用 cv2.warpAffine() 执行变换。
h, w = img.shape[:2]
center = (w // 2, h // 2)
# 获取旋转矩阵:中心点、角度、缩放比例
# 正角度 = 逆时针旋转
M = cv2.getRotationMatrix2D(center, 45, 1.0)
# 执行仿射变换
rotated = cv2.warpAffine(img, M, (w, h))
cv2.imshow('Rotated 45°', rotated)
你想想看,旋转 45 度后,四个角是不是就跑到画面外面去了?OpenCV 默认会裁剪掉这些部分。如果想保留完整图像,需要调整输出尺寸:
# 计算旋转后的新尺寸(保留全部内容)
import numpy as np
cos = abs(M[0, 0])
sin = abs(M[0, 1])
new_w = int(h * sin + w * cos)
new_h = int(h * cos + w * sin)
# 调整旋转矩阵中的平移量
M[0, 2] += (new_w / 2) - center[0]
M[1, 2] += (new_h / 2) - center[1]
rotated_full = cv2.warpAffine(img, M, (new_w, new_h))
4.4 绘制图形 — 给人脸画框、标点、写字
检测到人脸后,总得在画面上标出来吧?这就是绘制图形的用武之地。门禁系统里最常用的三个操作:画矩形框人脸、画圆形标关键点、写文字显示姓名或置信度。
4.4.1 画矩形 — 框出人脸
# 参数:图像、左上角、右下角、颜色(BGR)、线宽
cv2.rectangle(img, (100, 50), (300, 250), (0, 255, 0), 2)
# 画实心矩形(线宽填 -1)
cv2.rectangle(img, (100, 50), (300, 250), (0, 255, 0), -1)
颜色用 BGR 顺序,这点和常规的 RGB 相反。绿色是 (0, 255, 0),红色是 (0, 0, 255)。我刚开始用 OpenCV 时,想画个红色框,写成了 (255, 0, 0),结果出来个蓝色,愣了半天。
4.4.2 画圆形 — 标记关键点
# 参数:图像、圆心、半径、颜色、线宽
cv2.circle(img, (200, 150), 5, (0, 0, 255), -1)
# 画多个关键点(比如眼睛、鼻子)
keypoints = [(180, 120), (220, 120), (200, 160)]
for (x, y) in keypoints:
cv2.circle(img, (x, y), 3, (255, 0, 0), -1)
4.4.3 写文字 — 显示姓名或状态
# 参数:图像、文字、左下角坐标、字体、字号、颜色、线宽
cv2.putText(img, 'Zhang San', (100, 40),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)
# 中文怎么办?OpenCV 不支持直接写中文
# 我的方案:用 PIL 转一下
from PIL import Image, ImageDraw, ImageFont
import numpy as np
img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_pil)
font = ImageFont.truetype('simhei.ttf', 24)
draw.text((100, 40), '张三', font=font, fill=(255, 255, 255))
img = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
cv2.putText() 不支持中文,会显示成乱码。如果你需要在门禁系统界面上显示中文姓名,必须用 PIL 方案。我一般会封装一个工具函数,把中文绘制逻辑藏起来,调用时直接传中文就行。
4.5 综合实战:人脸检测框绘制
把今天学的串起来,写一个完整的人脸框绘制流程。假设我们已经用 Haar 级联检测器找到了人脸区域:
import cv2
img = cv2.imread('group_photo.jpg')
# 假设检测到两个人脸
faces = [(120, 80, 200, 180), (350, 100, 430, 200)]
for (x1, y1, x2, y2) in faces:
# 画矩形框
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 在框上方写标签
label = f'Person ({x1},{y1})'
cv2.putText(img, label, (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# 在框中心画一个红点
cx = (x1 + x2) // 2
cy = (y1 + y2) // 2
cv2.circle(img, (cx, cy), 4, (0, 0, 255), -1)
cv2.imshow('Face Detection Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
image_utils.py,里面放 resize_image()、crop_face()、rotate_face()、draw_detection() 四个函数。后续做门禁系统时,直接 import 调用,代码清爽多了。