计算机中的图像本质上是由无数像素组成的网格,彩色图像通常通过红(R)、绿(G)、蓝(B)三个独立的颜色通道来记录信息,每个通道都是一个二维数字矩阵,矩阵中的数值代表对应位置像素该颜色的亮度(通常为 0-255),三个通道的数值组合决定了像素最终呈现的颜色,计算机存储的就是这些通道矩阵数据,以及图像的宽、高、通道数等元信息。

1.图片视频基础读取
读取与展示图片
安装 OpenCV 使用 imread 读取图片。
pip install opencv-python

读取图片(核心)grayscale /ˈɡreɪskeɪl/ 灰度;unchanged /ʌnˈtʃeɪndʒd/ 未改变的;shape /ʃeɪp/ 形状
import cv2
# 1. 彩色图(默认 BGR 顺序)
img = cv2.imread("cat.jpg")
# 2. 灰度图
img_gray = cv2.imread("cat.jpg", cv2.IMREAD_GRAYSCALE)
# 3. 含透明通道(PNG)
img_alpha = cv2.imread("cat.png", cv2.IMREAD_UNCHANGED)
检查是否读取成功
if img is None:
print("读取失败,检查路径或文件是否存在")
else:
print("正常形状:", img.shape)
print("灰度形状:", img_gray.shape)
正常形状: (180, 300, 3)
灰度形状: (180, 300)
含透明通道:(180, 300, 4)
显示与保存
cv2.imshow("image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite("save.jpg", img)
cv2.waitKey(0):让图片窗口停住不闪退,等待你按任意键继续。cv2.destroyAllWindows():关掉所有打开的图片窗口,清理内存。
提醒:OpenCV 默认通道顺序是 BGR,不是 RGB。
img 打印出来是一个3维数组
array([[[ 6, 43, 125],
[ 6, 43, 125],
[ 5, 42, 122],
…,
[174, 207, 222],
[174, 207, 222],
[173, 206, 221]]], shape=(180, 300, 3), dtype=uint8)
img_gray 打印出来是一个2维数组
array([[ 63, 63, 62, …, 36, 36, 36],
[ 62, 62, 61, …, 36, 36, 36],
[ 59, 59, 58, …, 36, 36, 36],
…,
[202, 205, 207, …, 206, 205, 205],
[201, 199, 197, …, 207, 206, 206],
[198, 194, 193, …, 208, 208, 207]], shape=(180, 300), dtype=uint8)
获取类型
type(img)
numpy.ndarray
img.size
162000
img.dtype
dtype('uint8')
dtype('uint8') unsigned 8-bit integer(无符号 8 位整数):每个像素值用 1 字节(8 位)无符号整数存储,范围 0–255,只能存非负数,没有负数。
视频基础读取
OpenCV 读取视频和读图片逻辑几乎一样,只是把 cv2.imread 换成视频捕获对象,一帧一帧读取画面。capture /ˈkæptʃər/ 捕获;拍摄 frame /freɪm/ 帧; release /rɪˈliːs/ 释放
import cv2
# 1. 打开视频(可以是本地视频 或 摄像头 0)
cap = cv2.VideoCapture("movie.mp4") # 本地视频
# cap = cv2.VideoCapture(0) # 电脑摄像头(直接用0)
# 检查是否打开成功
if not cap.isOpened():
print("视频打开失败!")
exit()
# 2. 循环读取每一帧
while True:
# 读一帧画面
ret, frame = cap.read()
# 如果读不到帧(视频结束),退出循环
if not ret:
break
#img = cv2.cvtColor(frame, cv2.IMREAD_COLOR)
img = cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY)
cv2.imshow('result', img)
# 按 q 键退出,等待1毫秒刷新
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 3. 释放资源 + 关闭窗口
cap.release()
cv2.destroyAllWindows()
2.图像基本操作
1.截取部分图像数据
OpenCV 里截取图片区域(ROI) 的操作,非常常用!
# 定义显示函数(你代码里用的就是这个)
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 读取 + 截取 + 显示
img = cv2.imread('cat.jpg')
cat = img[0:50, 0:200] # 截左上角:高50,宽200
cv_show('cat', cat)
关键:图像切片规则
- img[ 行范围, 列范围 ]
- img[0:50, 0:200] = 截取
- 高度:第 0 行~第 50 行
- 宽度:第 0 列~第 200 列
- 也就是左上角一小块
2.颜色通道提取
把一张彩色图片,拆分成 蓝、绿、红 三个独立的颜色通道。
b,g,r=cv2.split(img)
- img:你读取的彩色图(BGR 格式)
- cv2.split():通道拆分
- 得到 3 张灰度图:
- b:蓝色通道
- g:绿色通道
- r:红色通道
r、g、b 三个数据格式如下:
array([[125, 125, 122, …, 39, 39, 39],
[121, 121, 120, …, 39, 39, 39],
[114, 112, 111, …, 39, 39, 39],
…,
[213, 216, 218, …, 220, 219, 219],
[212, 210, 208, …, 221, 220, 220],
[209, 205, 204, …, 222, 222, 221]], shape=(180, 300), dtype=uint8)
它们的 shape 都为 (180, 300)
只保留R通道
cur_img = img.copy()
cur_img[:,:,0] = 0
cur_img[:,:,1] = 0
cv_show('R',cur_img)
只保留G通道
cur_img = img.copy()
cur_img[:,:,0] = 0
cur_img[:,:,2] = 0
cv_show('G',cur_img)
只保留B通道
cur_img = img.copy()
cur_img[:,:,1] = 0
cur_img[:,:,2] = 0
cv_show('B',cur_img)
3.边界填充
OpenCV 边界填充就是给图片四周加上一圈边框,常用于深度学习预处理、图像扩展、卷积操作等,用函数 cv2.copyMakeBorder() 实现。
dst = cv2.copyMakeBorder(
img, # 原图
top, # 上边框宽度
bottom, # 下边框宽度
left, # 左边框宽度
right, # 右边框宽度
borderType, # 填充类型
value # 纯色填充时的颜色(BGR)
)
5 种填充类型(最常用)
cv2.BORDER_CONSTANT:纯色填充 、cv2.BORDER_REPLICATE:复制边缘像素填充(最常用)、cv2.BORDER_REFLECT:镜像反射填充、cv2.BORDER_REFLECT_101:更自然的镜像填充、cv2.BORDER_WRAP:平铺重复填充。constant /ˈkɑːnstənt/ 常量;replicate /ˈreplɪkeɪt/ 复制;reflect /rɪˈflekt/ 反射,反光;wrap /ræp/ 包裹
import cv2
# 1. 读取图片
img = cv2.imread('cat.jpg')
# 2. 设置填充大小(上下左右各填充20像素)
top = bottom = left = right = 20
# 3. 5种填充效果
# ① 纯色填充(蓝色:BGR(255,0,0))
constant = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(255,0,0))
# ② 复制边缘填充
replicate = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REPLICATE)
# ③ 镜像反射
reflect = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT)
# ④ 更自然的镜像
reflect101 = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_REFLECT_101)
# ⑤ 平铺重复
wrap = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_WRAP)
显示:
import matplotlib.pyplot as plt
plt.subplot(231), plt.imshow(img, 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
plt.show()
OpenCV 读取是 BGR 顺序,matplotlib 显示是 RGB 顺序,所以直接用 plt.imshow() 颜色会失真、变蓝 / 变绿!只是缺少 BGR 转 RGB,所以颜色不对,加一行 cv2.cvtColor(res, cv2.COLOR_BGR2RGB) 就完美了

BORDER_REPLICATE:复制法,也就是复制最边缘像素。
BORDER_REFLECT:反射法,对感兴趣的图像中的像素在两边进行复制例如:fedcba|abcdefgh|hgfedcb
BORDER_REFLECT_101:反射法,也就是以最边缘像素为轴,对称,gfedcb|abcdefgh|gfedcba
BORDER_WRAP:外包装法cdefgh|abcdefgh|abcdefg
BORDER_CONSTANT:常量法,常数值填充。
最常用:BORDER_REPLICATE(复制边缘)、BORDER_CONSTANT(纯色)
4.数值计算
OpenCV 图片是 uint8 类型,数值范围 0 ~ 255。如果做 +10 运算:
原本 ≤245 的值 → 正常加 10
原本 >245 的值 → 会溢出从头算(255+1=0)
比如:
255 + 10 = 9 (不是 265!)
250 + 10 = 260 → 4
这叫 uint8 溢出(wrap around)。
import cv2
img_cat = cv2.imread('cat.jpg') # 读取图片,dtype=uint8 (0-255)
img_cat2 = img_cat + 10 # 每个像素值 +10
img_cat[:5, :, 0] # 查看:前5行、所有列、第0通道(B) 的像素值
输出:
array([[6, 6, 5, …, 2, 2, 2],
[5, 5, 4, …, 2, 2, 2],
[4, 4, 3, …, 2, 2, 2],
[3, 3, 1, …, 3, 3, 3],
[4, 3, 2, …, 3, 3, 3]], shape=(5, 300), dtype=uint8)
img_cat2[:5,:,0] 输出
array([[16, 16, 15, …, 12, 12, 12],
[15, 15, 14, …, 12, 12, 12],
[14, 14, 13, …, 12, 12, 12],
[13, 13, 11, …, 13, 13, 13],
[14, 13, 12, …, 13, 13, 13]], shape=(5, 300), dtype=uint8)
相当于% 256
(img_cat + img_cat2)[:5,:,0] 输出
array([[22, 22, 20, …, 14, 14, 14],
[20, 20, 18, …, 14, 14, 14],
[18, 18, 16, …, 14, 14, 14],
[16, 16, 12, …, 16, 16, 16],
[18, 16, 14, …, 16, 16, 16]], shape=(5, 300), dtype=uint8)
cv2.add(img_cat,img_cat2)[:5,:,0] 称为 OpenCV 安全加法。
规则:相加超过 255 就截断成 255
公式:min(a + b, 255)
例子:255 + 10 = 265 → 255
5.图像缩放与融合
两张照片可以直接进行相加融合,但是前提条件是两张照片 shape 形状相同。

import cv2
img_cat=cv2.imread('cat.jpg')
img_dog=cv2.imread('dog.jpg')
img_cat + img_dog
运行报错
—————————————————————————
ValueError Traceback (most recent call last)
Cell In[24], line 4
1 import cv2
2 img_cat=cv2.imread('cat.jpg')
3 img_dog=cv2.imread('dog.jpg')
—-> 4 img_cat + img_dog
ValueError: operands could not be broadcast together with shapes (180,300,3) (193,300,3)
使用
import cv2
import matplotlib.pyplot as plt
# 读取图片
img_cat = cv2.imread('cat.jpg')
img_dog = cv2.imread('dog.jpg')
# 把狗的尺寸改成 和猫一样大(必须同尺寸才能融合)
img_dog = cv2.resize(img_dog, (300, 180))
# 图像融合:权重相加
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0)
# 显示
plt.imshow(res)

