OpenCV 图像处理实战指南
前言
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和图像处理库,拥有2500+优化算法,支持Python、C++、Java等多种编程语言。本文将带你从基础入门到实战,掌握 OpenCV 的核心图像处理技术。
1. 环境搭建
1
| pip install opencv-python opencv-contrib-python numpy matplotlib
|
提示:opencv-python 包含主要模块,opencv-contrib-python 包含额外的高级算法(如SIFT、SURF等)。
2. 图像基础原理
数字图像表示
数字图像本质是一个像素矩阵:
- 灰度图像:单通道矩阵,每个元素表示像素亮度(0-255)
- 彩色图像:多通道矩阵,通常为BGR三通道,每个通道0-255
- RGBA图像:四通道,包含透明度Alpha通道
1 2 3 4 5 6 7 8 9 10 11 12
| 图像坐标系统: ┌─────────────────────────────→ X (列) │ (0,0) (1,0) (2,0) │ ●────────●────────● │ │ pixel │ pixel │ │ │ │ │ │ (0,1) (1,1) (2,1) │ ●────────●────────● │ │ pixel │ pixel │ │ ↓ ↓ ↓ └───────────────────────────── Y (行)
|
色彩空间原理
| 色彩空间 |
组成 |
应用场景 |
| RGB |
红、绿、蓝加法混色 |
屏幕显示、图像存储 |
| BGR |
OpenCV默认格式 |
OpenCV内部处理 |
| GRAY |
亮度单通道 |
灰度处理、特征提取 |
| HSV |
色调、饱和度、明度 |
颜色分割、目标追踪 |
| LAB |
明度、a通道、b通道 |
色彩均衡、跨设备一致 |
HSV 色彩空间优势:H(色调)不受光线亮度影响,更适合颜色识别任务。
3. 基础图像操作
读取、显示和保存图像
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import cv2 import numpy as np import matplotlib.pyplot as plt
img = cv2.imread('image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
cv2.imshow('Image', img) cv2.waitKey(0) cv2.destroyAllWindows()
cv2.imwrite('output.jpg', img)
|
原理:OpenCV 默认使用 BGR 格式读取图像,而 Matplotlib 使用 RGB 格式,因此显示时需要转换。
获取图像基本信息
1 2 3
| print(f"图像形状: {img.shape}") print(f"图像数据类型: {img.dtype}") print(f"图像尺寸: {img.size} 像素")
|
4. 图像几何变换
调整大小与裁剪
1 2 3 4 5 6 7 8 9 10 11
| resized = cv2.resize(img, (800, 600))
scale = 0.5 width = int(img.shape[1] * scale) height = int(img.shape[0] * scale) resized = cv2.resize(img, (width, height))
cropped = img[100:400, 200:500]
|
旋转与翻转原理
旋转矩阵数学表达:
1 2
| R = [cosθ -sinθ] [sinθ cosθ]
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| center = (img.shape[1] // 2, img.shape[0] // 2) angle = 45 scale = 1.0 rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
rotated = cv2.warpAffine(img, rotation_matrix, (img.shape[1], img.shape[0]))
flipped_horizontal = cv2.flip(img, 1) flipped_vertical = cv2.flip(img, 0) flipped_both = cv2.flip(img, -1)
|
仿射变换与透视变换
仿射变换(Affine Transform):保持平行线的平行性,包括旋转、平移、缩放、剪切。
1 2 3 4 5 6 7
| 原始图像 仿射变换后 ┌────────┐ ╱╲ │ ●───● │ ─────→ ╱────╲ │ │ │ │ ╱ ╲ │ ●───● │ ╱________╲ └────────┘ 平行四边形 保持平行四边形
|
透视变换(Perspective Transform):可处理任意四边形到四边形的映射,用于矫正倾斜文档。
1 2 3 4 5 6 7 8 9 10 11
| pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv2.getPerspectiveTransform(pts1, pts2)
warped = cv2.warpPerspective(img, M, (300, 300))
|
5. 图像色彩空间处理
1 2 3 4 5 6 7 8 9 10 11 12 13
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
lower_blue = np.array([100, 50, 50]) upper_blue = np.array([130, 255, 255]) mask = cv2.inRange(hsv, lower_blue, upper_blue)
|
灰度转换原理
加权求和法(Luminosity Method):
1
| Gray = 0.299 × R + 0.587 × G + 0.114 × B
|
人眼对绿色最敏感,所以绿色权重最高。
颜色阈值分割原理
在 HSV 空间进行颜色分割更稳定,因为:
- H 通道代表颜色类型(0-180°)
- S 通道代表颜色纯度
- V 通道代表亮度
这样可以将”是什么颜色”和”有多亮”分开处理。
6. 图像平滑与降噪
模糊算法原理对比
| 算法 |
原理 |
特点 |
适用场景 |
| 均值模糊 |
邻域像素求平均 |
简单、计算快 |
快速去噪 |
| 高斯模糊 |
加权平均(高斯核) |
权重随距离递减 |
去除高斯噪声 |
| 中值模糊 |
邻域像素中值 |
对椒盐噪声效果好 |
脉冲噪声 |
| 双边滤波 |
空间+像素差加权 |
保留边缘 |
美颜、HDR |
1 2 3 4 5 6 7 8 9 10 11
| blur = cv2.blur(img, (5, 5))
gaussian_blur = cv2.GaussianBlur(img, (5, 5), 0)
median_blur = cv2.medianBlur(img, 5)
bilateral = cv2.bilateralFilter(img, 9, 75, 75)
|
高斯模糊数学原理
二维高斯函数:
1
| G(x,y) = (1/2πσ²) × e^(-(x²+y²)/2σ²)
|
其中 σ 控制模糊程度,值越大越模糊。
双边滤波原理
双边滤波同时考虑空间距离和像素值差异:
1
| BF(I) = (1/Wp) × Σ Gs(||p-q||) × Gr(|Ip-Iq|) × Iq
|
这样边缘处像素差大,权重小,被保留;平坦区域权重均匀,被平滑。
锐化处理
1 2 3 4
| kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]]) sharpened = cv2.filter2D(img, -1, kernel)
|
锐化原理:增强图像细节,公式:
$$\text{Sharpened} = \text{Original} + \alpha \times (\text{Original} - \text{Blurred})$$
拉普拉斯算子可提取边缘,将边缘加回原图即可锐化。
7. 边缘检测与轮廓
边缘检测算子对比
| 算子 |
原理 |
优缺点 |
| Sobel |
梯度近似(一阶导数) |
对噪声敏感,可检测方向 |
| Laplacian |
二阶导数 |
边缘更细,对噪声更敏感 |
| Canny |
多阶段算法 |
边缘细、定位准确、抗噪好 |
Canny 算法原理(多阶段)
1 2 3 4 5 6 7 8 9 10 11 12 13
| 原始图像 ↓ 高斯模糊(去噪) ↓ 梯度计算(Sobel) ↓ 非极大值抑制(NMS)- 细化边缘 ↓ 双阈值检测 - 区分强边缘/弱边缘 ↓ 边缘连接(滞后阈值处理) ↓ 最终边缘
|
双阈值机制:
- 高阈值:确定边缘
- 低阈值:连接断开的边缘(只连接有强边缘相邻的弱边缘)
1 2 3 4 5 6 7 8 9
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
cv2.imshow('Edges', edges) cv2.waitKey(0)
|
Sobel 和 Laplacian 算子
1 2 3 4 5 6 7 8 9
| sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
sobel = cv2.bitwise_or(sobelx, sobely)
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
|
Sobel 梯度原理:
1 2 3 4 5
| Gx = [-1 0 +1] Gy = [-1 -2 -1] [-2 0 +2] [ 0 0 0] [-1 0 +1] [+1 +2 +1]
|G| = √(Gx² + Gy²)
|
轮廓查找与绘制
1 2 3 4 5 6 7 8 9 10 11 12 13
| contours, hierarchy = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contour_img = img.copy() cv2.drawContours(contour_img, contours, -1, (0, 255, 0), 2)
for cnt in contours: area = cv2.contourArea(cnt) perimeter = cv2.arcLength(cnt, True) if area > 100: print(f"面积: {area}, 周长: {perimeter}")
|
轮廓检索模式:
RETR_EXTERNAL:只检索外轮廓
RETR_LIST:检索所有轮廓,不建立层级
RETR_TREE:检索所有轮廓,建立完整层级树
8. 图像阈值处理
全局阈值 vs 自适应阈值
| 方法 |
原理 |
适用场景 |
| 全局阈值 |
固定像素值作为阈值 |
光照均匀图像 |
| 自适应阈值 |
根据邻域计算局部阈值 |
光照不均图像 |
| Otsu自动阈值 |
最大化类间方差 |
双峰图像自动寻优 |
1 2 3 4 5 6 7 8 9
| _, thresh_binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
adaptive_thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
_, otsu_thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
Otsu 阈值法原理
自动寻找最佳分割阈值,使前景和背景的类间方差最大:
1
| σ² = w₁ × w₂ × (μ₁ - μ₂)²
|
其中 w₁, w₂ 是两类权重,μ₁, μ₂ 是两类均值。
阈值类型
1 2 3 4
| THRESH_BINARY: if src > thresh: dst = max_val else: dst = 0 THRESH_BINARY_INV: if src > thresh: dst = 0 else: dst = max_val THRESH_TRUNC: if src > thresh: dst = thresh else: dst = src THRESH_TOZERO: if src > thresh: dst = src else: dst = 0
|
9. 形态学操作
腐蚀与膨胀原理
| 操作 |
原理 |
效果 |
| 腐蚀 (Erosion) |
邻域最小值替换中心 |
去除白色噪点、断开连接 |
| 膨胀 (Dilation) |
邻域最大值替换中心 |
扩大白色区域、填充空洞 |
1 2 3 4
| 原始 腐蚀后 膨胀后 ██████ ██ ██ ████████ ██ ██ → ██ ██ → ████████ ██████ ██ ██ ████████
|
1 2 3 4 5 6 7
| kernel = np.ones((5, 5), np.uint8)
eroded = cv2.erode(img, kernel, iterations=1)
dilated = cv2.dilate(img, kernel, iterations=1)
|
开运算与闭运算
| 操作 |
组成 |
作用 |
| 开运算 |
先腐蚀后膨胀 |
去除小噪点、断开细小连接 |
| 闭运算 |
先膨胀后腐蚀 |
填充小孔洞、连接邻近物体 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
|
10. 图像分割
分水岭算法原理
模拟地形水淹过程:
- 从低洼处(局部最小值)开始注水
- 水位上升,相邻盆地相遇时筑坝
- 最终水坝即为分割边界
1 2 3 4 5 6 7
| dist_transform = cv2.distanceTransform(gray, cv2.DIST_L2, 5)
markers = np.zeros(gray.shape, dtype=np.int32)
watershed = cv2.watershed(img, markers)
|
GrabCut 算法原理
基于图割(Graph Cut)的迭代前景提取:
- 用户定义初始矩形(前景种子)
- 建立像素级图模型
- 迭代优化:最小割 → 更新分割 → 重复
- 最终得到前景/背景概率
1 2 3 4 5 6 7 8 9 10
| rect = (50, 50, 300, 300) mask = np.zeros(gray.shape, np.uint8) bgd_model = np.zeros((1, 65), np.float64) fgd_model = np.zeros((1, 65), np.float64)
cv2.grabCut(img, mask, rect, bgd_model, fgd_model, 5, cv2.GC_INIT_WITH_RECT)
final_mask = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
|
11. 霍夫变换
霍夫直线检测原理
将笛卡尔坐标系的直线转换到霍夫参数空间:
$$y = mx + b \Rightarrow b = -mx + y$$
一条直线上的点,在参数空间中汇聚成一条线:
1 2 3 4 5 6
| 笛卡尔坐标系 霍夫参数空间 (m, b) │ │ │ ● P1 │ ● (m1,b1) │ ● P2 │ ● │● P3 │● 交点=(m,b)对应一直线 └─────────→ └─────────→
|
1 2 3 4 5 6
| lines = cv2.HoughLinesP(edges, 1, np.pi/180, 100, minLineLength=50, maxLineGap=10) for line in lines: x1, y1, x2, y2 = line[0] cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
|
霍夫圆检测原理
圆的一般方程:
需要三个参数 (a, b, r),因此是三维霍夫空间。
1 2
| 投票累加器:统计 (a, b, r) 组合的票数 峰值点 → 检测到的圆
|
1 2 3 4 5 6 7
| circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=30) if circles is not None: circles = np.uint16(np.around(circles)) for circle in circles[0, :]: cv2.circle(img, (circle[0], circle[1]), circle[2], (0, 255, 0), 2)
|
12. 图像拼接与混合
图像融合原理
金字塔融合:
- 构建高斯金字塔
- 构建拉普拉斯金字塔
- 在金字塔层级上融合
- 重建图像
1 2
| blended = cv2.addWeighted(img1, 0.5, img2, 0.5, 0)
|
拼接方法
1 2 3 4 5
| stitched = np.hstack((img1, img2))
stitched = np.vstack((img1, img2))
|
13. 实战项目:文档扫描仪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| def scan_document(image): """ 文档扫描完整流程: 1. 去噪 2. 边缘检测 3. 轮廓查找 4. 透视变换矫正 """ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) edged = cv2.Canny(blurred, 75, 200) contours, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5] for cnt in contours: peri = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.02 * peri, True) if len(approx) == 4: pts = approx.reshape(4, 2) return approx return None
|
14. 性能优化技巧
| 技巧 |
说明 |
| 使用 NumPy 向量化 |
避免 Python 循环,用数组操作 |
| 适当降采样 |
大图先缩小处理,再放大 |
| 选择合适的数据类型 |
uint8 vs float32,按需转换 |
| 并行处理 |
cv2.setUseOptimized(True) 启用优化 |
| GPU 加速 |
使用 cv2.cuda 模块(需安装 cuda 版本) |
1 2 3 4 5 6
| cv2.setUseOptimized(True) cv2.setNumThreads(8)
small = img[::2, ::2]
|
总结
本文深入讲解了 OpenCV 图像处理的核心原理与实战技巧:
| 类别 |
内容 |
| 基础 |
图像读取、显示、色彩空间转换 |
| 变换 |
旋转、翻转、仿射变换、透视变换 |
| 滤波 |
高斯模糊、中值滤波、双边滤波、锐化 |
| 边缘 |
Sobel、Laplacian、Canny 算子 |
| 形态学 |
腐蚀、膨胀、开闭运算 |
| 分割 |
阈值分割、分水岭、GrabCut |
| 变换检测 |
霍夫直线、霍夫圆检测 |
进阶学习方向
- 视频处理:摄像头读取、帧间处理
- 特征检测:SIFT、ORB、FAST 特征点
- 目标追踪:KCF、MOSSE、卡尔曼滤波
- 深度学习集成:DNN 模块加载 TensorFlow/ONNX 模型
- 3D 重建:立体匹配、点云处理
参考资源
关注公众号,获取更多技术干货!