Appearance
第一章 OpenCV 图像处理
课程导航
| 项目 | 内容 |
|---|---|
| 课时 | 1.5 小时 |
| 类型 | 理论 + 代码实战 |
| 前置知识 | Python 基础、NumPy 数组操作 |
一、学习目标
- 掌握 OpenCV 的图像读写、显示与保存全流程
- 理解并实现常见的几何变换(缩放、旋转、仿射、透视)
- 掌握图像滤波与增强方法(高斯滤波、中值滤波、直方图均衡化)
- 理解边缘检测原理并能运用 Canny / Sobel / Laplacian 算子
- 了解 SIFT 与 ORB 特征提取的基本原理与调用方式
- 掌握 Haar Cascade 实现人脸检测与车牌识别
二、核心知识点
2.1 图像读写、显示与保存
2.1.1 定义
OpenCV 使用 cv2.imread() 读取图像,返回 NumPy 数组(形状为 H×W×C,BGR 通道顺序);cv2.imshow() 在窗口显示图像;cv2.imwrite() 将图像保存到磁盘。
| 函数 | 说明 | 关键参数 |
|---|---|---|
cv2.imread(path, flags) | 读取图像 | cv2.IMREAD_COLOR(默认)、IMREAD_GRAYSCALE、IMREAD_UNCHANGED |
cv2.imshow(winname, img) | 显示图像 | 窗口名、图像数据 |
cv2.imwrite(path, img) | 保存图像 | 路径+图像数据,扩展名决定格式 |
注意:OpenCV 默认 BGR 通道顺序,与 matplotlib 的 RGB 不同,显示时需转换。
2.1.2 代码示例
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('lena.jpg') # BGR 彩色
gray = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE) # 灰度
# BGR → RGB(用于 matplotlib 正确显示)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.subplot(1, 2, 1); plt.imshow(img_rgb); plt.title('彩色')
plt.subplot(1, 2, 2); plt.imshow(gray, cmap='gray'); plt.title('灰度')
plt.show()
# 保存图像
cv2.imwrite('output/lena_copy.jpg', img)
print(f"图像尺寸: {img.shape}") # (height, width, channels)2.2 几何变换
2.2.1 定义
几何变换改变图像的空间布局,包括缩放、旋转、仿射变换和透视变换。
| 操作 | 函数 | 说明 |
|---|---|---|
| 缩放 | cv2.resize() | 线性/立方插值重采样 |
| 旋转 | cv2.getRotationMatrix2D() + cv2.warpAffine() | 绕指定中心旋转 |
| 仿射变换 | cv2.getAffineTransform() + warpAffine | 3 点确定 2×3 矩阵,保持平行线 |
| 透视变换 | cv2.getPerspectiveTransform() + warpPerspective() | 4 点确定 3×3 矩阵,矫正视角 |
2.2.2 代码示例
python
import cv2
import numpy as np
img = cv2.imread('lena.jpg')
h, w = img.shape[:2]
# ---------- 1. 缩放 ----------
resized = cv2.resize(img, (224, 224)) # 指定尺寸
resized_ratio = cv2.resize(img, None, fx=0.5, fy=0.5) # 按比例
# ---------- 2. 旋转 ----------
M_rotate = cv2.getRotationMatrix2D((w//2, h//2), 45, 1.0) # 中心、角度、缩放
rotated = cv2.warpAffine(img, M_rotate, (w, h))
# ---------- 3. 仿射变换 ----------
src_pts = np.float32([[50,50], [200,50], [50,200]])
dst_pts = np.float32([[10,100], [200,50], [100,250]])
M_affine = cv2.getAffineTransform(src_pts, dst_pts)
affined = cv2.warpAffine(img, M_affine, (w, h))
# ---------- 4. 透视变换(视角矫正) ----------
src_4 = np.float32([[0,0], [w-1,0], [0,h-1], [w-1,h-1]])
dst_4 = np.float32([[50,0], [w-50,0], [0,h-1], [w-1,h-1]])
M_persp = cv2.getPerspectiveTransform(src_4, dst_4)
warped = cv2.warpPerspective(img, M_persp, (w, h))
cv2.imwrite('output/geometric.jpg', np.hstack([resized, rotated, affined, warped]))2.3 滤波与增强
2.3.1 定义
滤波操作用于去噪、平滑或锐化图像;直方图均衡化增强对比度。
| 方法 | 函数 | 核心理念 |
|---|---|---|
| 高斯滤波 | cv2.GaussianBlur() | 用高斯核对邻域加权平均,去除高斯噪声 |
| 中值滤波 | cv2.medianBlur() | 用邻域中值替代中心值,去椒盐噪声极佳 |
| 直方图均衡化 | cv2.equalizeHist() | 拉伸直方图分布,让像素均匀散布 |
2.3.2 代码示例
python
import cv2
import numpy as np
img = cv2.imread('lena.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 高斯滤波(核尺寸必须为正奇数)
gaussian = cv2.GaussianBlur(img, (5, 5), sigmaX=1.5)
# 中值滤波(核尺寸为奇数)
median = cv2.medianBlur(img, 5)
# 直方图均衡化(仅灰度图)
equ = cv2.equalizeHist(gray)
# 自适应直方图均衡化(CLAHE — 避免过度放大噪声)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
clahe_result = clahe.apply(gray)
# 对比展示
stack = np.hstack([
np.hstack([gray, equ, clahe_result]),
])
cv2.imwrite('output/histogram_equalization.jpg', stack)
# 彩色图像直方图均衡化(需在 YUV 空间处理)
img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])
img_color_equ = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR)2.4 边缘检测
2.4.1 定义
边缘是图像中像素值发生剧烈变化的位置,对应物体的轮廓。
| 算子 | 原理 | 特点 |
|---|---|---|
| Sobel | 一阶导数梯度近似(Gx, Gy) | 简单、对噪声敏感 |
| Laplacian | 二阶导数,检测过零点 | 对噪声极敏感,常结合高斯使用(LoG) |
| Canny | 多阶段:高斯平滑→梯度→非极大值抑制→双阈值 | 最优边缘检测器,连续且单像素 |
2.4.2 代码示例
python
import cv2
import numpy as np
img = cv2.imread('lena.jpg', cv2.IMREAD_GRAYSCALE)
# ---------- Sobel ----------
sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 水平梯度
sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # 垂直梯度
sobel = cv2.magnitude(sobel_x, sobel_y) # 合并幅值
# ---------- Laplacian ----------
laplacian = cv2.Laplacian(img, cv2.CV_64F, ksize=3)
# ---------- Canny(推荐)----------
edges = cv2.Canny(img, threshold1=50, threshold2=150) # 低/高阈值
# 合并输出
cv2.imwrite('output/edges.jpg', np.hstack([
img, sobel.astype(np.uint8),
np.uint8(np.abs(laplacian)), edges
]))Canny 调参经验:
threshold1(低阈值):threshold2(高阈值)≈ 1:2 ~ 1:3- 先高斯模糊可减少虚假边缘
- 对光照变化大的场景,可先用 CLAHE 增强再检测
2.5 特征提取:SIFT 与 ORB
2.5.1 定义
特征点(关键点)是图像中具有显著局部纹理的位置,可用于图像匹配、拼接、目标跟踪。
| 算法 | 类型 | 是否专利 | 特点 |
|---|---|---|---|
| SIFT | 尺度不变特征变换 | 已过期(2020) | 尺度/旋转/光照不变,计算慢 |
| ORB | 带方向的 FAST + BRIEF | 开源免费 | 速度极快,旋转不变,无尺度不变 |
2.5.2 代码示例
python
import cv2
import numpy as np
img1 = cv2.imread('book_cover.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('book_scene.jpg', cv2.IMREAD_GRAYSCALE)
# ---------- SIFT ----------
sift = cv2.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# ---------- ORB ----------
orb = cv2.ORB_create(nfeatures=1000)
kp1_orb, des1_orb = orb.detectAndCompute(img1, None)
kp2_orb, des2_orb = orb.detectAndCompute(img2, None)
# ---------- 特征匹配(暴力匹配)----------
bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True) # SIFT 用 L2
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
# 绘制前 30 个最佳匹配
matched_img = cv2.drawMatches(img1, kp1, img2, kp2, matches[:30], None,
flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
cv2.imwrite('output/sift_matches.jpg', matched_img)
print(f"SIFT 关键点数: {len(kp1)} / {len(kp2)}")
print(f"ORB 关键点数: {len(kp1_orb)} / {len(kp2_orb)}")💡 FLANN 快速匹配(适用于大规模场景):
python
index_params = dict(algorithm=1, trees=5) # KD-Tree
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)
# Lowe 比率测试
good = [m for m, n in matches if m.distance < 0.7 * n.distance]2.6 人脸检测与车牌识别(Haar Cascade)
2.6.1 定义
Haar Cascade 是一种基于 AdaBoost 的级联分类器,通过 Haar-like 特征快速判别图像窗口是否包含目标对象。OpenCV 提供预训练模型。
2.6.2 代码示例
python
import cv2
# ---------- 人脸检测 ----------
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
)
img = cv2.imread('group_photo.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1, # 每次缩放比例
minNeighbors=5, # 每个候选区域需要多少个相邻矩形才保留
minSize=(30, 30) # 最小人脸尺寸
)
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imwrite('output/face_detection.jpg', img)
print(f"检测到 {len(faces)} 张人脸")
# ---------- 车牌识别 ----------
plate_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_russian_plate_number.xml'
)
car_img = cv2.imread('car.jpg')
car_gray = cv2.cvtColor(car_img, cv2.COLOR_BGR2GRAY)
plates = plate_cascade.detectMultiScale(car_gray, 1.1, 3)
for (x, y, w, h) in plates:
cv2.rectangle(car_img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imwrite('output/plate_detection.jpg', car_img)检测参数调优:
scaleFactor越小(如 1.01)检测越精确但越慢minNeighbors增大可减少误报但可能漏检- 对视频流可先降采样再检测以提高 FPS
三、小结
| 知识点 | 掌握程度 | 核心函数 |
|---|---|---|
| 图像 IO | 必须掌握 | imread / imwrite / imshow |
| 几何变换 | 必须掌握 | resize / warpAffine / warpPerspective |
| 滤波增强 | 必须掌握 | GaussianBlur / medianBlur / equalizeHist / CLAHE |
| 边缘检测 | 必须掌握 | Canny(首选) / Sobel / Laplacian |
| 特征提取 | 理解原理 | SIFT_create / ORB_create / BFMatcher |
| Haar Cascade | 理解应用 | CascadeClassifier.detectMultiScale |
四、课后练习
基础题
- 读取一张暗光照片,先用 CLAHE 增强,再用 Canny 边缘检测,保存结果。
- 用
cv2.resize将一张大图缩放到 640×480,再用仿射变换做一个 30° 旋转。
进阶题
- 拍摄两幅有重叠区域的照片,使用 SIFT 特征匹配 +
cv2.findHomography+cv2.warpPerspective实现图像拼接(panorama)。 - 打开摄像头实时人脸检测,并用矩形框显示检测结果;按
q退出。 - 使用 ORB 实现一个图像检索 demo:给定一张查询图,从图库中找到最相似的 3 张图并排序展示。
思考题
- 为什么 Canny 比单纯的 Sobel 或 Laplacian 效果更好?它做了哪些额外步骤?
- 直方图均衡化在彩色图像上直接对 RGB 三个通道分别做会有什么问题?应该怎么做?