cv2.addWeighted 图像融合公式
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma)
参数说明
- src1:第一张输入图像
- alpha:第一张图权重(0~1 常用)
- src2:第二张输入图像
- beta:第二张图权重(0~1 常用)
- gamma:亮度偏移量(标量,整体加值)
- dst:输出融合图像
计算公式
dst = src1 × α + src2 × β + γ
关键前提:两张图像尺寸、通道数必须完全一致,否则报错。
OpenCV 读取是 BGR 顺序,matplotlib 显示是 RGB 顺序,所以直接用 plt.imshow() 颜色会失真、变蓝 / 变绿!只是缺少 BGR 转 RGB,所以颜色不对,加一行 cv2.cvtColor(res, cv2.COLOR_BGR2RGB) 就完美了
res_rgb = cv2.cvtColor(res, cv2.COLOR_BGR2RGB)
plt.imshow(res_rgb)

cv2.resize 有两种用法:
1.指定目标尺寸
cv2.resize(img, (宽, 高))
2.指定缩放比例
cv2.resize(img, (0,0), fx=倍数, fy=倍数)
3.图像阈值(二值化)
cv2.threshold 是 OpenCV 里最简单的图像分割方法,把图像变成只有黑和白两种颜色(二值图)。threshold [ˈθreʃhəʊld] 阈值。
ret, dst = cv2.threshold(src, thresh, maxval, type)
src原图(最好是灰度图)
thresh阈值(你自己设定的分割线,比如 127)
maxval最大值(一般填 255)
type阈值类型(决定怎么分割)
thresh /θreʃ/ 脱粒;binary /ˈbaɪnəri/ 二进制的;trunc 截断;tozero 归零
ret 返回你设定的阈值dst处理后的二值图像
import cv2
import matplotlib.pyplot as plt
img = cv2.imread('cat.jpg')
# 2. 将图片转换为灰度图(阈值处理必须使用灰度图)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 3. 将BGR格式转换为RGB格式,方便matplotlib正常显示原图颜色
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 4. 5种不同的阈值处理
# 二进制阈值:大于127(白),小于等于127(黑)
ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
# 反二进制阈值:大于127(黑),小于等于127(白)
ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
# 截断阈值:大于127的部分变为127,小于127不变
ret, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
# 阈值化为0:大于127不变,小于127变为0
ret, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
# 反阈值化为0:大于127变为0,小于127不变
ret, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)
# 5. 定义每个子图的标题
titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
# 把所有图片放进列表中,方便循环显示
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
# 6. 循环绘制6张子图(2行3列)
for i in range(6):
# 绘制子图:2行3列,第i+1个位置
plt.subplot(2, 3, i + 1)
# 显示图片,cmap='gray'表示以灰度图显示
plt.imshow(images[i], 'gray')
# 设置子图标题
plt.title(titles[i])
# 隐藏x轴和y轴刻度
plt.xticks([])
plt.yticks([])
# 7. 显示所有图像
plt.show()
效果图:

4.HSV
HSV 是另一种色彩表达模型,和 RGB/BGR 不同,它更贴合人眼对颜色的感知,由三个分量组成:
- 代表是什么颜色,取值范围 0~179(OpenCV 中)。
- 对应色环:红→黄→绿→青→蓝→紫,循环变化。
- 代表颜色鲜艳程度,取值 0~255。
- 数值越高颜色越浓郁;越低越偏向灰白,0 就是灰度。
- 代表明暗程度,取值 0~255。
- 0 为纯黑,255 为最亮。
hsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
cv2.imshow("hsv", hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()

和 BGR/RGB 的区别
- BGR/RGB:靠三原色混合描述颜色,适合屏幕显示;不适合颜色筛选。
- HSV:把「颜色、鲜艳度、亮度」拆分开,OpenCV 颜色追踪、目标提取首选。
补充小知识点
最简记忆
- H = 什么颜色
- S = 艳不艳
- V = 亮不亮
5.图像平滑(滤波)
图像平滑 / 滤波主要用来降噪、模糊图像,常用四类:均值滤波、高斯滤波、中值滤波、方框滤波。
先统一前置代码:
import cv2
import matplotlib.pyplot as plt
# 读取图片
img = cv2.imread("cat.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 转RGB,方便matplotlib显示
一、均值滤波 cv2.blur()

原理:邻域像素求平均值,简单模糊,降噪同时会丢失细节。 语法:
dst = cv2.blur(src, ksize)
- ksize:卷积核大小 (w, h),必须为正奇数(如 (3,3)、(5,5))
示例:
# 3×3 均值滤波
blur = cv2.blur(img, (3, 3))
二、高斯滤波 cv2.GaussianBlur()
按高斯分布加权平均,中心像素权重更高,模糊更自然

像素原理图

40+107*2+5+198*2+226*8+223*2+37+68*2+193=3275
3275/20=163.75
像素原理图
原理:和均值滤波类似,但每个像素的权重由高斯核决定,离中心越近权重越大
特点:比均值滤波更平滑,是最常用的降噪方法,同样会模糊边缘
最常用,模拟人眼模糊,平滑效果更自然,优先用于普通降噪。 语法:
dst = cv2.GaussianBlur(src, ksize, sigmaX)
- ksize:核大小,宽高必须是奇数
- sigmaX:X 方向高斯标准差,控制模糊程度;设为 0 会自动计算
示例:
# 5×5 高斯核,sigmaX=0
gauss = cv2.GaussianBlur(img, (5, 5), 0)
三、中值滤波 cv2.medianBlur()

专门处理椒盐噪声(黑白噪点),效果极强,同时保留边缘。 原理:取邻域像素中间值替换当前像素。 语法:
dst = cv2.medianBlur(src, ksize)
- ksize:单个数字,奇数(3 / 5 / 7)
# 3×3 中值滤波
median = cv2.medianBlur(img, 3)
四、方框滤波(Box Filter)
基本和均值滤波一样,可以选择是否归一化
box = cv2.boxFilter(img, -1, (3,3), normalize=True)
cv2.imshow('box', box)
cv2.waitKey(0)
cv2.destroyAllWindows()
参数说明
cv2.boxFilter(src, ddepth, ksize, normalize=True)
- img:输入图像
- -1:输出图像的深度(-1 表示和原图一致)
- (3,3):卷积核大小,这里是 3×3
- normalize=True:是否做归一化
二、和均值滤波的关系
方框滤波其实是均值滤波的 “通用版”:
- 当 normalize=True 时,和 cv2.blur() 完全等价
- 公式:输出 = (邻域像素和) / (核大小)
- 当 normalize=False 时,只是做邻域像素和,不除以核大小
- 结果容易超出 0-255 范围,会出现 uint8 溢出(255+1=0)
三、两种模式对比
import cv2
img = cv2.imread('lenaNoise.png')
# 1. 归一化(等价于均值滤波)
box_norm = cv2.boxFilter(img, -1, (3,3), normalize=True)
# 2. 不归一化(直接求和,容易溢出)
box_no_norm = cv2.boxFilter(img, -1, (3,3), normalize=False)
# 3. 对比均值滤波
blur = cv2.blur(img, (3,3))
cv2.imshow('original', img)
cv2.imshow('box normalized', box_norm)
cv2.imshow('box no normalize', box_no_norm)
cv2.imshow('blur', blur)
cv2.waitKey(0)
cv2.destroyAllWindows()
四、关键结论
- cv2.boxFilter + normalize=True = cv2.blur
- normalize=False 只在特殊场景用,普通降噪推荐用归一化模式
- 不归一化的结果很容易因为像素值过大溢出,导致图像变白或出现异常
完整对比代码(一键运行 + 可视化)

import cv2
import matplotlib.pyplot as plt
# 读图并转RGB
img = cv2.imread("lenaNoise.png")
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 四种滤波
blur = cv2.blur(img_rgb, (3, 3)) # 均值
gauss = cv2.GaussianBlur(img_rgb, (5, 5), 0) # 高斯
median = cv2.medianBlur(img_rgb, 3) # 中值
box = cv2.boxFilter(img, -1, (3,3), normalize=False) # 方框
# 绘图展示
titles = ["Original", "Blur", "Gaussian", "Median", "Box"]
imgs = [img_rgb, blur, gauss, median, box]
plt.figure(figsize=(12,6))
for i in range(5):
plt.subplot(2,3,i+1)
plt.imshow(imgs[i])
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

补充要点
- 核尺寸越大,模糊程度越强。
- 均值、高斯会把边缘一起模糊掉;中值能较好保留边缘。
6.腐蚀操作(Erosion)
腐蚀是形态学处理的基础操作,核心作用是收缩图像中亮区域、消除细小噪声、断开粘连物体边缘,常用来处理二值图像。erosion [ɪˈroʊʒn] 腐蚀
如下是一个十字核(结构B)在原图上腐蚀的过程,结构B的中心点会在结构A上每个位置扫一遍,只要出现有一个绿色和红色点位对不上就会导致该点位被腐蚀。

一、腐蚀操作原理
和你之前看的卷积类似,腐蚀也是用一个 ** 结构元素(核)** 在图像上滑动:
二、OpenCV 核心函数
cv2.erode(src, kernel, iterations=1)
- src:输入图像(一般是二值图,也支持灰度 / 彩色图)
- kernel:腐蚀用的结构元素(核),决定腐蚀的形状和大小
- iterations:腐蚀次数,次数越多,图像收缩越明显
常用核的创建方式
# 1. 3×3 全1正方形核(最常用)
kernel = np.ones((3,3), np.uint8)
# 2. 自定义形状核(如十字形、圆形,用 cv2.getStructuringElement)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3)) # 矩形核
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (3,3)) # 十字核
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)) # 圆形核
三、完整可运行示例

