云计算百科
云计算领域专业知识百科平台

OpenCV入门 --- 2


date: 2026-08-12

目录

  • 1.图像基本操作 — 数值计算 — 数值计算、溢出处理(取模 vs 饱和)
  • 2.图像融合: — resize 改变 shape、addWeighted 融合
  • 图像阈值: — threshold 五种二值化类型
  • 图像平滑处理: — 均值/方框/高斯/中值/双边滤波
  • 图像拼接: — np.hstack / np.vstack—

1.图像基本操作 — 数值计算

img_pp1 = img_pp + 10
cv.imshow('img_pp1', img_pp1)
cv.waitKey(0)
cv.destroyAllWindows()

img_pp[:5,:,0]

结果:

(146, 133, 3)
array([[254, 254, 254, …, 254, 254, 254], [254, 254, 254, …, 254, 254, 254], [253, 253, 253, …, 254, 254, 254], [253, 253, 253, …, 254, 254, 254], [252, 252, 252, …, 254, 254, 254]], shape=(5, 526), dtype=uint8)

img_pp1[:5,:,0]

结果:

array([[8, 8, 8, …, 8, 8, 8], [8, 8, 8, …, 8, 8, 8], [7, 7, 7, …, 8, 8, 8], [7, 7, 7, …, 8, 8, 8], [6, 6, 6, …, 8, 8, 8]], shape=(5, 526), dtype=uint8)

img_pp2 = img_pp1 + img_pp
img_pp2[:5,:,0]

运行结果:

array([[6, 6, 6, …, 6, 6, 6], [6, 6, 6, …, 6, 6, 6], [4, 4, 4, …, 6, 6, 6], [4, 4, 4, …, 6, 6, 6], [2, 2, 2, …, 6, 6, 6]], shape=(5, 526), dtype=uint8)

使用opencv函数:

cv.add(img_pp1, img_pp)[:5,:,0]

运行结果:

array([[255, 255, 255, …, 255, 255, 255], [255, 255, 255, …, 255, 255, 255], [255, 255, 255, …, 255, 255, 255], [255, 255, 255, …, 255, 255, 255], [255, 255, 255, …, 255, 255, 255]], shape=(5, 526), dtype=uint8)

  • 由此可得,溢出处理:
运算方式溢出处理例:200+100
numpy 直接 img1 + img2 取模(mod 256,绕回) 300 % 256 = 44 ❌
cv.add(img1, img2) 饱和(封顶) 255 ✅
  • [:5,:,0] 怎么理解

    • 这是 numpy 数组切片的问题,numpy 多维数组索引是逐维的,逗号分隔每一维:数组[第0维, 第1维, 第2维, …],对图像(三维数组 img[高, 宽, 通道])来说,img[:5, :, 0] 拆开看:
部分含义作用
:5 第 0 维(高/行)取前 5 个 等价 0:5,取前 5 行
: 第 1 维(宽/列)全部 所有列都要
0 第 2 维(通道)只取第 0 个 B 蓝色通道

结果:一张 5 × 宽 的二维数组——前 5 行的蓝色通道浓度图。 “0:5” 是取第 0、1、2、3、4 行,共 5 行 —— 不包含第 5 行。 Python 切片是左闭右开 [start, stop)


2.图像融合:

im1 = img + img_pp
im1[:5,:,0]

直接相加会报错:

ValueError Traceback (most recent call last) Cell In[19], [line 1](vscode-notebook-cell:?execution_count=19&line=1) —-> [1](vscode-notebook-cell:?execution_count=19&line=1) im1 = img + img_pp 2 im1[:5,:,0] ValueError: operands could not be broadcast together with shapes (146,133,3) (531,526,3)

原因是图像维度不一致,shape值不同

img_pp2 = cv.resize(img_pp, (img.shape[1], img.shape[0]))
img_pp2.shape

通过resize函数 ,改变图像的shape值

plt.subplot(121),plt.imshow(img_pp),plt.title('img_pp')
plt.subplot(122),plt.imshow(img_pp2),plt.title('img_pp2')

在这里插入图片描述

  • resize不仅可以指定高宽,还能指定倍数

img_pp3 = cv.resize(img_pp,(0,0),fx=3,fy=1)
img_pp3.shape
plt.imshow(img_pp3)

  • 在这里插入图片描述

