OpenCV(Python 版)
2026/6/15 22:02:07 网站建设 项目流程

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

1.图片视频基础读取

读取与展示图片

安装 OpenCV 使用 imread 读取图片。

pip install opencv-python

读取图片(核心)

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)

显示与保存

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 换成视频捕获对象,一帧一帧读取画面。

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:平铺重复填充

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()

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 图像融合公式
plaintext
融合图 = 图1 × α + 图2 × β + γ
你写的:
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0)
猫占 40%
狗占 60%
最后亮度偏移:0
这叫图像加权融合,必须保证两张图宽高完全一样!

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 里最简单的图像分割方法,把图像变成只有黑和白两种颜色(二值图)。

ret, dst = cv2.threshold(src, thresh, maxval, type)

src原图(最好是灰度图

thresh阈值(你自己设定的分割线,比如 127)

maxval最大值(一般填255

type阈值类型(决定怎么分割)

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→255(白),小于等于127→0(黑) ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 反二进制阈值:大于127→0(黑),小于等于127→255(白) 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 不同,它更贴合人眼对颜色的感知,由三个分量组成:

  1. H (Hue) 色相 / 色调
    • 代表是什么颜色,取值范围0~179(OpenCV 中)。
    • 对应色环:红→黄→绿→青→蓝→紫,循环变化。
  2. S (Saturation) 饱和度
    • 代表颜色鲜艳程度,取值0~255
    • 数值越高颜色越浓郁;越低越偏向灰白,0 就是灰度。
  3. V (Value) 明度 / 亮度
    • 代表明暗程度,取值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 颜色追踪、目标提取首选

补充小知识点

  1. cv2.imshow直接显示 HSV 图,画面会看起来怪异,因为窗口仍按 BGR 规则解析数据,仅用于查看数组,不代表真实色彩
  2. 常用场景:按指定色相范围抠取特定颜色物体(比如提取画面中的红色、蓝色物体)。

最简记忆

  • 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显示

一、1. 均值滤波cv2.blur()

原理:邻域像素求平均值,简单模糊,降噪同时会丢失细节。 语法:

dst = cv2.blur(src, ksize)
  • ksize:卷积核大小(w, h),必须为正奇数(如 (3,3)、(5,5))

示例:

# 3×3 均值滤波 blur = cv2.blur(img, (3, 3))

二、2. 高斯滤波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)

三、3. 中值滤波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)

腐蚀是形态学处理的基础操作,核心作用是收缩图像中亮区域、消除细小噪声、断开粘连物体边缘,常用来处理二值图像。

如下是一个十字核(结构B)在原图上腐蚀的过程,结构B的中心点会在结构A上每个位置扫一遍,只要出现有一个绿色和红色点位对不上就会导致该点位被腐蚀。


一、腐蚀操作原理

和你之前看的卷积类似,腐蚀也是用一个 ** 结构元素(核)** 在图像上滑动:

  1. 核的中心对准图像的每个像素;
  2. 取核覆盖区域内的最小值,作为中心像素的新值;
  3. 对二值图来说,只要核内有一个像素是 0(黑色),中心像素就会被 “腐蚀” 成 0,相当于把亮区域的边缘 “啃掉” 一圈。

二、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),它会让亮区域变大。

  • 腐蚀 + 膨胀组合:开运算(先腐蚀后膨胀,去噪同时保留物体大小)
  • 膨胀 + 腐蚀组合:闭运算(先膨胀后腐蚀,填充孔洞、连接断裂)
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)
定义:先腐蚀,再膨胀
公式:开运算 = 膨胀(腐蚀(原图))
作用:去除小的白色噪声点,同时保留物体整体大小
场景:去噪、断开物体间的细小粘连

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)

参数说明:

  1. src:输入图像(建议先转灰度图)
  2. ddepth:输出图像深度,推荐填cv2.CV_64F。原因:边缘存在正负梯度,uint8 会截断负数,丢失反向边缘
  3. dxX 方向梯度(水平边缘),1开启 /0关闭
  4. dyY 方向梯度(垂直边缘),1开启 /0关闭
  5. ksize:卷积核大小,必须是奇数,常用3

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

二、关键知识点

  1. X 方向梯度dx=1, dy=0检测垂直边缘(左右灰度变化大的位置)
  2. Y 方向梯度dx=0, dy=1检测水平边缘(上下灰度变化大的位置)
  3. 取绝对值Sobel 计算会出现负值,用cv2.convertScaleAbs()取绝对值,还原完整边缘。
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()

四、易错点强调

  1. 不要直接用 uint8灰度由亮→暗时,梯度为负数,uint8会直接置 0,边缘缺失。 正确流程:CV_64F计算 → 取绝对值 → 转回正常图像。

  2. 不建议直接dx=1, dy=1cv2.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')

​​​​​​​

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询