import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像(推荐用带噪声/边缘粘连的二值图)
img = cv2.imread('noise.jpg', cv2.IMREAD_GRAYSCALE)
# 2. 创建腐蚀核
kernel = np.ones((3,3), np.uint8)
# 3. 执行腐蚀操作(iterations=1,腐蚀1次)
erosion1 = cv2.erode(img, kernel, iterations=1)
# 腐蚀2次
erosion3 = cv2.erode(img, kernel, iterations=2)
# 4. 对比显示
titles = ['Original Binary', 'Erosion 1x', 'Erosion 3x']
images = [img, erosion1, erosion3]
for i in range(3):
plt.subplot(1,3,i+1)
plt.imshow(images[i], cmap='gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

四、关键效果与用途
| 去除椒盐噪声 | 细小的白色噪点会被腐蚀消失 |
| 断开粘连物体 | 两个靠得近的物体,边缘被腐蚀后会分开 |
| 细化轮廓 | 文字、线条会变细,适合骨架提取前处理 |
注意:腐蚀次数不是越多越好,过度腐蚀会让目标物体被过度收缩,甚至消失。
五、和膨胀操作的关系(延伸)
腐蚀的 “反向操作” 是膨胀(cv2.dilate),它会让亮区域变大。dilate [daɪˈleɪt ] 扩张
- 腐蚀 + 膨胀组合:开运算(先腐蚀后膨胀,去噪同时保留物体大小)
- 膨胀 + 腐蚀组合:闭运算(先膨胀后腐蚀,填充孔洞、连接断裂)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像(推荐用带噪声/边缘粘连的二值图)
img = cv2.imread('noise.jpg', cv2.IMREAD_GRAYSCALE)
# 2. 创建腐蚀核
kernel = np.ones((3,3), np.uint8)
# 3. 执行腐蚀操作(iterations=1,腐蚀1次)
erosion1 = cv2.erode(img, kernel, iterations=1)
# 膨胀1次
dilate = cv2.dilate(erosion1,kernel,iterations = 1)
# 4. 对比显示
titles = ['Original Binary', 'Erosion 1x', 'Dilate']
images = [img, erosion1, dilate]
for i in range(3):
plt.subplot(1,3,i+1)
plt.imshow(images[i], cmap='gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

开运算(Open Operation)
定义:先腐蚀,再膨胀
公式:开运算 = 膨胀(腐蚀(原图))
作用:去除小的白色噪声点,同时保留物体整体大小
场景:去噪、断开物体间的细小粘连
morphology /mɔːrˈfɑːlədʒi/ 形态学
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像(推荐用带噪声/边缘粘连的二值图)
img = cv2.imread('noise.jpg', cv2.IMREAD_GRAYSCALE)
# 2. 创建腐蚀核
kernel = np.ones((3,3), np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 4. 对比显示
titles = ['Original Binary', 'Opening']
images = [img, opening]
for i in range(2):
plt.subplot(1,2,i+1)
plt.imshow(images[i], cmap='gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

闭运算(Close Operation)
定义:先膨胀,再腐蚀
公式:闭运算 = 腐蚀(膨胀(原图))
作用:填充物体内部的小黑洞,连接断裂的线条
场景:修复孔洞、连接断开的物体
# 闭运算
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
1. 形态学梯度(Gradient)
公式:膨胀 – 腐蚀
- 效果:提取物体的轮廓边缘
- 场景:边缘检测、轮廓提取
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
2. 顶帽运算(Top Hat)
公式:原图 – 开运算
- 效果:提取原图中比背景亮的细小部分(比如白色噪声、纹理)
- 场景:提取亮细节、校正光照不均
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
3. 黑帽运算(Black Hat)
公式:闭运算 – 原图
- 效果:提取原图中比背景暗的细小部分(比如物体内部的小黑洞)
- 场景:提取暗细节、孔洞检测
blackhat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
完整对比代码(一键运行)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 生成带噪声、粘连和孔洞的演示二值图
h, w = 200, 300
img = np.zeros((h, w), dtype=np.uint8)
# 两个粘连方块
cv2.rectangle(img, (50, 50), (120, 120), 255, -1)
cv2.rectangle(img, (130, 50), (200, 120), 255, -1)
# 方块内部加一个小黑洞
cv2.rectangle(img, (80, 80), (90, 90), 0, -1)
# 加白色噪声点
np.random.seed(0)
noise = (np.random.rand(h, w) > 0.98) * 255
img = cv2.bitwise_or(img, noise.astype(np.uint8))
# 2. 定义核
kernel = np.ones((3,3), np.uint8)
# 3. 各种形态学操作
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) # 开运算
closing = 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) # 黑帽
# 4. 显示对比
titles = ['Original', 'Open', 'Close', 'Gradient', 'TopHat', 'BlackHat']
images = [img, opening, closing, gradient, tophat, blackhat]
plt.figure(figsize=(12, 6))
for i in range(6):
plt.subplot(2, 3, i+1)
plt.imshow(images[i], cmap='gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.tight_layout()
plt.show()

7.图像梯度-Sobel算子
Sobel 算子用于检测图像边缘,原理是计算像素灰度的变化率(梯度),分为 水平梯度(X 方向) 和 垂直梯度(Y 方向)。

# Sobel 算子函数
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
参数说明:

img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
cv_show(sobelx,'sobelx')

二、关键知识点
img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
cv_show(sobelx,'sobelx')

三、完整可运行代码
单方向 + 合并梯度演示

import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像并转灰度图
img = cv2.imread("pie.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 1. X方向 Sobel(检测垂直边缘)
sobel_x = cv2.Sobel(gray, cv2.CV_64F, dx=1, dy=0, ksize=3)
sobel_x = cv2.convertScaleAbs(sobel_x) # 取绝对值
# 2. Y方向 Sobel(检测水平边缘)
sobel_y = cv2.Sobel(gray, cv2.CV_64F, dx=0, dy=1, ksize=3)
sobel_y = cv2.convertScaleAbs(sobel_y)
# 3. 合并 X+Y 梯度(完整边缘)
sobel_xy = cv2.addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0)
# 可视化对比
titles = ["Original Gray", "Sobel X", "Sobel Y", "Sobel X+Y"]
images = [gray, sobel_x, sobel_y, sobel_xy]
plt.figure(figsize=(12, 6))
for i in range(4):
plt.subplot(1, 4, i+1)
plt.imshow(images[i], cmap="gray")
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()


四、易错点强调
不要直接用 uint8 灰度由亮→暗时,梯度为负数,uint8 会直接置 0,边缘缺失。 正确流程:CV_64F 计算 → 取绝对值 → 转回正常图像。
不建议直接 dx=1, dy=1 cv2.Sobel(gray, cv2.CV_64F, 1, 1, 3) 效果差、边缘模糊, 标准做法:分别计算 X、Y 再加权融合。
五、简化写法(一行合并)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像并转灰度图
img = cv2.imread("cat.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 分别求梯度再融合(推荐)
gx = cv2.convertScaleAbs(cv2.Sobel(gray, cv2.CV_64F, 1, 0, 3))
gy = cv2.convertScaleAbs(cv2.Sobel(gray, cv2.CV_64F, 0, 1, 3))
combined = cv2.add(gx, gy)
# 可视化对比
titles = ["Original Gray", "combined"]
images = [gray, combined]
plt.figure(figsize=(12, 6))
for i in range(2):
plt.subplot(1,2, i+1)
plt.imshow(images[i], cmap="gray")
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

六、总结
- dx=1,dy=0 → 垂直边缘
- dx=0,dy=1 → 水平边缘
- 固定搭配:CV_64F + convertScaleAbs()
- 完整边缘:分方向计算后再相加 / 加权融合
图像梯度-Scharr算子

图像梯度-laplacian算子

img = cv2.imread('cat.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0)
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((sobelxy,scharrxy,laplacian))
cv_show(res,'res')

8.Canny 边缘检测
Canny 是最优边缘检测算法,流程严谨、抗噪性强,是工程中最常用的边缘提取方法。
一、函数语法
edges = cv2.Canny(image, threshold1, threshold2)
参数说明:
- image:输入图像,建议先转灰度图
- threshold1:低阈值
- threshold2:高阈值
阈值规则(核心)

经验:高低阈值比例常取 1:2 ~ 1:3
二、Canny 完整四大步骤
1:高斯滤波器

2:梯度和方向

3:非极大值抑制


三、基础使用代码
import cv2
import matplotlib.pyplot as plt
# 读取图片并转灰度
img = cv2.imread("cat.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Canny 边缘检测
canny = cv2.Canny(gray, 50, 150)
# 显示对比
plt.subplot(121), plt.imshow(gray, cmap="gray"), plt.title("Gray")
plt.subplot(122), plt.imshow(canny, cmap="gray"), plt.title("Canny Edge")
plt.xticks([]), plt.yticks([])
plt.show()

四、调节阈值对比(直观演示)
阈值大小直接控制边缘多少:
- 阈值偏小:检出大量细节、噪声也会被当成边缘
- 阈值偏大:只保留明显主边缘,细小边缘丢失
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("cat.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 不同阈值组合
edge1 = cv2.Canny(gray, 20, 60)
edge2 = cv2.Canny(gray, 50, 150)
edge3 = cv2.Canny(gray, 100, 200)
titles = ["20/60(边缘多)", "50/150(适中)", "100/200(边缘少)"]
imgs = [edge1, edge2, edge3]
plt.figure(figsize=(12,4))
for i in range(3):
plt.subplot(1,3,i+1)
plt.imshow(imgs[i], cmap="gray")
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()

9.图像金字塔
图像金字塔用于图像缩放,分为向下采样 (缩小)、向上采样 (放大),两类金字塔是图像融合、尺度检测的基础。

一、高斯金字塔
高斯金字塔实现图像缩小和放大,核心两步:高斯模糊 + 尺寸变换。
1. 向下采样(缩小图像)cv2.pyrDown()
作用:图像尺寸变为原来 1/2,宽高都减半,画面缩小。 原理:

dst = cv2.pyrDown(src)
2. 向上采样(放大图像)cv2.pyrUp()
作用:图像尺寸变为原来 2 倍,宽高都翻倍,画面放大。 原理:

dst = cv2.pyrUp(src)
重点:先缩小再放大,图像会丢失细节,无法复原。
高斯金字塔完整演示代码
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("cat.jpg")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 向下采样(缩小)
down = cv2.pyrDown(img)
# 向上采样(放大)
up = cv2.pyrUp(img)
up_down = cv2.pyrUp(down)
# 展示
titles = ["Original", "Down", "Up", "Up Down"]
imgs = [img, down, up, up_down]
plt.figure(figsize=(10,6))
for i in range(4):
plt.subplot(2,2,i+1)
plt.imshow(imgs[i])
plt.title(titles[i])
plt.show()

二、拉普拉斯金字塔
拉普拉斯金字塔不做缩放,用来保存缩放丢失的细节,常配合高斯金字塔做图像重建、图像融合。
公式
Li=Gi−pyrUp(pyrDown(Gi))
- Gi:高斯金字塔第 i 层图像
- Li:拉普拉斯金字塔第 i 层(差值 = 原图 – 先缩小再放大的图)
- 结果类似边缘 / 残差图

结合高斯金字塔 + 拉普拉斯金字塔,可以由低层图像 + 残差 还原高层原图。
拉普拉斯金字塔代码

import cv2
import matplotlib.pyplot as plt
img = cv2.imread("AM.png")
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 高斯下采样
g1 = cv2.pyrDown(img)
# 高斯上采样
g1_up = cv2.pyrUp(g1)
# 计算拉普拉斯层(残差)
laplacian = img – g1_up
# 显示
plt.subplot(121)
plt.imshow(img)
plt.title("Original")
plt.xticks([]), plt.yticks([])
plt.subplot(122)
plt.imshow(laplacian)
plt.title("Laplacian")
plt.xticks([]), plt.yticks([])
plt.show()

10.图像轮廓
轮廓(Contour)是二值图像中连通的边缘像素集合,常用于物体检测、形状识别、目标裁剪。核心流程:灰度化 → 二值化 → 查找轮廓 → 绘制 / 计算轮廓。
一、核心函数说明
1. 基础流程函数
cv2.cvtColor(img, cv2.COLOR_BGR2GRAY):彩色图转灰度图
cv2.threshold():图像二值化(区分前景 / 背景,找轮廓前提)
cv2.findContours():查找轮廓(核心)
contours, hierarchy = cv2.findContours(binary, mode, method)
- binary:二值图像(黑白图)
- mode:轮廓检索模式
- cv2.RETR_EXTERNAL:只检测最外层轮廓(最常用)
- cv2.RETR_TREE:检测所有轮廓,建立层级关系(内外轮廓)
- method:轮廓逼近方法
- cv2.CHAIN_APPROX_SIMPLE:压缩轮廓点,只保留拐点(省内存,推荐)
- cv2.CHAIN_APPROX_NONE:保留全部轮廓点
- 返回值:
- contours:列表,每个元素是一个轮廓 (点坐标数组)
- hierarchy:轮廓层级(内外轮廓关系,简单场景可忽略)
cv2.drawContours():绘制轮廓
cv2.drawContours(dst, contours, idx, color, thickness)
- dst:绘制目标图像
- idx:轮廓索引,-1 表示绘制所有轮廓
2. 轮廓常用计算(形状、面积、周长)
- 轮廓面积:cv2.contourArea(contour)
- 轮廓周长:cv2.arcLength(contour, True)(True= 闭合轮廓)
- 外接矩形:cv2.boundingRect(contour) → (x, y, w, h)
- 最小外接矩形 / 圆、多边形拟合:用于形状识别
二、完整入门代码(基础轮廓检测 + 绘制)
示例 1:基础轮廓查找 & 绘制
import cv2
import numpy as np
# 1. 读取图像
img = cv2.imread("pie.png")
img_copy = img.copy() # 备份原图,用于绘制
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 2. 二值化(阈值分割,黑白分离)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 3. 查找轮廓
contours, hierarchy = cv2.findContours(
binary,
cv2.RETR_TREE, # 取所有轮廓
cv2.CHAIN_APPROX_SIMPLE
)
# 4. 绘制所有轮廓:颜色(蓝,绿,红),线宽2
cv2.drawContours(img_copy, contours, -1, (0, 0, 255), 2)
# 5. 显示结果
cv2.imshow("Original", img)
cv2.imshow("Binary", binary)
cv2.imshow("Contours", img_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()

三、实用进阶案例
案例 1:筛选轮廓(按面积过滤噪声)
图片常有小白点噪声,通过轮廓面积过滤小轮廓:
import cv2
import numpy as np
img = cv2.imread("AM.png")
img_copy = img.copy()
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 遍历所有轮廓,过滤面积过小的噪声
for cnt in contours:
area = cv2.contourArea(cnt)
print(area)
if area > 200: # 面积阈值,根据图片调整
cv2.drawContours(img_copy, [cnt], -1, (0, 255, 0), 2)
cv2.imshow("Filter Contours", img_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()
案例 2:绘制轮廓外接矩形

框选出每个物体的包围盒:
import cv2
import numpy as np
img = cv2.imread("test.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
area = cv2.contourArea(cnt)
if area > 200:
# 外接矩形:x,y 左上角坐标;w,h 宽高
x, y, w, h = cv2.boundingRect(cnt)
# 画矩形
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imshow("Bounding Rect", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

案例 3:Canny 边缘 + 轮廓(复杂图像推荐)
单纯阈值效果差时,先用边缘检测再找轮廓:
import cv2
img = cv2.imread("test.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 高斯模糊去噪
blur = cv2.GaussianBlur(gray, (3, 3), 0)
# Canny边缘检测
edges = cv2.Canny(blur, 50, 150)
# 找轮廓并绘制
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (0, 255, 255), 2)
cv2.imshow("Canny Edge", edges)
cv2.imshow("Contour from Edge", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、常见问题 & 注意事项
轮廓找不到 / 全是噪声
- 先做高斯模糊去噪;
- 调整二值化阈值 127,或改用自适应二值化 cv2.adaptiveThreshold。
轮廓内外重叠(孔洞也被检测)
- 检索模式改用 cv2.RETR_EXTERNAL,只保留最外层轮廓。
drawContours 报错
- 传入单个轮廓时,必须写成 [cnt](列表格式)。
黑白颠倒(背景白、物体黑)
- 二值化加反转:cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)
五、常用拓展接口
# 1. 轮廓周长
perimeter = cv2.arcLength(cnt, True)
# 2. 多边形拟合(轮廓近似)
approx = cv2.approxPolyDP(cnt, 0.02*perimeter, True)
# 3. 最小外接圆
(x, y), r = cv2.minEnclosingCircle(cnt)
11.OpenCV 图像直方图(histogram)
1、概念
直方图:统计图像每个像素灰度值 (0~255) 出现的像素数量。
- x 轴:灰度值 0~255
- y 轴:该灰度的像素个数
用途:对比度调整、亮度分析、直方图均衡化、阈值选取。

2、核心函数
① cv2.calcHist () 计算直方图
hist = cv2.calcHist([img], channels, mask, histSize, ranges)
- [img]:图像放列表里
- channels=[0]:灰度图;彩色[0]B [1]G [2]R
- mask:掩码,全图统计填None
- histSize=[256]:分 256 个 bin
- ranges=[0,256]:像素范围
② cv2.equalizeHist () 全局直方图均衡(灰度增强)
③ cv2.createCLAHE () 自适应均衡 CLAHE(防局部过曝)
3、代码 1:灰度图直方图绘制
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("cat.jpg",0)
# 计算直方图
hist = cv2.calcHist([img],[0],None,[256],[0,256])
plt.plot(hist)
plt.xlim([0,256])
plt.title("Gray Hist")
plt.show()

4、代码 2:彩色三通道直方图
img = cv2.imread("cat.jpg")
color = ("b","g","r")
for i,c in enumerate(color):
hist = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(hist,color=c)
plt.xlim([0,256])
plt.show()

5、直方图均衡化(提亮偏暗图片)
全局均衡 equalizeHist
gray = cv2.imread("cat.jpg",0)
equ = cv2.equalizeHist(gray)
cv2.imshow("src",gray)
cv2.imshow("equal",equ)
cv2.waitKey(0)

CLAHE 自适应均衡(推荐,细节保留更好)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
cla_img = clahe.apply(gray)
cv2.imshow("clahe",cla_img)
cv2.waitKey(0)

- clipLimit:对比度限制,越大增强越强
- tileGridSize:分块大小
6、掩码局部直方图(只统计图片某一块)
gray = cv2.imread("test.jpg",0)
mask = np.zeros(gray.shape[:2],np.uint8)
mask[100:300,100:300]=255 #感兴趣区域
hist_mask = cv2.calcHist([gray],[0],mask,[256],[0,256])
plt.plot(hist_mask)
plt.show()
7、要点总结
12.模板匹配 templateMatch
一、原理
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)
res = cv2.matchTemplate(img, template, method)
- img:原图(待搜索大图)
- template:小模板
- method:匹配算法(6 种)
二、6 种匹配方法
1. cv2.TM_SQDIFF 平方差 → 值越小越匹配
2. cv2.TM_SQDIFF_NORMED 归一平方差 → 0最佳
3. cv2.TM_CCORR 相关 → 值越大越匹配
4. cv2.TM_CCORR_NORMED 归一相关 → 1最佳
5. cv2.TM_COEFF 相关系数 → 越大越好
6. cv2.TM_COEFF_NORMED 归一相关系数 → 1最佳【最常用】

日常首选:TM_CCOEFF_NORMED
三、完整基础代码


import cv2
import numpy as np
# 大图、小模板
img = cv2.imread("lena.jpg")
tpl = cv2.imread("face.jpg")
h, w = tpl.shape[:2]
# 模板匹配
res = cv2.matchTemplate(img, tpl, cv2.TM_CCOEFF_NORMED)
# 找最大最小值位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# TM_CCOEFF_NORMED取最大值位置
top_left = max_loc
bottom_right = (top_left[0]+w, top_left[1]+h)
# 画框
cv2.rectangle(img, top_left, bottom_right, (0,0,255), 2)
cv2.imshow("res",res)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、多目标匹配(阈值筛选多个物体)
![]()

一张图多个相同目标,用阈值筛选:
import cv2
import numpy as np
img = cv2.imread("big.jpg")
tpl = cv2.imread("small.jpg")
h,w = tpl.shape[:2]
res = cv2.matchTemplate(img, tpl, cv2.TM_CCOEFF_NORMED)
# 阈值,大于0.7认为匹配成功
threshold = 0.7
loc = np.where(res >= threshold)
# 遍历所有匹配点
for pt in zip(*loc[::-1]):
cv2.rectangle(img, pt, (pt[0]+w, pt[1]+h), (0,255,0), 2)
cv2.imshow("multi", img)
cv2.waitKey(0)
五、关键点
- 解决:多尺度遍历缩放模板 / 改用特征匹配 (SIFT)
六、优缺点
✅优点:简单、速度快 ❌缺点:不抗缩放、旋转、形变
13.图像傅里叶变换
傅里叶变换:把空间域图像转为频率域,低频对应图像整体灰度、大轮廓;高频对应边缘、噪声、细节。 常用场景:低通滤波 (模糊)、高通滤波 (锐化 / 提边缘)、去噪。
一、核心原理
- 图像中心:低频分量
- 图像四周:高频分量
二、关键函数
# 1. 傅里叶正变换
np.fft.fft2(img) # 二维傅里叶
np.fft.fftshift() # 低频移到图像中心(中心化,必用)
# 2. 傅里叶逆变换
np.fft.ifftshift() # 先还原频域位置
np.fft.ifft2() # 逆傅里叶
np.abs() # 取幅值(去除复数虚部)
# 3. 可视化频域:对数缩放(原始频域太暗看不清)
np.log(1 + np.abs(频谱))
三、完整代码 1:基础傅里叶变换 + 频域可视化
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 1. 读取图像,转灰度
img = cv2.imread("test.jpg", 0) # 0=直接灰度图
h, w = img.shape
# 2. 转浮点型(傅里叶要求)
img_float = np.float32(img)
# 3. 傅里叶变换
dft = np.fft.fft2(img_float)
dft_shift = np.fft.fftshift(dft) # 低频移中心
# 4. 频域可视化(对数缩放)
magnitude = 20 * np.log(np.abs(dft_shift))
# 5. 显示原图 & 频谱
plt.figure(figsize=(10,5))
plt.subplot(121),plt.imshow(img, cmap="gray"),plt.title("Original")
plt.subplot(122),plt.imshow(magnitude, cmap="gray"),plt.title("Frequency Spectrum")
plt.show()


四、完整代码 2:低通滤波(模糊、去高频噪声)
低通:保留中心低频,遮挡四周高频 → 图像变模糊、抑制噪声
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("test.jpg", 0)
h, w = img.shape
img_float = np.float32(img)
# 傅里叶变换
dft = np.fft.fft2(img_float)
dft_shift = np.fft.fftshift(dft)
# ========== 构建低通掩码 ==========
# 半径:控制保留多少低频,值越大图像越清晰
radius = 30
mask = np.zeros((h, w), np.uint8)
# 中心坐标
cx, cy = w//2, h//2
# 画圆形掩码:只保留中心低频
cv2.circle(mask, (cx, cy), radius, 255, -1)
# 频域相乘滤波
dft_shift = dft_shift * mask
# ========== 逆傅里叶变换 ==========
idft_shift = np.fft.ifftshift(dft_shift)
img_idft = np.fft.ifft2(idft_shift)
img_result = np.abs(img_idft)
# 归一化到 0~255
img_result = cv2.normalize(img_result, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# 显示
plt.figure(figsize=(12,4))
plt.subplot(131),plt.imshow(img, cmap="gray"),plt.title("Original")
plt.subplot(132),plt.imshow(mask, cmap="gray"),plt.title("Low-Pass Mask")
plt.subplot(133),plt.imshow(img_result, cmap="gray"),plt.title("Low-Pass Result")
plt.show()


五、完整代码 3:高通滤波(提取边缘、锐化)
高通:遮挡中心低频,保留四周高频 → 只显示边缘、细节
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("lena.jpg", 0)
h, w = img.shape
img_float = np.float32(img)
dft = np.fft.fft2(img_float)
dft_shift = np.fft.fftshift(dft)
# ========== 高通掩码(和低通相反) ==========
radius = 30
mask = np.ones((h, w), np.uint8) * 255
cx, cy = w//2, h//2
# 涂黑中心低频区域
cv2.circle(mask, (cx, cy), radius, 0, -1)
dft_shift = dft_shift * mask
# 逆变换
idft_shift = np.fft.ifftshift(dft_shift)
img_idft = np.fft.ifft2(idft_shift)
img_result = np.abs(img_idft)
img_result = cv2.normalize(img_result, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# 显示
plt.figure(figsize=(12,4))
plt.subplot(131),plt.imshow(img, cmap="gray"),plt.title("Original")
plt.subplot(132),plt.imshow(mask, cmap="gray"),plt.title("Low-Pass Mask")
plt.subplot(133),plt.imshow(img_result, cmap="gray"),plt.title("High-Pass (Edge)")
plt.show()


六、核心知识点总结
必须步骤
- 图像转 float32
- fft2 → fftshift(低频居中)
- 滤波后:ifftshift → ifft2 → 取绝对值
频域对应关系
- 低频:整体亮度、大块区域 → 低通滤波 = 模糊、去噪
- 高频:边缘、纹理、椒盐噪声 → 高通滤波 = 提边缘、锐化
调参技巧
- radius 越小:低通越模糊 / 高通边缘越强
- 频域图发黑:一定要用 20*np.log(1+np.abs(频谱)) 对数缩放可视化
常见报错
- 未转 float:傅里叶计算异常
- 忘记 fftshift:低频不在中心,滤波完全失效
- 逆变换后不取 abs:出现复数,图像乱色
傅里叶变换在图像处理中的基本原理是将图像从空间域转换到频率域,通过分解图像为不同频率的正弦和余弦波的叠加来分析图像的频率成分。通过傅里叶变换实现图像的频域滤波,首先对图像进行傅里叶变换得到频域表示,然后在频域中应用滤波器去除或增强特定频率成分,最后对滤波后的频域图像进行傅里叶逆变换转换回空间域,得到滤波后的图像。
14.透视变换
透视变换 warpPerspective(四点矫正,证件 / 倾斜文档拉直)
公式原理
dst=M⋅src
- M:3×3 透视变换矩阵,getPerspectiveTransform由 4 组对应点算出
- 4 个源点(原图倾斜四边形)→4 个目标点(规整矩形)
两个核心 API
#1 根据四点求变换矩阵
M = cv2.getPerspectiveTransform(src_points, dst_points)
#2 执行透视变换
out = cv2.warpPerspective(img, M, (out_w, out_h))
坐标必须 np.float32,四点顺序统一:左上→右上→右下→左下
完整示例:倾斜图转正,输出 400×500
import cv2
import numpy as np
img = cv2.imread("doc.jpg")
# 【原图4个倾斜角点:左上、右上、右下、左下】
src = np.array([
[35, 42],
[420, 30],
[435, 510],
[22, 495]
], dtype=np.float32)
# 【目标矩形宽400 高500】
W, H = 400, 500
dst = np.array([
[0, 0],
[W, 0],
[W, H],
[0, H]
], dtype=np.float32)
# 求透视矩阵+变换
M = cv2.getPerspectiveTransform(src, dst)
res = cv2.warpPerspective(img, M, (W, H))
cv2.imshow("src", img)
cv2.imshow("wrap", res)
cv2.waitKey(0)
cv2.destroyAllWindows()
从轮廓自动提取四点 + 自动排序(常用 OCR 文档矫正)
你前面获取的上下左右极值点是乱序,用坐标求和 / 差值自动排序四点
def order_points(pts):
# pts:(4,2) 无序四点,返回规范[左上,右上,右下,左下]
rect = np.zeros((4,2), dtype=np.float32)
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] #左上:x+y最小
rect[2] = pts[np.argmax(s)] #右下:x+y最大
diff = np.diff(pts,axis=1)
rect[1] = pts[np.argmin(diff)]#右上:y-x最小
rect[3] = pts[np.argmax(diff)]#左下:y-x最大
return rect
#====使用====
# cnt是目标物体轮廓
pts = cnt.reshape(-1,2)
# 取轮廓四个极值点(也可用minAreaRect四点)
left=pts[np.argmin(pts[:,0])]
right=pts[np.argmax(pts[:,0])]
top=pts[np.argmin(pts[:,1])]
bot=pts[np.argmax(pts[:,1])]
four = np.array([left,right,top,bot])
src = order_points(four) #自动排好顺序
#目标尺寸
W,H=400,500
dst = np.array([[0,0],[W,0],[W,H],[0,H]],np.float32)
M = cv2.getPerspectiveTransform(src,dst)
res = cv2.warpPerspective(img,M,(W,H))
仿射 vs 透视区别(容易混淆)
易错点
项目:银行卡号识别
素材:






工具类 myutils.py
import cv2
def sort_contours(contours, method="left-to-right"):
reverse = False
i = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
i = 1
bounding_boxes = [cv2.boundingRect(c) for c in contours] # 用一个最小的矩形,把找到的形状包起来x,y,h,w
(contours, bounding_boxes) = zip(*sorted(zip(contours, bounding_boxes),
key=lambda b: b[1][i], reverse=reverse))
return contours, bounding_boxes
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
(h, w) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
resized = cv2.resize(image, dim, interpolation=inter)
return resized
主程序代码:
# 导入工具包
import cv2
import numpy as np
from imutils import contours
import myutils
template_path = "images/ocr_a_reference.png"
image_path = "images/credit_card_03.png"
# 绘图展示
def cv_show(name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 读取一个模板图像
img = cv2.imread(template_path)
cv_show('img', img)
# 灰度图
ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('ref', ref)
# 二值图像
ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1]
cv_show('ref', ref)
# 计算轮廓
# cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标
# 返回的list中每个元素都是图像中的一个轮廓
refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, refCnts, -1, (0, 0, 255), 3)
cv_show('img', img)
print("轮廓总数量:", len(refCnts))
refCnts = myutils.sort_contours(refCnts, method="left-to-right")[0] # 排序,从左到右,从上到下
digits = {}
# 遍历每一个轮廓
for (i, c) in enumerate(refCnts):
# 计算外接矩形并且resize成合适大小
(x, y, w, h) = cv2.boundingRect(c)
roi = ref[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
# 每一个数字对应每一个模板
digits[i] = roi
# 初始化卷积核
rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
# 读取输入图像,预处理
image = cv2.imread(image_path)
cv_show('image', image)
image = myutils.resize(image, width=300)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv_show('gray', gray)
# 礼帽操作,突出更明亮的区域
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel)
cv_show('tophat', tophat)
# # ksize=-1相当于用3*3的
gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
gradX = np.absolute(gradX)
(minVal, maxVal) = (np.min(gradX), np.max(gradX))
gradX = (255 * ((gradX – minVal) / (maxVal – minVal)))
gradX = gradX.astype("uint8")
print(np.array(gradX).shape)
cv_show('gradX', gradX)
# 通过闭操作(先膨胀,再腐蚀)将数字连在一起
gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel)
cv_show('gradX', gradX)
# THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0
thresh = cv2.threshold(gradX, 0, 255,
cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('thresh', thresh)
# 再来一个闭操作
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) # 再来一个闭操作
cv_show('thresh', thresh)
# 计算轮廓
threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
cur_img = image.copy()
cv2.drawContours(cur_img, threshCnts, -1, (0, 0, 255), 3)
cv_show('img', cur_img)
locs = []
# 遍历轮廓
for (i, c) in enumerate(threshCnts):
# 计算矩形
(x, y, w, h) = cv2.boundingRect(c)
ar = w / float(h)
# 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组
if ar > 2.5 and ar < 4.0:
if (w > 40 and w < 55) and (h > 10 and h < 20):
# 符合的留下来
locs.append((x, y, w, h))
# 将符合的轮廓从左到右排序
locs = sorted(locs, key=lambda x: x[0])
output = []
# 遍历每一个轮廓中的数字
for (i, (gX, gY, gW, gH)) in enumerate(locs):
# initialize the list of group digits
groupOutput = []
# 根据坐标提取每一个组
group = gray[gY – 5:gY + gH + 5, gX – 5:gX + gW + 5]
cv_show('group', group)
# 自动全局阈值分割,不用手动填阈值,适合明暗区分明显的字符、二维码
group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
cv_show('group', group)
# 计算每一组的轮廓
digitCnts = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
digitCnts = contours.sort_contours(digitCnts, method="left-to-right")[0]
# 计算每一组中的每一个数值
for c in digitCnts:
# 找到当前数值的轮廓,resize成合适的的大小
(x, y, w, h) = cv2.boundingRect(c)
roi = group[y:y + h, x:x + w]
roi = cv2.resize(roi, (57, 88))
cv_show('roi', roi)
# 计算匹配得分
scores = []
# 在模板中计算每一个得分
for (digit, digitROI) in digits.items():
# 模板匹配
result = cv2.matchTemplate(roi, digitROI,
cv2.TM_CCOEFF)
(_, score, _, _) = cv2.minMaxLoc(result)
scores.append(score)
# 得到最合适的数字
groupOutput.append(str(np.argmax(scores)))
# 画出来
cv2.rectangle(image, (gX – 5, gY – 5),
(gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
cv2.putText(image, "".join(groupOutput), (gX, gY – 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
# 得到结果
output.extend(groupOutput)
# 打印结果
print("Credit Card #: {}".format("".join(output)))
cv2.imshow("Image", image)
cv2.waitKey(0)