res = cv.addWeighted(img,0.7,img_pp2,0.6,0)
plt.imshow(res)

  • 在这里插入图片描述 关于addWeighted函数的参数,两个输入,即两个图像,后面分别跟着权重,可以理解为,谁更明显,谁占大头,最后的参数是偏置量,同样输入的shape值需要一样。

图像阈值:

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

  • 参数
    • src: 输入图,只能输入单通道图像,通常来说是灰度图
    • dst:输出图
    • thresh:阈值
    • maxval:当像素值超过阈值(或者小于阈值,根据type来决定),所赋予的值
    • type:二值化操作的类型,包含以下5种类型:
      • cv.THRESH_BINARY : 二值化。像素值 > thresh → maxval,否则 → 0(最常用,得到黑白图)
      • cv.THRESH_BINARY_INV : 反二值化。像素值 > thresh → 0,否则 → maxval(黑白互换)
      • cv.THRESH_TRUNC : 截断。像素值 > thresh → thresh,否则不变(高亮部分被压暗,不会全黑)
      • cv.THRESH_TOZERO : 取零。像素值 > thresh → 原值不变,否则 → 0(低于阈值的区域变黑)
      • cv.THRESH_TOZERO_INV : 反取零。像素值 > thresh → 0,否则 → 原值不变(高于阈值的区域变黑)

img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
plt.imshow(img_gray, cmap='gray')

ret,thresh1 = cv.threshold(img_gray,127,255,cv.THRESH_BINARY) # 超过阈值的部分全取max,低于阈值的部分全取0
plt.imshow(thresh1, cmap='gray')

在这里插入图片描述 在这里插入图片描述

  • 整合到一起

ret,thresh1 = cv.threshold(img_gray,127,255,cv.THRESH_BINARY) # 超过阈值的部分全取max,低于阈值的部分全取0
ret,thresh2 = cv.threshold(img_gray,127,255,cv.THRESH_BINARY_INV) # 超过阈值的部分全取0,低于阈值的部分全取max
ret,thresh3 = cv.threshold(img_gray,127,255,cv.THRESH_TRUNC) # 超过阈值的部分全取阈值,低于阈值的部分不变
ret,thresh4 = cv.threshold(img_gray,127,255,cv.THRESH_TOZERO) # 超过阈值的部分不变,低于阈值的部分全取0
ret,thresh5 = cv.threshold(img_gray,127,255,cv.THRESH_TOZERO_INV) # 超过阈值的部分全取0,低于阈值的部分不变

imgs = [img_gray, thresh1, thresh2, thresh3, thresh4, thresh5]
titles =['OriginalImage','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']

for i in range(6):
plt.subplot(2,3,i+1),plt.imshow(imgs[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])

plt.show()

