Skip to content

第一章 OpenCV 图像处理


课程导航

项目内容
课时1.5 小时
类型理论 + 代码实战
前置知识Python 基础、NumPy 数组操作

一、学习目标

  1. 掌握 OpenCV 的图像读写、显示与保存全流程
  2. 理解并实现常见的几何变换(缩放、旋转、仿射、透视)
  3. 掌握图像滤波与增强方法(高斯滤波、中值滤波、直方图均衡化)
  4. 理解边缘检测原理并能运用 Canny / Sobel / Laplacian 算子
  5. 了解 SIFT 与 ORB 特征提取的基本原理与调用方式
  6. 掌握 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_GRAYSCALEIMREAD_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() + warpAffine3 点确定 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

四、课后练习

基础题

  1. 读取一张暗光照片,先用 CLAHE 增强,再用 Canny 边缘检测,保存结果。
  2. cv2.resize 将一张大图缩放到 640×480,再用仿射变换做一个 30° 旋转。

进阶题

  1. 拍摄两幅有重叠区域的照片,使用 SIFT 特征匹配 + cv2.findHomography + cv2.warpPerspective 实现图像拼接(panorama)。
  2. 打开摄像头实时人脸检测,并用矩形框显示检测结果;按 q 退出。
  3. 使用 ORB 实现一个图像检索 demo:给定一张查询图,从图库中找到最相似的 3 张图并排序展示。

思考题

  1. 为什么 Canny 比单纯的 Sobel 或 Laplacian 效果更好?它做了哪些额外步骤?
  2. 直方图均衡化在彩色图像上直接对 RGB 三个通道分别做会有什么问题?应该怎么做?

Python 学习资料