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)
- 由此可得,溢出处理:
| 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 显示图片时几乎都这么写。
-
三个常用写法对比(由轻到重):
-
- 隐藏坐标轴的刻度,把 x 轴 / y 轴的刻度**设成空列表 [],相当于"不显示任何刻度"——刻度线和刻度数字(0、50、100…)都隐藏了。
| 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 竖向拼接
网硕互联帮助中心




评论前必须登录!
注册