在这里插入图片描述

  • plt.imshow(imgs[i],'gray')
    • 灰度图是二维数组,本身没有颜色,显示成什么颜色,靠 matplotlib 的 colormap(色图) 决定。而 imshow 对二维数组的默认色图是 viridis(黄绿渐变),OpenCV 对单通道图直接用亮度值显示,不做颜色映射。这就是两个库显示习惯的差别。
  • `plt.xticks([]),plt.yticks([])
    • 隐藏坐标轴的刻度,把 x 轴 / y 轴的刻度**设成空列表 [],相当于"不显示任何刻度"——刻度线和刻度数字(0、50、100…)都隐藏了。
      • 为什么常见:显示图像时,坐标数值(像素坐标)通常没用,还挡视线。隐藏后图像更干净。OpenCV 用 matplotlib 显示图片时几乎都这么写。

      • 三个常用写法对比(由轻到重):

写法效果
plt.xticks([]), plt.yticks([]) 只隐藏刻度,坐标轴边框还在
plt.axis('off') 隐藏整个坐标轴(边框 + 刻度 + 标签),最彻底
plt.tick_params(left=False, bottom=False) 精细控制:只关闭指定方向的刻度线(最灵活)
  • for i in range(6):
  • i 依次 = 0, 1, 2, 3, 4, 5(共 6 次循环)

图像平滑处理:

  • OpenCV 读图默认是 BGR 顺序,而 matplotlib 按 RGB 解释。不转的话红蓝色对调——比如红花会显示成蓝色。
    • 转换方法:
    • 方法一:cvtColor 显式转换(推荐,语义清晰)
    • 方法二:numpy 反转通道( […, ::-1] 写法)

# 方法一:
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.imshow(img_rgb)

plt.imshow(img[…, ::-1]) # … 省略前面的维,::-1 反转最后一维

  • 如何给图像加上白色噪点?

img_noisy = img.copy()
n = 1000 # 噪声点数量
x = np.random.randint(0, img.shape[0], n) # 随机行坐标
y = np.random.randint(0, img.shape[1], n) # 随机列坐标
img_noisy[x, y] = 255 # 白点(盐);换 0 就是黑点(椒)

plt.subplot(1,2,1),plt.imshow(img_noisy,'gray')
plt.title('img_noisy')
plt.title('noise')
plt.subplot(1,2,2),plt.imshow(img,'gray')
plt.title('Original Image')
plt.show()

在这里插入图片描述

  • x = np.random.randint(0, img.shape[0], n)

    • np.random.randint – numpy 的随机整数生成函数
    • 0 – 最小值(包含)——最小的数是 0
    • img.shape[0] – 最大值(不包含)——即图像行数
    • n – 生成 n 个数
  • img_noisy[x, y] = 255

    • 用两个数组做"花式索引",批量给随机像素赋白色
    • x 和 y 都是数组(各 n 个随机坐标)。img_noisy[x, y] 是 numpy 的花式索引(fancy indexing):两个数组逐对配对,一次性取出/写入 n 个像素位置。
    • 两个细节:
      • 不是笛卡尔积:x 数组 y 数组是对应位置配对(zip 语义),不是所有 x×y 组合
      • 彩图广播:如果 img_noisy 是彩色图 (H, W, 3),标量 255 会自动广播到三个通道——所以 img_noisy[x, y] = 255 和 = [255,255,255] 结果一样
  • 均值滤波:

#均值滤波
#简单的卷积运算
blur = cv.blur(img_noisy,(3,3))
plt.imshow(blur)

在这里插入图片描述

  • 可以看到,白点变淡了,但是实际上效果还是不是很好。

  • 方框滤波:

#方框滤波
#可以选择归一化
box = cv.boxFilter(img_noisy,1,(3,3),normalize=True)
plt.imshow(box)

在这里插入图片描述

  • 结果和均值滤波没什么区别

dst = cv.boxFilter(src, ddepth,
ksize[, dst[,
anchor[, normalize[,
borderType]]]])

  • 参数:
    • src — 输入图像
    • ddepth — 输出图像深度,-1 = 与 src 相同
    • ksize — 核大小 (宽, 高),越大越模糊
    • dst — 输出图像(可省略)
    • anchor — 锚点(核内对齐位置),默认 (-1,-1)=核中心
    • normalize — 归一化开关,默认 True
    • borderType — 边界外推方式,默认 BORDER_DEFAULT(注意不支持 BORDER_WRAP)
    • 和 cv.blur 的关系:blur 只能做归一化均值滤波;boxFilter 是加强版,多一个 normalize 开关,其余参数一样。
    • 归一化 = 让核元素之和等于 1,方框核里每个元素都是 1,3×3 核一共 9 个元素,和为 9。
      • normalize=True:每个元素除以 9 → 核变成 1/9 的 9 个 → 核元素之和 = 1 → 输出是邻域平均值
      • normalize=False:元素保持 1 → 核元素之和 = 9 → 输出是邻域求和
    • 不做归一化意味着什么:
    • 数值被放大:每个输出像素 = 邻域内所有像素的总和,放大 w×h 倍。900 远超 255,uint8 直接饱和成一片白
    • “平均"变"求和”:归一化保留亮度水平(图像整体明暗不变,只是变平滑);不归一化是算邻域"总能量",图像整体剧烈变亮
    • 用途不同:求和结果适合算积分特征——邻域的总亮度、总能量、导数乘积的协方差(光流算法用)。所以需要保留真实和值时,ddepth 要配更大的深度(如 CV_16S 能到 32767),不能 -1(会溢出饱和)

高斯滤波:

  • 高斯模糊的卷积核里的数值是满足高斯分布(高斯分布 = 正态分布),相当于更重视中间的,相当于创建的一个权重矩阵

gaussian = cv.GaussianBlur(img_noisy,(3,3),0)

plt.imshow(gaussian)

在这里插入图片描述

  • 用"高斯分布形状"的权重核做平滑滤波 *一句话:高斯滤波 = 卷积核的权重不是平均的,而是服从**高斯分布——中心像素权重最大,越往边缘权重越小,对邻域做加权平均来平滑去噪。
  • dst = cv.GaussianBlur(src, ksize, sigmaX)
    • ksize:核大小,必须正奇数,如 (3,3)、(5,5)
    • sigmaX:X 方向高斯标准差,越大越模糊;填 0 时由 ksize 自动计算(sigma = 0.3×((ksize-1)×0.5-1)+0.8)
  • 常见用途:去噪;Canny 边缘检测的第一步预处理;需要"平滑但保留边缘"的场景。

问题 1:卷积核大小决定什么? 核大小 = “每个像素看多远 + 平均范围多大”,它同时决定:

决定什么核越大
平滑强度 模糊越强(参与平均的邻域更大)
感受野 每个输出像素能"看到"的邻居范围越大
能去的噪声 能抹掉越大的噪点(小核只能去小噪点)
细节损失 边缘、纹理被平均得越厉害,丢得越多
边界效应 图像边缘处理不到的条带越宽
计算量 越大(每像素 w×h 次乘加)

直觉:核大小是"平滑强度"和"细节保留"的旋钮——3×3 只看周围一圈,5×5 看周围两圈,越大越糊、越丢细节。

问题 2:为什么去噪时中间权重更大?

核心原因:自然图像里,离中心越近的像素越"可信",越远的越可能是"另一回事"。

  • 空间相关性:自然图像相邻像素高度相关——近处像素大概率属于同一个物体/区域,值接近;远处像素可能跨过边缘、属于另一个物体
  • 信任模型:去噪 = 用邻居估计中心像素的真实值。邻居越近越"说真话"(相关性强),越远越可能带来错误信息(比如边缘另一边的值),所以给近的权重大、远的权重小
  • 保边缘(关键区别):均值滤波等权重,边缘两边像素各占一半 → 边缘被平均糊掉;高斯滤波下,边缘另一边的"远处"像素权重很小 → 边缘保留。这就是高斯滤波"保边更好"的原因
  • 中值滤波:

    #中值滤波
    #相当于用中值代替
    median = cv.medianBlur(img_noisy,3)
    plt.imshow(median)

    在这里插入图片描述

    • 看起来中值滤波的效果更好
      • 为什么:当前噪声是椒盐噪声(随机黑白点,见前文 img_noisy[x, y] = 255 撒点)。中值滤波把邻域像素排序后取中值——白点 255 / 黑点 0 这类离群值排序后被挤到两端,取不到中值,所以椒盐点被直接剔除,且不糊边缘(相比均值/高斯滤波)
      • cv.medianBlur(src, ksize):ksize 同样必须正奇数;对椒盐噪声效果最好

    双边滤波:

    bilateral = cv.bilateralFilter(img_noisy,9,75,75)
    plt.imshow(bilateral)

    在这里插入图片描述

    • dst = cv.bilateralFilter(src, d, sigmaColor, sigmaSpace)
      • 参数:
        • d — 邻域直径(核大小),常用 5 或 9;非正时由 sigmaSpace 自动算
        • sigmaColor — 颜色相似度标准差——灰度差超过它的像素不参与加权(保边关键)
        • sigmaSpace — 空间距离标准差——和 GaussianBlur 的 sigma 类似,控制距离衰减
      • 两种权重(这才是"双边"的含义):
      • 空间权重:离中心近的像素影响大
      • 颜色权重:灰度/颜色和中心像素接近的像素影响大
      • 用途:人像磨皮/美颜(核心技术)、去噪同时保细节、纹理保留等。参数直觉:sigmaColor 越大越平滑(容忍更多色差)、越小保边越强但去噪弱。

    图像拼接:

    res = np.hstack((img_noisy, blur, box, gaussian, median, bilateral))
    plt.imshow(res)

    在这里插入图片描述

    • hstack 将图像横向拼接
    • vstack 竖向拼接
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » OpenCV入门 --- 2
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!