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))
实际项目经验:在门禁系统中,我通常不会直接旋转整张图。而是先检测到人脸,算出人脸的倾斜角度,然后只对人脸区域做旋转校正。这样计算量小得多,而且旋转后的图像直接送去识别,准确率能提升 10% 以上。

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()
图像几何变换与绘制知识体系 OpenCV 图像操作 缩放 resize() 裁剪 切片操作 旋转 warpAffine() 绘制图形 矩形 rectangle 圆形 circle 文字 putText 应用场景:人脸检测框标注、关键点标记、身份信息显示、图像预处理
我的建议:把这四个操作封装成一个工具模块。我在项目里就是这么干的——写一个 image_utils.py,里面放 resize_image()、crop_face()、rotate_face()、draw_detection() 四个函数。后续做门禁系统时,直接 import 调用,代码清爽多了。