目录
[二、核心模块 1:图像基础操作](#二、核心模块 1:图像基础操作)
[2.1 概念与原理](#2.1 概念与原理)
[2.2 实战代码与目标结果](#2.2 实战代码与目标结果)
[2.2.1 图像读取与属性查看](#2.2.1 图像读取与属性查看)
[2.2.2 灰度图读取与保存](#2.2.2 灰度图读取与保存)
[2.2.3 ROI 区域提取(感兴趣区域)](#2.2.3 ROI 区域提取(感兴趣区域))
[2.2.4 BGR 通道拆分与合并](#2.2.4 BGR 通道拆分与合并)
[2.2.5 图像修改(打码、拼接、缩放)](#2.2.5 图像修改(打码、拼接、缩放))
[三、核心模块 2:图像运算](#三、核心模块 2:图像运算)
[3.1 概念与原理](#3.1 概念与原理)
[3.2 实战代码与目标结果](#3.2 实战代码与目标结果)
[3.2.1 边界填充(cv2.copyMakeBorder)](#3.2.1 边界填充(cv2.copyMakeBorder))
[3.2.2 图像加法运算](#3.2.2 图像加法运算)
[3.2.3 阈值处理(二值化)](#3.2.3 阈值处理(二值化))
[四、核心模块 3:图像平滑(去噪)](#四、核心模块 3:图像平滑(去噪))
[4.1 概念与原理](#4.1 概念与原理)
[4.2 实战代码与目标结果](#4.2 实战代码与目标结果)
[五、核心模块 4:形态学操作](#五、核心模块 4:形态学操作)
[5.1 概念与原理](#5.1 概念与原理)
[5.2 实战代码与目标结果](#5.2 实战代码与目标结果)
[六、核心模块 5:边缘检测](#六、核心模块 5:边缘检测)
[6.1 概念与原理](#6.1 概念与原理)
[6.2 实战代码与目标结果](#6.2 实战代码与目标结果)
[6.2.1 Sobel 算子](#6.2.1 Sobel 算子)
[6.2.2 Scharr 算子](#6.2.2 Scharr 算子)
[6.2.3 Canny 边缘检测(摄像头实时)](#6.2.3 Canny 边缘检测(摄像头实时))
[七、核心模块 6:轮廓分析](#七、核心模块 6:轮廓分析)
[7.1 概念与原理](#7.1 概念与原理)
[7.2 实战代码与目标结果](#7.2 实战代码与目标结果)
[7.2.1 轮廓检测与绘制](#7.2.1 轮廓检测与绘制)
[7.2.2 轮廓特征提取](#7.2.2 轮廓特征提取)
[7.2.3 近似轮廓(花束轮廓)](#7.2.3 近似轮廓(花束轮廓))
[八、核心模块 7:视频处理](#八、核心模块 7:视频处理)
[8.1 概念与原理](#8.1 概念与原理)
[8.2 实战代码与目标结果](#8.2 实战代码与目标结果)
[8.2.1 视频文件读取与灰度转换](#8.2.1 视频文件读取与灰度转换)
[8.2.2 摄像头实时添加椒盐噪声并去噪](#8.2.2 摄像头实时添加椒盐噪声并去噪)
一、引言
OpenCV(Open Source Computer Vision Library)是一套跨平台的计算机视觉开源库,涵盖了图像读取、预处理、形态学操作、边缘检测、轮廓分析、视频处理等全流程能力。本文将基于实战代码,从基础操作、图像运算、图像平滑、形态学操作、边缘检测、轮廓分析、视频处理七大模块,系统讲解 OpenCV 核心功能的概念、原理、实现代码及目标结果,帮助读者从入门到进阶掌握图像处理核心技能。
二、核心模块 1:图像基础操作
2.1 概念与原理
图像基础操作是图像处理的入门环节,核心包括:
• 图像读取 / 保存:将图像文件加载为矩阵格式,或将处理后的矩阵保存为文件;
• 图像属性:形状(高 × 宽 × 通道数)、数据类型(默认 uint8)、像素总数;
• 灰度转换:将 3 通道彩色图转为单通道灰度图(亮度值);
• ROI 提取:截取图像中感兴趣的局部区域;
• 通道拆分 / 合并:分离 BGR 通道(OpenCV 默认通道顺序),或合并单通道为彩色图。
2.2 实战代码与目标结果
2.2.1 图像读取与属性查看
python
import cv2
from PIL import Image
import numpy as np
# 1. 读取图像(PIL与OpenCV对比)
a = Image.open("zc.jpg")
print("PIL读取结果:", a) # 输出图像对象信息
aa = cv2.imread("zc.jpg")
print("OpenCV读取结果:", aa) # 输出图像像素矩阵
# 2. 显示图像
cv2.imshow('原始彩色图', aa)
cv2.waitKey(0) # 等待按键输入
cv2.destroyAllWindows()
# 3. 查看图像核心属性
print("图像形状(高×宽×通道):", aa.shape) # 例:(720, 1080, 3)
print("图像数据类型:", aa.dtype) # 输出:uint8(0-255像素值)
print("图像像素总数:", aa.size) # 高×宽×通道数
目标结果:
- 成功加载图像并显示窗口;
- 输出图像的形状、数据类型、像素总数,理解图像在计算机中的存储形式
2.2.2 灰度图读取与保存
python
# 读取灰度图(参数cv2.IMREAD_GRAYSCALE)
b = cv2.imread(r'D:\software\Pycharm\zc.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('灰度图', b)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 查看灰度图属性(单通道)
print("灰度图形状:", b.shape) # 例:(720, 1080)
print("灰度图数据类型:", b.dtype)
print("灰度图像素总数:", b.size)
# 保存灰度图
cv2.imwrite('D:\software\Pycharm\zc_GRAY.jpg', b)
目标结果:
• 显示单通道灰度图(无色彩,仅亮度);
• 保存灰度图到指定路径,验证图像保存功能。
2.2.3 ROI 区域提取(感兴趣区域)
python
"""
ROI:区域感兴趣(Region of Interest)的缩写。
它指的是图像或视频中感兴趣的特定区域,需要进行分析或处理。
ROI可以由用户手动选择,也可以使用计算机视觉算法自动检测。
"""
a = cv2.imread(r'D:\software\Pycharm\zc.jpg')
# 截取[y1:y2, x1:x2]区域(注意:OpenCV中先高后宽)
b = a[30:400, 100:500]
cv2.imshow('原图', a)
cv2.imshow('ROI截取区域', b)
cv2.waitKey(0)
cv2.destroyAllWindows()
原理:通过数组切片提取图像局部区域,聚焦关键目标(如只处理人脸区域)。
目标结果:
• 同时显示原图和截取的局部区域,验证 ROI 提取效果。
2.2.4 BGR 通道拆分与合并
python
"""
提取RGB颜色通道(注意:OpenCV默认通道顺序为BGR)
"""
# 1. 读取图像
a = cv2.imread(r'D:\software\Pycharm\zc.jpg')
# 2. 提取颜色通道(两种方式)
# 方式1:数组切片
a1 = a[:, :, 0] # 蓝色通道
a2 = a[:, :, 1] # 绿色通道
a3 = a[:, :, 2] # 红色通道
# 方式2:cv2.split()
b, g, r = cv2.split(a)
# 3. 显示单通道(灰度图形式,亮度对应通道值)
cv2.imshow('蓝色通道', b)
cv2.imshow('绿色通道', g)
cv2.imshow('红色通道', r)
cv2.waitKey(0)
cv2.destroyAllWindows()
"""
注意:单通道显示为灰色,因为仅保留亮度值;
若要显示彩色单通道,需将其他通道置0
"""
# 保留蓝色通道(绿、红通道置0)
a_new = a.copy()
a_new[:, :, 1] = 0 # 绿色通道置0
a_new[:, :, 2] = 0 # 红色通道置0
cv2.imshow('仅保留蓝色通道', a_new)
cv2.waitKey(0)
# 保留绿色通道(蓝、红通道置0)
a_new2 = a.copy()
a_new2[:, :, 0] = 0
a_new2[:, :, 2] = 0
cv2.imshow('仅保留绿色通道', a_new2)
cv2.waitKey(0)
# 保留红色通道(蓝、绿通道置0)
a_new3 = a.copy()
a_new3[:, :, 0] = 0
a_new3[:, :, 1] = 0
cv2.imshow('仅保留红色通道', a_new3)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 4. 合并通道
img = cv2.merge((b, g, r)) # 合并为彩色图
cv2.imshow('合并后彩色图', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
原理:
• 彩色图像的每个像素由 B(蓝)、G(绿)、R(红)三个通道值组成(0-255);
• 单通道显示时,通道值作为亮度值,因此呈现灰度;
• 保留单一彩色通道需将其他通道置 0,恢复彩色显示。
目标结果:
• 分别显示单通道灰度图和保留单一彩色通道的彩色图;
• 验证通道拆分与合并的可逆性。
2.2.5 图像修改(打码、拼接、缩放)
python
"""
图片修改:打码、拼接、缩放
"""
# 1. 图片打码(随机像素填充)
a = cv2.imread(r'D:\software\Pycharm\zc.jpg')
# 生成3×3随机像素(示例)
xx = np.random.randint(0, 256, (3, 3))
print("3×3随机像素矩阵:", xx)
# 目标区域填充随机像素(50:400行,200:500列)
a[50:400, 200:500] = np.random.randint(10, 205, (350, 300, 3))
cv2.imshow('打码后的图像', a)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 2. 图片拼接(将bb嵌入aa)
aa = cv2.imread('zc.jpg')
bb = cv2.imread('zx_min.jpg')
# 注意:嵌入区域尺寸需匹配(aa的100:550行对应bb的0:450行,150:450列对应50:350列)
aa[100:550, 150:450] = bb[0:450, 50:350]
cv2.imshow('被嵌入的图像bb', bb)
cv2.imshow('拼接后的图像aa', aa)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 3. 图片缩放(cv2.resize)
"""
cv2.resize参数说明:
- src: 输入图像
- dsize: 输出尺寸(宽,高)
- fx/fy: 缩放系数(dsize为None时生效)
"""
bb = cv2.imread('zx.jpg')
bb_new = cv2.resize(bb, (400, 555)) # 宽400,高555
cv2.imshow('原始图像', bb)
cv2.imshow('缩放后图像', bb_new)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 保存缩放后的图像
cv2.imwrite('D:\software\Pycharm\zx_min.jpg', bb_new)
原理:
- 打码:用随机像素覆盖目标区域,实现隐私保护;
- 拼接:通过数组赋值将一张图像的局部区域嵌入另一张;
- 缩放:插值计算调整图像尺寸,支持固定尺寸或比例缩放。
目标结果:
• 打码:目标区域呈现随机噪点效果;
• 拼接:一张图像的内容嵌入另一张的指定位置;
• 缩放:图像尺寸按指定大小调整,保存后可验证。
三、核心模块 2:图像运算
3.1 概念与原理
图像运算包括边界填充、像素加法、加权加法、阈值处理,核心原理:
• 边界填充:为图像添加额外边框,解决处理中边缘失真问题;
• 像素加法:分为 "直接加法"(截断规则)和 "cv2.add"(饱和规则);
• 加权加法:按权重融合两幅图像,实现渐变效果;
• 阈值处理:将灰度图转为二值图(黑白),是轮廓检测的基础。
3.2 实战代码与目标结果
3.2.1 边界填充(cv2.copyMakeBorder)
python
"""
cv2.copyMakeBorder():给图像添加额外的边界(padding)
参数说明:
- src: 原始图像
- top/bottom/left/right: 各方向边框宽度
- borderType: 边框类型:
- BORDER_CONSTANT: 固定颜色填充(需指定value)
- BORDER_REFLECT: 镜面反射(gfedcba|abcdefgh|hgfedcba)
- BORDER_REFLECT_101: 镜面反射(无重复,gfedcb|abcdefgh|gfedcba)
- BORDER_REPLICATE: 边界像素延伸(aaaaaa|abcdefgh|hhhhhhh)
- BORDER_WRAP: 循环填充(cdefgh|abcdefgh|abcdefg)
- value: 固定填充颜色(BGR格式)
"""
import cv2
import numpy as np
zm = cv2.imread('zx_min.jpg')
# 设置各方向边框宽度(均为50像素)
top, bottom, left, right = 50, 50, 50, 50
# 1. 固定颜色填充(粉色:B=299, G=25, R=80)
constant = cv2.copyMakeBorder(zm, top, bottom, left, right,
borderType=cv2.BORDER_CONSTANT, value=(299,25,80))
# 2. 镜面反射填充
reflect = cv2.copyMakeBorder(zm, top, bottom, left, right, borderType=cv2.BORDER_REFLECT)
# 3. 镜面反射填充(无重复)
reflect101 = cv2.copyMakeBorder(zm, top, bottom, left, right, borderType=cv2.BORDER_REFLECT101)
# 4. 边界像素延伸填充
replicate = cv2.copyMakeBorder(zm, top, bottom, left, right, borderType=cv2.BORDER_REPLICATE)
# 5. 循环填充
wrap = cv2.copyMakeBorder(zm, top, bottom, left, right, borderType=cv2.BORDER_WRAP)
# 显示结果
cv2.imshow('原始图像', zm)
cv2.imshow('CONSTANT(固定颜色)', constant)
cv2.imshow('REFLECT(镜面反射)', reflect)
cv2.imshow('REFLECT101(无重复反射)', reflect101)
cv2.imshow('REPLICATE(边界延伸)', replicate)
cv2.imshow('WRAP(循环填充)', wrap)
cv2.waitKey(0)
cv2.destroyAllWindows()
目标结果:
- 显示原始图像和 5 种填充效果的图像,直观对比不同填充方式的差异;
- 理解不同填充方式的适用场景(如边界延伸用于避免边缘失真,固定颜色用于添加边框)。
3.2.2 图像加法运算
python
"""
图像加法运算规则:
1. 直接加法(+):
- 像素和 < 255:结果=像素和
- 像素和 ≥ 255:结果=像素和 - 256(截断)
2. cv2.add():
- 像素和 < 255:结果=像素和
- 像素和 ≥ 255:结果=255(饱和)
3. 加权加法(cv2.addWeighted):
- 公式:dst = src1×α + src2×β + γ(γ为亮度偏移)
"""
import cv2
import numpy as np
# 1. 单图像像素偏移(+10)
a = cv2.imread('zx_min.jpg')
c = a + 10 # 所有像素值+10(亮度提升)
cv2.imshow('原始图像', a)
cv2.imshow('a+10(亮度提升)', c)
cv2.waitKey(0)
# 2. 两图像局部区域加法(直接+)
b = cv2.imread('zx_min.jpg')
c = a[50:450, 50:400] + b[50:450, 50:400]
cv2.imshow('a+b(局部直接加法)', c)
cv2.waitKey(0)
# 3. cv2.add()加法(饱和规则)
a = cv2.imread('lp.png')
b = cv2.imread('zx_min.jpg')
# 统一尺寸(200×200)
b = cv2.resize(b, (200, 200))
a = cv2.resize(a, (200, 200))
c = cv2.add(a, b)
cv2.imshow('cv2.add(a,b)(饱和加法)', c)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 4. 加权加法(图像融合)
a = cv2.imread('lp.png')
b = cv2.imread('zx_min.jpg')
# 统一尺寸(400×400)
b = cv2.resize(b, (400, 400))
a = cv2.resize(a, (400, 400))
# 权重均为0.5,亮度偏移10
c = cv2.addWeighted(a, 0.5, b, 0.5, 10)
cv2.imshow('加权融合(0.5+0.5)', c)
cv2.waitKey(0)
cv2.destroyAllWindows()
原理:
• 直接加法易出现像素值截断(如 260→4),导致图像失真;
• cv2.add () 采用饱和规则,避免失真,更符合人眼视觉;
• 加权加法可实现两幅图像的平滑融合(如水印添加、图像渐变)。
目标结果:
• 验证不同加法方式的效果差异;
• 实现两幅图像的加权融合,呈现半透明叠加效果。
3.2.3 阈值处理(二值化)
python
"""
cv2.threshold():将灰度图转为二值图
参数说明:
- src: 灰度图像
- thresh: 阈值(150)
- maxval: 最大值(255)
- type: 阈值类型:
- THRESH_BINARY: 像素>阈值→255,否则→0
- THRESH_BINARY_INV: 像素>阈值→0,否则→255(反二值)
- THRESH_TRUNC: 像素>阈值→阈值,否则不变(截断)
- THRESH_TOZERO: 像素<阈值→0,否则不变(归零)
- THRESH_TOZERO_INV: 像素>阈值→0,否则不变(反归零)
"""
import cv2
# 读取灰度图
image = cv2.imread('lpp.png', 0)
# 不同阈值类型处理
ret, binary = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY)
ret1, binaryinv = cv2.threshold(image, 150, 255, cv2.THRESH_BINARY_INV)
ret2, trunc = cv2.threshold(image, 150, 255, cv2.THRESH_TRUNC)
ret3, tozero = cv2.threshold(image, 150, 255, cv2.THRESH_TOZERO)
ret4, tozeroinv = cv2.threshold(image, 150, 255, cv2.THRESH_TOZERO_INV)
# 显示结果
cv2.imshow('原始灰度图', image)
cv2.imshow('THRESH_BINARY(二值化)', binary)
cv2.imshow('THRESH_BINARY_INV(反二值)', binaryinv)
cv2.imshow('THRESH_TRUNC(截断)', trunc)
cv2.imshow('THRESH_TOZERO(归零)', tozero)
cv2.imshow('THRESH_TOZERO_INV(反归零)', tozeroinv)
cv2.waitKey(0)
cv2.destroyAllWindows()
原理:
• 阈值处理的核心是根据像素值与阈值的关系,将灰度图转为高对比度的二值图;
• 不同阈值类型适用于不同场景(如反二值化适用于黑底白字的文本识别)。
目标结果:
• 显示 6 张图像(原图 + 5 种阈值效果),直观理解不同阈值类型的差异;
• 掌握二值化的核心参数调整方法,为后续轮廓检测做准备。
四、核心模块 3:图像平滑(去噪)
4.1 概念与原理
图像平滑(模糊处理)是通过消除噪声或细节使图像模糊的操作,核心原理:
• 噪声生成:模拟真实场景中的椒盐噪声(随机黑白亮点);
• 均值滤波:邻域像素平均,简单但易模糊边缘;
• 方框滤波:可选择是否归一化(归一化 = 均值滤波);
• 高斯滤波:加权平均(中心像素权重高),保留边缘的同时去噪;
• 中值滤波:邻域像素中值,对椒盐噪声最优(非线性滤波)。
4.2 实战代码与目标结果
python
"""
图像平滑(smoothing)/模糊处理(bluring):
核心滤波器:
- 均值滤波:cv2.blur()
- 方框滤波:cv2.boxFilter()
- 高斯滤波:cv2.GaussianBlur()
- 中值滤波:cv2.medianBlur()
"""
import cv2
import numpy as np
# 定义添加椒盐噪声的函数
def add_peppersalt_noise(image, n=10000):
result = image.copy() # 避免修改原图
h, w = image.shape[:2] # 获取图像高、宽
for i in range(n):
# 随机生成噪声点坐标
x = np.random.randint(0, h)
y = np.random.randint(0, w)
# 随机生成黑色(0)或白色(255)噪声
if np.random.randint(0, 2) == 0:
result[x, y] = 0
else:
result[x, y] = 255
return result
# 1. 生成含椒盐噪声的图像
image = cv2.imread('zx_min.jpg')
cv2.imshow('原始图像', image)
noise = add_peppersalt_noise(image)
cv2.imshow('含椒盐噪声的图像', noise)
cv2.waitKey(0)
# 2. 均值滤波(注释可取消运行)
# blur_1 = cv2.blur(noise, (3, 3)) # 3×3核
# cv2.imshow('均值滤波(3×3)', blur_1)
# blur_2 = cv2.blur(noise, (63, 63)) # 63×63核(模糊更强)
# cv2.imshow('均值滤波(63×63)', blur_2)
# cv2.waitKey(0)
# 3. 方框滤波
boxFilter_1 = cv2.boxFilter(noise, -1, (3, 3), normalize=True) # 归一化(=均值滤波)
cv2.imshow('方框滤波(归一化)', boxFilter_1)
boxFilter_2 = cv2.boxFilter(noise, -1, (3, 3), normalize=False) # 不归一化(像素值易溢出)
cv2.imshow('方框滤波(不归一化)', boxFilter_2)
cv2.waitKey(0)
# 4. 高斯滤波
GaussianB = cv2.GaussianBlur(noise, (3, 3), 1) # 3×3核,标准差1
cv2.imshow('高斯滤波', GaussianB)
cv2.waitKey(0)
# 5. 中值滤波(修正原代码错误:cv2.medianBlur而非GaussianBlur)
medianB = cv2.medianBlur(noise, 3) # 核大小为奇数
cv2.imshow('中值滤波(椒盐噪声最优)', medianB)
cv2.waitKey(0)
cv2.destroyAllWindows()
原理补充:
- 中值滤波对椒盐噪声的去噪效果远优于其他滤波(直接剔除极端值);
- 高斯滤波更适合去除高斯噪声(自然模糊),保留图像边缘细节。目标结果:
- 生成含椒盐噪声的图像,验证不同滤波方式的去噪效果;
- 理解核大小对滤波效果的影响(核越大,模糊越强);
- 掌握中值滤波在椒盐噪声去噪中的核心优势
五、核心模块 4:形态学操作
5.1 概念与原理
形态学操作是基于形状的图像处理方法,核心原理:
• 腐蚀:收缩前景区域(去除小亮点、细化边缘);
• 膨胀:扩张前景区域(填补小孔洞、加粗边缘);
• 开运算:腐蚀 + 膨胀(去除小亮斑,保留大目标);
• 闭运算:膨胀 + 腐蚀(填补小孔洞,保留大目标);
• 梯度运算:膨胀 - 腐蚀(提取目标边缘);
• 顶帽:原图 - 开运算(提取亮细节);
• 黑帽:闭运算 - 原图(提取暗细节)。
5.2 实战代码与目标结果
python
"""
形态学操作核心API:
- 腐蚀:cv2.erode()
- 膨胀:cv2.dilate()
- 组合运算:cv2.morphologyEx()
"""
import cv2
import numpy as np
# 1. 图像腐蚀
"""
cv2.erode参数:
- src: 输入图像
- kernel: 结构元素(核)
- iterations: 迭代次数(次数越多,腐蚀越强)
"""
sun = cv2.imread('sun.png')
cv2.imshow('原始图像', sun)
kernel = np.ones((3, 3), np.uint8) # 3×3核
erosion_1 = cv2.erode(sun, kernel, iterations=2) # 迭代2次
cv2.imshow('腐蚀效果(2次迭代)', erosion_1)
cv2.waitKey(0)
# 2. 图像膨胀
"""
cv2.dilate参数:
- img: 目标图像
- kernel: 结构元素
- iterations: 迭代次数
"""
wenzi = cv2.imread('wenzi.png')
cv2.imshow('原始文字图像', wenzi)
kernel = np.ones((2, 2), np.uint8) # 2×2核
wenzi_new = cv2.dilate(wenzi, kernel, iterations=2) # 迭代2次
cv2.imshow('膨胀效果(2次迭代)', wenzi_new)
cv2.waitKey(0)
# 3. 开运算(腐蚀+膨胀)
zhiwen = cv2.imread('zhiwen.png')
cv2.imshow('原始指纹图像', zhiwen)
kernel = np.ones((2, 2), np.uint8)
zhiwen_new = cv2.morphologyEx(zhiwen, cv2.MORPH_OPEN, kernel) # 开运算
cv2.imshow('开运算(去除小亮斑)', zhiwen_new)
cv2.waitKey(0)
# 4. 闭运算(膨胀+腐蚀)
zhiwen_duan = cv2.imread('zhiwen_duan.png')
cv2.imshow('断裂指纹图像', zhiwen_duan)
kernel = np.ones((4, 4), np.uint8)
zhiwen_new = cv2.morphologyEx(zhiwen_duan, cv2.MORPH_CLOSE, kernel) # 闭运算
cv2.imshow('闭运算(填补断裂)', zhiwen_new)
cv2.waitKey(0)
# 5. 梯度运算(膨胀-腐蚀)
wenzi = cv2.imread('wenzi.png')
kernel = np.ones((2, 2), np.uint8)
pz_wenzi = cv2.dilate(wenzi, kernel, iterations=1) # 膨胀
fs_wenzi = cv2.erode(wenzi, kernel, iterations=1) # 腐蚀
bianyuan = cv2.morphologyEx(wenzi, cv2.MORPH_GRADIENT, kernel) # 梯度运算
cv2.imshow('膨胀后的文字', pz_wenzi)
cv2.imshow('腐蚀后的文字', fs_wenzi)
cv2.imshow('梯度运算(提取边缘)', bianyuan)
cv2.waitKey(0)
# 6. 顶帽与黑帽运算
sun = cv2.imread('sun.png')
cv2.imshow('原始太阳图像', sun)
kernel = np.ones((2, 2), np.uint8)
# 开运算
open_sun = cv2.morphologyEx(sun, cv2.MORPH_OPEN, kernel)
# 顶帽(提取亮细节)
tophat = cv2.morphologyEx(sun, cv2.MORPH_TOPHAT, kernel)
# 闭运算
close_sun = cv2.morphologyEx(sun, cv2.MORPH_CLOSE, kernel)
# 黑帽(提取暗细节)
blackhat = cv2.morphologyEx(sun, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('开运算结果', open_sun)
cv2.imshow('顶帽运算(亮细节)', tophat)
cv2.imshow('闭运算结果', close_sun)
cv2.imshow('黑帽运算(暗细节)', blackhat)
cv2.waitKey(0)
cv2.destroyAllWindows()
目标结果:
• 腐蚀:前景区域收缩,去除小噪点;
• 膨胀:前景区域扩张,填补小孔洞;
• 开运算:去除指纹图像中的小亮斑,保留核心纹理;
• 闭运算:填补断裂的指纹纹理,恢复完整形状;
• 梯度运算:提取文字的边缘轮廓;
• 顶帽 / 黑帽:分别提取图像中的亮细节和暗细节
六、核心模块 5:边缘检测
6.1 概念与原理
边缘检测是提取图像中目标轮廓的核心操作,核心原理:
• Sobel 算子:计算一阶导数,检测水平 / 垂直边缘(需保留负数梯度);
• Scharr 算子:Sobel 的优化版,更高精度的边缘检测;
• Canny 算子:多阶段优化(去噪→非极大值抑制→双阈值筛选),边缘检测更精准。
6.2 实战代码与目标结果
6.2.1 Sobel 算子
python
"""
Sobel算子参数:
- src: 输入图像
- ddepth: 输出深度(CV_64F保留负数梯度)
- dx/dy: 导数方向(1,0为x方向,0,1为y方向)
- ksize: 算子大小(1,3,5,7,默认3)
"""
import cv2
import numpy as np
yuan = cv2.imread('yuan.png')
cv2.imshow('原始图像', yuan)
# 1. x方向边缘(默认深度,丢失负数梯度)
yuan_x = cv2.Sobel(yuan, -1, dx=1, dy=0)
cv2.imshow('x方向边缘(默认深度)', yuan_x)
cv2.waitKey(0)
# 2. x方向边缘(CV_64F保留负数,转换为绝对值)
yuan_x_64 = cv2.Sobel(yuan, cv2.CV_64F, 1, 0) # 保留负数梯度
yuan_x_full = cv2.convertScaleAbs(yuan_x_64) # 绝对值转换(显示完整边缘)
cv2.imshow('x方向边缘(完整)', yuan_x_full)
cv2.waitKey(0)
# 3. y方向边缘
yuan_y_64 = cv2.Sobel(yuan, cv2.CV_64F, 0, 1)
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)
cv2.imshow('y方向边缘(完整)', yuan_y_full)
cv2.waitKey(0)
# 4. 合并x/y方向边缘
yuan_xy = cv2.Sobel(yuan, -1, 1, 1) # 不建议直接同时计算
cv2.imshow('x+y方向边缘(直接计算)', yuan_xy)
yuan_xy_full = cv2.addWeighted(yuan_x_full, 1, yuan_y_full, 1, 0) # 加权合并
cv2.imshow('x+y方向边缘(加权合并)', yuan_xy_full)
cv2.waitKey(0)
# 5. 灰度图Sobel检测
lp = cv2.imread('lp.png', 0)
lp_x_64 = cv2.Sobel(lp, cv2.CV_64F, dx=1, dy=0)
lp_x_full = cv2.convertScaleAbs(lp_x_64)
lp_y_64 = cv2.Sobel(lp, cv2.CV_64F, dx=0, dy=1)
lp_y_full = cv2.convertScaleAbs(lp_y_64)
lp_xy_sobel_full = cv2.addWeighted(lp_x_full, 1, lp_y_full, 1, 0)
cv2.imshow('灰度图', lp)
cv2.imshow('lp_x边缘', lp_x_full)
cv2.imshow('lp_y边缘', lp_y_full)
cv2.imshow('lp_xy边缘', lp_xy_sobel_full)
cv2.waitKey(0)
cv2.destroyAllWindows()
原理补充: Sobel 算子的cv2.CV_64F深度可保存边缘另一侧的负数梯度(如亮→暗的边缘); convertScaleAbs将负数转换为正数,确保边缘完整显示;
加权合并 x/y 方向边缘比直接计算更清晰。
目标结果: 分别显示 x、y 方向边缘,以及合并后的完整边缘; 理解灰度图边缘检测的优势(计算量更小)。
6.2.2 Scharr 算子
python
"""
Scharr算子:Sobel的优化版,固定3×3核,精度更高
"""
import cv2
import numpy as np
lp = cv2.imread('lp.png', cv2.IMREAD_GRAYSCALE)
# 修正原代码错误:灰度图无需再次转灰度
# lp = cv2.cvtColor(lp, cv2.COLOR_BGR2GRAY) # 灰度图转灰度会报错,注释掉
# Scharr检测x/y方向边缘
lp_x_64 = cv2.Scharr(lp, cv2.CV_64F, dx=1, dy=0)
lp_x_full = cv2.convertScaleAbs(lp_x_64)
lp_y_64 = cv2.Scharr(lp, cv2.CV_64F, dx=0, dy=1)
lp_y_full = cv2.convertScaleAbs(lp_y_64)
# 合并边缘
lp_xy_sobel_full = cv2.addWeighted(lp_x_full, 1, lp_y_full, 1, 0)
# 显示结果
cv2.imshow('灰度图', lp)
cv2.imshow('Scharr_x边缘', lp_x_full)
cv2.imshow('Scharr_y边缘', lp_y_full)
cv2.imshow('Scharr_xy边缘', lp_xy_sobel_full)
cv2.waitKey(0)
cv2.destroyAllWindows()
关键修正:原代码中对灰度图执行cv2.cvtColor(lp, cv2.COLOR_BGR2GRAY)会报错,需注释掉。目标结果:
• 对比 Sobel,Scharr 算子检测的边缘更细腻、精准;
• 掌握 Scharr 算子在细微边缘检测中的应用。
6.2.3 Canny 边缘检测(摄像头实时)
python
"""
Canny边缘检测:多阶段优化,实时检测首选
参数:
- src: 灰度图
- threshold1: 低阈值
- threshold2: 高阈值
"""
import cv2
import numpy as np
# 打开摄像头
video_capture = cv2.VideoCapture(0)
# 检查摄像头是否成功打开
if not video_capture.isOpened():
print("无法打开摄像头")
exit()
# 循环读取视频帧
while True:
ret, frame = video_capture.read()
if not ret:
print("无法获取帧,退出...")
break
# 1. 转灰度图
gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 2. Canny边缘检测
canny_frame = cv2.Canny(gray_frame, 100, 200) # 调整阈值优化效果
# 3. Sobel边缘检测
sobel_x = cv2.Sobel(gray_frame, cv2.CV_64F, 1, 0, ksize=3)
sobel_y = cv2.Sobel(gray_frame, cv2.CV_64F, 0, 1, ksize=3)
sobel_x = cv2.convertScaleAbs(sobel_x)
sobel_y = cv2.convertScaleAbs(sobel_y)
sobel_frame = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
# 4. Sobel反二值化
_, binary_sobel = cv2.threshold(sobel_frame, 40, 255, cv2.THRESH_BINARY_INV)
# 显示结果
cv2.imshow('Canny边缘检测', canny_frame)
cv2.imshow('Sobel边缘检测', sobel_frame)
cv2.imshow('反向二值化Sobel', binary_sobel)
# ESC键退出
if cv2.waitKey(30) == 27:
break
# 释放资源
video_capture.release()
cv2.destroyAllWindows()
原理补充:
• Canny 算子的双阈值筛选:高于高阈值的为强边缘,低于低阈值的舍弃,中间的需连接到强边缘才保留;
• 实时检测中,调整阈值(如 100/200)可优化边缘完整性。
目标结果:
• 实时显示摄像头画面的 Canny 边缘、Sobel 边缘及反二值化结果;
• 掌握实时边缘检测的核心流程。
七、核心模块 6:轮廓分析
7.1 概念与原理
轮廓是图像中连续的、相同像素值的边界,核心原理:
• 轮廓检测:基于二值图,检测目标的边界点集合;
• 轮廓绘制:将检测到的轮廓绘制在图像上;
• 轮廓特征:面积、周长、外接圆、外接矩形等;
• 轮廓筛选:按面积排序 / 过滤,聚焦核心目标;
• 近似轮廓:简化轮廓点数量(如将不规则轮廓转为多边形)。
7.2 实战代码与目标结果
7.2.1 轮廓检测与绘制
python
"""
轮廓检测API:cv2.findContours()
参数:
- img: 二值图像
- mode: 轮廓检索模式(RETR_TREE:完整层级)
- method: 轮廓近似方法(CHAIN_APPROX_NONE:保存所有点)
返回:
- contours: 轮廓列表(每个轮廓为点坐标数组)
- hierarchy: 轮廓层级
"""
import cv2
import numpy as np
# 1. 读取图像并转二值图
phone = cv2.imread('phone.png')
phone_gray = cv2.cvtColor(phone, cv2.COLOR_BGR2GRAY)
# 阈值处理为二值图
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)
cv2.imshow('灰度图', phone_gray)
cv2.imshow('二值图', phone_binary)
cv2.waitKey(0)
# 2. 检测轮廓(兼容不同OpenCV版本,取[-2])
contours = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2]
print("检测到的轮廓数量:", len(contours))
# 3. 绘制所有轮廓
image_copy = phone.copy()
# contourIdx=-1绘制所有轮廓,color=(0,255,0)绿色,thickness=2
cv2.drawContours(image=image_copy, contours=contours, contourIdx=-1, color=(0,255,0), thickness=2)
cv2.imshow('所有轮廓', image_copy)
cv2.waitKey(0)
# 4. 绘制指定轮廓(索引9)
for i in range(len(contours)):
image_copy = cv2.drawContours(image_copy, contours, contourIdx=9, color=(0,255,0), thickness=2)
cv2.imshow('指定轮廓(索引9)', image_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()
目标结果:
• 显示灰度图、二值图、所有轮廓、指定轮廓;
• 输出轮廓数量,理解轮廓的层级结构。
7.2.2 轮廓特征提取
python
"""
轮廓特征:
- 面积:cv2.contourArea()
- 周长:cv2.arcLength()
- 外接圆:cv2.minEnclosingCircle()
- 外接矩形:cv2.boundingRect()
"""
import cv2
import numpy as np
phone = cv2.imread('phone.png')
phone_gray = cv2.imread('phone.png', 0)
ret, phone_binary = cv2.threshold(phone_gray, 120, 255, cv2.THRESH_BINARY)
contours = cv2.findContours(phone_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2]
# 1. 计算轮廓面积和周长
area_0 = cv2.contourArea(contours[0])
area_1 = cv2.contourArea(contours[1])
length_0 = cv2.arcLength(contours[0], closed=True) # closed=True表示闭合轮廓
print("轮廓0面积:", area_0)
print("轮廓1面积:", area_1)
print("轮廓0周长:", length_0)
# 2. 按面积筛选轮廓(>10000)
a_list = []
for i in contours:
if cv2.contourArea(i) > 10000:
a_list.append(i)
image_copy = phone.copy()
cv2.drawContours(image_copy, a_list, -1, (0,255,0), thickness=3)
cv2.imshow('面积>10000的轮廓', image_copy)
cv2.waitKey(0)
# 3. 按面积排序(取第4个轮廓,索引3)
sortcnt = sorted(contours, key=cv2.contourArea, reverse=True)[3]
image_copy = cv2.drawContours(image_copy, [sortcnt], -1, (0,255,255), thickness=2) # 黄色
cv2.imshow('面积排序第4的轮廓', image_copy)
cv2.waitKey(0)
# 4. 外接圆与外接矩形
cnt = contours[7]
# 外接圆
(x, y), r = cv2.minEnclosingCircle(cnt)
phone_circle = cv2.circle(phone, (int(x), int(y)), int(r), (0,255,0), 1)
cv2.imshow('外接圆', phone_circle)
cv2.waitKey(0)
# 外接矩形
x, y, w, h = cv2.boundingRect(cnt)
phone_rectangle = cv2.rectangle(phone, (x, y), (x+w, y+h), (0,255,0), 1)
cv2.imshow('外接矩形', phone_rectangle)
cv2.waitKey(0)
cv2.destroyAllWindows()
目标结果:
- 输出轮廓的面积、周长;
- 显示面积筛选后的轮廓、排序后的轮廓、外接圆、外接矩形;
- 掌握轮廓特征在目标定位中的应用。
7.2.3 近似轮廓(花束轮廓)
python
"""
近似轮廓:cv2.approxPolyDP()
参数:
- curve: 原始轮廓
- epsilon: 近似精度(周长的比例)
- closed: 是否闭合
"""
import cv2
import numpy as np
# 1. 读取图像并转二值图
hua = cv2.imread('hua.png')
cv2.imshow('原始花束图像', hua)
hua_gray = cv2.cvtColor(hua, cv2.COLOR_BGR2GRAY)
# 反二值化(花朵为前景)
ret, hua_binary = cv2.threshold(hua_gray, 240, 255, cv2.THRESH_BINARY_INV)
cv2.imshow('花束二值图', hua_binary)
cv2.waitKey(0)
# 2. 检测外轮廓
contours = cv2.findContours(hua_binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
print("花束轮廓数量:", len(contours))
# 3. 取面积最大的轮廓
sortcnt = sorted(contours, key=cv2.contourArea, reverse=True)[0]
image_copy = hua.copy()
# 绘制原始轮廓(红色)
hua_contours = cv2.drawContours(image_copy, [sortcnt], -1, (0,0,255), 3)
cv2.imshow('花束原始轮廓', hua_contours)
cv2.waitKey(0)
# 4. 计算近似轮廓
epsilon = 0.005 * cv2.arcLength(sortcnt, True) # 周长的0.5%
approx = cv2.approxPolyDP(sortcnt, epsilon, True)
print("原始轮廓点数量:", sortcnt.shape)
print("近似轮廓点数量:", approx.shape)
# 5. 绘制近似轮廓(绿色)
hua_contours = cv2.drawContours(image_copy, [approx], -1, (0,255,0), 3)
cv2.imshow('花束近似轮廓', hua_contours)
cv2.waitKey(0)
cv2.destroyAllWindows()
原理补充:
epsilon越小,近似轮廓越接近原始轮廓;- 近似轮廓可大幅减少轮廓点数量,简化后续计算。目标结果:
- 显示花束的原始轮廓和近似轮廓;
- 输出轮廓点数量,验证近似轮廓的简化效果。
八、核心模块 7:视频处理
8.1 概念与原理
视频是连续的图像帧序列,视频处理的核心是 "逐帧读取→逐帧处理→逐帧显示",核心原理:
- 视频读取:
cv2.VideoCapture加载视频文件 / 摄像头; - 逐帧处理:对每一帧执行图像操作(如去噪、边缘检测);
- 实时显示:通过循环显示处理后的帧,实现视频流效果。
8.2 实战代码与目标结果
8.2.1 视频文件读取与灰度转换
python
"""
视频文件读取核心流程:
1. 打开视频:cv2.VideoCapture()
2. 循环读取帧:read()
3. 处理帧:转灰度、去噪等
4. 显示帧:imshow()
5. 释放资源:release()
"""
import cv2
video_capture = cv2.VideoCapture('转场.mp4')
# 检查视频是否打开
if not video_capture.isOpened():
print("无法打开视频文件")
exit()
# 逐帧读取
while True:
ret, frame = video_capture.read()
if not ret: # 视频结束
break
# 转灰度图
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 显示帧
cv2.imshow('Video(灰度)', frame)
# ESC键退出(60ms/帧,控制播放速度)
if cv2.waitKey(60) == 27:
break
# 释放资源
video_capture.release()
cv2.destroyAllWindows()
目标结果:
• 播放视频文件,并将每一帧转为灰度图显示;
• 掌握视频播放的核心控制逻辑(退出、播放速度)。
8.2.2 摄像头实时添加椒盐噪声并去噪
python
"""
实时视频处理:添加椒盐噪声 + 中值滤波去噪
"""
import cv2
import numpy as np
# 定义添加椒盐噪声的函数(适配彩色/灰度图)
def add_peppersalt_noise(image, n=10000):
result = image.copy()
h, w = image.shape[:2]
for i in range(n):
x = np.random.randint(0, h)
y = np.random.randint(0, w)
# 彩色图(3通道)/灰度图(单通道)适配
if len(image.shape) == 3:
result[x, y] = [0, 0, 0] if np.random.randint(0, 2) == 0 else [255, 255, 255]
else:
result[x, y] = 0 if np.random.randint(0, 2) == 0 else 255
return result
# 打开视频文件/摄像头(摄像头用0)
video_capture = cv2.VideoCapture('test.avi')
if not video_capture.isOpened():
print("无法打开视频文件/摄像头!")
exit()
# 逐帧处理
while True:
ret, frame = video_capture.read()
if not ret:
break
# 添加椒盐噪声
noise_frame = add_peppersalt_noise(frame, n=10000)
# 中值滤波去噪
median = cv2.medianBlur(noise_frame, 3)
# 显示结果
cv2.imshow('原视频', frame)
cv2.imshow('噪声处理的视频', noise_frame)
cv2.imshow('平滑处理后的视频', median)
# ESC键退出
if cv2.waitKey(60) == 27:
break
# 释放资源
video_capture.release()
cv2.destroyAllWindows()
目标结果:
• 实时显示原始视频、含椒盐噪声的视频、去噪后的视频;
• 验证中值滤波在视频去噪中的效果。
九、总结
本文围绕 OpenCV 图像处理核心功能,从基础操作到高级分析,系统讲解了七大模块的概念、原理、代码和目标结果,核心要点总结:
-
基础操作:图像读取 / 保存、ROI 提取、通道拆分 / 合并是所有处理的基础,需掌握 OpenCV 的 BGR 通道顺序;
-
图像运算:阈值处理是二值化核心,加权加法可实现图像融合,边界填充解决边缘失真;
-
图像平滑:中值滤波对椒盐噪声最优,高斯滤波保留边缘,核大小决定模糊强度;
-
形态学操作:腐蚀 / 膨胀是基础,开运算去噪、闭运算补洞、梯度运算提边缘;
-
边缘检测:Canny 算子精度最高(实时检测首选),Sobel/Scharr 适合简单边缘提取;
-
轮廓分析:基于二值图检测轮廓,按面积筛选 / 排序可聚焦核心目标,近似轮廓简化计算;
-
视频处理:核心是 "逐帧读取→处理→显示",可复用所有图像操作逻辑。
掌握这些核心功能后,可拓展至更复杂的场景(如目标检测、图像分割、人脸识别),关键是理解每个操作的原理,并根据业务场景调整参数。