欢迎光临
我们一直在努力

OpenCV入门学习(二):图像预处理

OpenCV入门学习(二):图像预处理

前言:本文是OpenCV系列教程的第二篇。在掌握了图像的基本操作之后,我们将进入图像预处理阶段。本章涵盖五个核心知识点:阈值处理、噪声处理、边界填充、边缘检测、图像形态学。以函数用法和参数调优为主,理论点到为止,代码紧跟知识点。

目录

  • 一、阈值处理
  • 二、噪声处理
  • 三、边界填充
  • 四、边缘检测
  • 五、图像形态学
  • 六、总结

一、阈值处理

阈值处理是指剔除图像内像素值高于一定值或低于一定值的像素点。

核心函数:retval, dst = cv2.threshold(src, thresh, maxval, type)

参数说明
src 要进行阈值分割的图像,可以是多通道的,8位或32位浮点型数值
thresh 要设定的阈值
maxval 当type为THRESH_BINARY或THRESH_BINARY_INV时,需要设定的最大值
retval 返回的阈值
dst 阈值分割结果图像,与原始图像具有相同的大小和类型
type 阈值分割的类型

阈值类型详解:

选项像素值 > thresh其他情况
cv2.THRESH_BINARY maxval 0
cv2.THRESH_BINARY_INV 0 maxval
cv2.THRESH_TRUNC thresh 当前灰度值
cv2.THRESH_TOZERO 当前灰度值 0
cv2.THRESH_TOZERO_INV 0 当前灰度值

import cv2

# 读取图像,0表示以灰度模式读取
img = cv2.imread("yys1.jpg", 0)

# 阈值处理:像素值大于175的设为255,小于等于175的设为0
ret1, binary = cv2.threshold(img, 175, 255, cv2.THRESH_BINARY)

# 阈值处理:像素值大于175的设为0,小于等于175的设为255(与BINARY相反)
ret2, binaryinv = cv2.threshold(img, 175, 255, cv2.THRESH_BINARY_INV)

# 阈值处理:像素值大于175的截断为175,小于等于175的保持不变
ret3, trunc = cv2.threshold(img, 175, 255, cv2.THRESH_TRUNC)

# 阈值处理:像素值大于175的保持不变,小于等于175的设为0
ret4, tozero = cv2.threshold(img, 175, 255, cv2.THRESH_TOZERO)

# 阈值处理:像素值大于175的设为0,小于等于175的保持不变(与TOZERO相反)
ret5, tozeroinv = cv2.threshold(img, 175, 255, cv2.THRESH_TOZERO_INV)

# 显示原图
cv2.imshow("yuan", img)

# 显示五种阈值处理结果
cv2.imshow('binary', binary)
cv2.imshow('binaryinv', binaryinv)
cv2.imshow('trunc', trunc)
cv2.imshow('tozero', tozero)
cv2.imshow('tozeroinv', tozeroinv)

# 等待按键后关闭所有窗口
cv2.waitKey(0)
cv2.destroyAllWindows()

二、噪声处理

噪声是图像中的随机干扰信号,椒盐噪声是其中常见的一种,表现为随机出现的黑白像素点。滤波是去除噪声的主要手段。

2.1 椒盐噪声生成

代码位置:codes/ch2/04_add_noise.py

import cv2
import numpy as np

# 椒盐噪声生成函数
def add_peppersalt_noise(img, n=10000):
"""
为图像添加椒盐噪声
参数:
img: 输入图像
n: 噪声点的数量,默认10000
返回:
添加噪声后的图像
"""

result = img.copy()
h, w = img.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

img = cv2.imread('yys1.jpg')
noise = add_peppersalt_noise(img)

cv2.imshow('img', img)
cv2.imshow('noise', noise)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

2.2 均值滤波

核心函数:dst = cv2.blur(src, ksize)

参数说明
src 需要处理的图像
ksize 滤波核的大小,如(3,3)、(63,63)

一般情况下,使用 dst = cv2.blur(src, ksize) 即可。

# 均值滤波:用邻域内所有像素的平均值替代中心像素值
# 核大小为3×3(较小,去噪效果弱但保留更多细节)
blur_1 = cv2.blur(noise, (3, 3))
cv2.imshow('blur_1', blur_1)

# 均值滤波:核大小为63×63(较大,去噪效果好但图像更模糊)
blur_2 = cv2.blur(noise, (63, 63))
cv2.imshow('blur_2', blur_2)

# 显示原图和噪声图作为对比
cv2.imshow('img', img)
cv2.imshow('noise', noise)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

2.3 方框滤波

核心函数:dst = cv2.boxFilter(src, ddepth, ksize, anchor, normalize, borderType)

参数说明
src 需要处理的图像
ddepth 图像深度,-1表示与原始图像相同
ksize 滤波核的大小
normalize True时归一化,与均值滤波效果相同;False时不归一化

# 方框滤波
# normalize=True时,归一化,与均值滤波效果相同
boxFilter_1 = cv2.boxFilter(noise, 1, (3, 3), normalize=True)
# normalize=False时,不归一化,直接使用邻域像素值的和,和>255时使用255
boxFilter_2 = cv2.boxFilter(noise, 1, (3, 3), normalize=False)

cv2.imshow('boxFilter_1', boxFilter_1)
cv2.imshow('boxFilter_2', boxFilter_2)
cv2.imshow('img', img)
cv2.imshow('noise', noise)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

2.4 高斯滤波

核心函数:cv2.GaussianBlur(src, ksize, sigmaX)

参数说明
src 输入图像
ksize 滤波器的大小,如(5,5)
sigmaX X轴方向的标准差

# 高斯滤波:使用高斯核进行加权平均,离中心越近的像素权重越大
# 核大小5×5,X轴标准差为1
gaussian_blur = cv2.GaussianBlur(noise, (5, 5), 1)
cv2.imshow('gaussian_blur', gaussian_blur)

cv2.imshow('img', img)
cv2.imshow('noise', noise)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

2.5 中值滤波

核心函数:cv2.medianBlur(src, ksize)

参数说明
src 输入图像
ksize 滤波器的大小,整数,如3表示3×3

# 中值滤波:用邻域内所有像素的中值替代中心像素值
# 对椒盐噪声效果极佳,核大小为3×3
median_blur = cv2.medianBlur(noise, 3)
cv2.imshow('median_blur', median_blur)

cv2.imshow('img', img)
cv2.imshow('noise', noise)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

三、边界填充

边界填充是在图像周围添加额外的像素边框,常用于卷积操作前保持图像尺寸、或为图像添加装饰边框。

核心函数:cv2.copyMakeBorder(src, top, bottom, left, right, borderType, value)

参数说明
src 要扩充边界的原始图像
top, bottom, left, right 相应方向上的边框宽度
borderType 定义要添加边框的类型
value 填充值(仅对BORDER_CONSTANT有效)

borderType填充方式详解:

类型说明
cv2.BORDER_CONSTANT 添加的边框像素值为常数(需要额外给定value参数)
cv2.BORDER_REFLECT 边框像素是边界元素的镜面反射,例如:gfedcba | abcdefgh | hgfedcba(交界处也复制了)
cv2.BORDER_REFLECT_101 或 cv2.BORDER_DEFAULT 与上面类似,但交界处除外,例如:gfedcb | abcdefgh | gfedcba
cv2.BORDER_REPLICATE 使用最边界的像素值代替,例如:aaaaaa | abcdefgh | hhhhhhh
cv2.BORDER_WRAP 上下左右边依次替换,例如:cdefgh | abcdefgh | abcdefg

import cv2

img = cv2.imread('yys1.jpg')
top, bot, lf, rt = 15, 15, 15, 15

# 常数填充:边框像素值为常数[50,50,50]
constant = cv2.copyMakeBorder(img, top, bot, lf, rt, borderType=cv2.BORDER_CONSTANT, value=[50, 50, 50])

# 镜像填充:边框像素是边界元素的镜面反射,例如 gfedcba|abcdefgh|hgfedcba
refl = cv2.copyMakeBorder(img, top, bot, lf, rt, borderType=cv2.BORDER_REFLECT)

# 镜像填充101:与REFLECT类似,但交界处不重复,例如 gfedcb|abcdefgh|gfedcba
refl101 = cv2.copyMakeBorder(img, top, bot, lf, rt, borderType=cv2.BORDER_REFLECT101)

# 边缘复制填充:使用最边界的像素值代替,例如 aaaaaa|abcdefgh|hhhhhhh
repl = cv2.copyMakeBorder(img, top, bot, lf, rt, borderType=cv2.BORDER_REPLICATE)

# 平铺填充:上下左右边依次替换,例如 cdefgh|abcdefgh|abcdefg
wrap = cv2.copyMakeBorder(img, top, bot, lf, rt, borderType=cv2.BORDER_WRAP)

cv2.imshow("img", img)
cv2.imshow('constant', constant)
cv2.imshow('reflect', refl)
cv2.imshow('reflect101', refl101)
cv2.imshow('replicate', repl)
cv2.imshow('wrap', wrap)
cv2.waitKey(0)
cv2.destroyAllWindows()

四、边缘检测

边缘是图像中灰度发生剧烈变化的位置,边缘检测是计算机视觉中最基础的特征提取方法。

4.1 Sobel算子

核心函数:cv2.Sobel(src, ddepth, dx, dy[, ksize[, scale[, delta[, borderType]]]])

参数说明
src 输入图像
ddepth 输出图像的深度,-1表示与原图像相同的深度
dx, dy 当组合为dx=1,dy=0时求x方向的一阶导数;dx=0,dy=1时求y方向的一阶导数
ksize Sobel算子的大小,必须是1、3、5或7,默认为3

import cv2

# ==================== Sobel算子基础演示 ====================
# cv2.Sobel(src, ddepth, dx, dy[, ksize])
# 参数:
# src: 输入图像
# ddepth: 输出图像的深度,-1表示与原图像相同
# dx,dy: dx=1,dy=0求x方向一阶导数;dx=0,dy=1求y方向一阶导数
# ksize: Sobel算子大小,必须是1、3、5或7,默认为3

yuan = cv2.imread("yuan.png")
yuan = cv2.resize(yuan, None, fx=0.7, fy=0.7)
cv2.imshow("yuan", yuan)

# x方向边缘(uint8类型,负数信息保存不了)
yuan_x = cv2.Sobel(yuan, 1, dx=1, dy=0)
cv2.imshow("yuan_x", yuan_x)

# x方向边缘(CV_64F类型,可保存负数)
yuan_x_64 = cv2.Sobel(yuan, cv2.CV_64F, 1, 0)
cv2.imshow("yuan_x_64", yuan_x_64)

# x方向边缘(取绝对值,负数转正数显示)
yuan_x_full = cv2.convertScaleAbs(yuan_x_64)
cv2.imshow("yuan_x_full", yuan_x_full)

# y方向边缘(uint8类型,负数信息保存不了)
yuan_y = cv2.Sobel(yuan, 1, 0, 1)
cv2.imshow("yuan_y", yuan_y)

# y方向边缘(CV_64F类型,可保存负数)
yuan_y_64 = cv2.Sobel(yuan, cv2.CV_64F, 0, 1)
cv2.imshow("yuan_y_64", yuan_y_64)

# y方向边缘(取绝对值,负数转正数显示)
yuan_y_full = cv2.convertScaleAbs(yuan_y_64)
cv2.imshow("yuan_y_full", yuan_y_full)

# 不建议同时使用x和y方向(dx=1,dy=1效果不佳)
yuan_xy = cv2.Sobel(yuan, 1, 1, 1)
cv2.imshow("yuan_xy", yuan_xy)

# 使用加权运算组合x和y方向的边缘
yuan_xy_full = cv2.addWeighted(yuan_x_full, 1, yuan_y_full, 1, 0)
cv2.imshow('yuan_xy_full', yuan_xy_full)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

封装为函数:

def sobel_edge(img, flag=None):
"""Sobel边缘检测"""
if isinstance(img, str):
img = cv2.imread(img, flag)
elif flag == 0 and len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# X方向梯度(CV_64F保存负数)
img_x_64 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
img_x_full = cv2.convertScaleAbs(img_x_64)

# Y方向梯度(CV_64F保存负数)
img_y_64 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
img_y_full = cv2.convertScaleAbs(img_y_64)

# 加权组合X和Y方向边缘
img_xy_full = cv2.addWeighted(img_x_full, 1, img_y_full, 1, 0)
return img_xy_full

# 分别用彩色图和灰度图测试Sobel效果
a = cv2.imread("yys_1.jpg")
a = cv2.resize(a, None, fx=0.7, fy=0.7)
b = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY)

cv2.imshow("sobel_edge_color", sobel_edge(a))
cv2.imshow("sobel_edge_gray", sobel_edge(b))
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

4.2 Scharr算子

Scharr算子是Sobel算子的改进版,对边缘检测更敏感,核大小固定为3×3。

def scharr_edge(img, flag=None):
"""Scharr边缘检测(Sobel的改进版,对边缘更敏感)"""
if isinstance(img, str):
img = cv2.imread(img, flag)
elif flag == 0 and len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# X方向梯度(Scharr对边缘更敏感)
img_x_64 = cv2.Scharr(img, cv2.CV_64F, 1, 0)
img_x_full = cv2.convertScaleAbs(img_x_64)

# Y方向梯度
img_y_64 = cv2.Scharr(img, cv2.CV_64F, 0, 1)
img_y_full = cv2.convertScaleAbs(img_y_64)

# 加权组合X和Y方向边缘
img_xy_full = cv2.addWeighted(img_x_full, 1, img_y_full, 1, 0)
return img_xy_full

# 分别用彩色图和灰度图测试Scharr效果
cv2.imshow("scharr_edge_color", scharr_edge(a))
cv2.imshow("scharr_edge_gray", scharr_edge(b))
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

4.3 Laplacian算子

Laplacian算子通过二阶导数检测边缘,对噪声敏感。

核心函数:cv2.Laplacian(src, ddepth[, ksize])

参数说明
src 输入图像
ddepth 输出图像的深度
ksize 算子大小,默认为3

# Laplacian边缘检测函数
def laplacian_edge(img, flag=None):
"""Laplacian边缘检测(对噪声敏感)"""
if isinstance(img, str):
img = cv2.imread(img, flag)
elif flag == 0 and len(img.shape) == 3:
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# Laplacian边缘检测
img_lap = cv2.Laplacian(img, cv2.CV_64F, ksize=3)
img_lap_full = cv2.convertScaleAbs(img_lap)
return img_lap_full

# 分别用彩色图和灰度图测试Laplacian效果
cv2.imshow("laplacian_edge_color", laplacian_edge(a))
cv2.imshow("laplacian_edge_gray", laplacian_edge(b))
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

4.4 Canny边缘检测

Canny算法是最经典、最完善的边缘检测方法。

核心函数:cv2.Canny(src, threshold1, threshold2)

参数说明
src 输入图像
threshold1 低阈值
threshold2 高阈值

def canny_edge(img, thresh1=100, thresh2=150):
"""Canny边缘检测(输入彩色或灰度均可,输出为二值边缘图)"""
return cv2.Canny(img, thresh1, thresh2)

cv2.imshow("canny_edge", canny_edge(a))
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

阈值参考:

场景threshold1threshold2
边缘清晰,噪声少 100 200
边缘较淡,需敏感 30 100
通用起始值 50 150
强边缘,抗噪声 150 250

调参技巧:先设 (100, 200),若边缘太多(噪声多)则提高阈值,若边缘太少(丢失细节)则降低阈值。

4.5 算子对比速查

算子特点适用场景
Sobel 计算简单 基础边缘检测
Scharr 对边缘更敏感 需要更精细边缘
Laplacian 对噪声敏感 提取孤立点或线
Canny 效果最好 通用边缘检测首选

五、图像形态学

形态学操作基于结构元素对二值图像进行形状处理,常用于二值图像的后处理。

5.1 结构元素创建

import cv2
import numpy as np

# 直接创建全1矩阵作为结构元素
kernel = np.ones((3, 3), np.uint8)

5.2 腐蚀

核心函数:cv2.erode(src, kernel, dst, anchor, iterations, borderType, borderValue)

参数说明
src 输入图像
kernel 用于腐蚀的结构元素,形状和大小直接影响腐蚀效果
iterations 腐蚀操作的迭代次数,默认为1,次数越多腐蚀效果越明显

sun = cv2.imread("sun.png")
cv2.imshow("img", sun)

# 腐蚀:用结构元素对图像进行卷积,只有结构元素完全覆盖的区域才保留
# kernel为结构元素,iterations=2表示连续腐蚀两次
erode_sun = cv2.erode(sun, kernel, iterations=2)
cv2.imshow('erode_sun', erode_sun)

在这里插入图片描述

5.3 膨胀

核心函数:cv2.dilate(img, kernel, iterations)

参数说明
img 目标图片
kernel 进行操作的内核
iterations 膨胀次数,默认为1

wenzi = cv2.imread('wenzi.png')
cv2.imshow('wenzi', wenzi)

# 膨胀:用结构元素对图像进行卷积,结构元素覆盖区域有任一像素为白则全部变白
# 用于连接断裂区域、扩大白色区域,iterations=1表示膨胀一次
dilate_wenzi = cv2.dilate(wenzi, kernel, iterations=1)
cv2.imshow('dilate_wenzi', dilate_wenzi)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

5.4 开运算与闭运算

核心函数:cv2.morphologyEx(src, op, kernel)

op操作作用
MORPH_OPEN 先腐蚀后膨胀 平滑物体的轮廓、断开较窄的狭颈并消除细的突出物
MORPH_CLOSE 先膨胀后腐蚀 弥合较窄的间断和细长的沟壑,消除小的孔洞,填补轮廓线

# 开运算:先腐蚀后膨胀,去除小噪点,分离粘连物体
zhiwen = cv2.imread('zhiwen.png')
cv2.imshow('zhiwen', zhiwen)

open_zhiwen = cv2.morphologyEx(zhiwen, cv2.MORPH_OPEN, kernel)
cv2.imshow('open_zhiwen', open_zhiwen)

# 闭运算:先膨胀后腐蚀,填充小空洞,连接断裂区域
zhiwen_duan = cv2.imread('zhiwen_duan.png')
cv2.imshow('zhiwen_duan', zhiwen_duan)

close_zhiwen_duan = cv2.morphologyEx(zhiwen_duan, cv2.MORPH_CLOSE, kernel, iterations=2)
cv2.imshow('close_zhiwen_duan', close_zhiwen_duan)

cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

在这里插入图片描述

5.5 梯度运算

梯度运算 = 膨胀 – 腐蚀,作用:突出显示图像中强度变化剧烈的地方(即物体轮廓)。

核心函数:cv2.morphologyEx(img, op, kernel)

参数说明
img 输入图像
op 操作类型,梯度运算为 MORPH_GRADIENT
kernel 结构元素

# 梯度运算 = 膨胀 – 腐蚀,用于提取物体轮廓
gradient_wenzi = cv2.morphologyEx(wenzi, cv2.MORPH_GRADIENT, kernel)
cv2.imshow('wenzi', wenzi)
cv2.imshow('gradient_wenzi', gradient_wenzi)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

5.6 顶帽与黑帽

操作公式作用
顶帽 原始图像 – 开运算结果 提取比周围区域亮的细节
黑帽 闭运算结果 – 原始图像 提取比周围区域暗的细节

cv2.imshow('sun', sun)

# 开运算:先腐蚀后膨胀,去除小噪点
open_sun = cv2.morphologyEx(sun, cv2.MORPH_OPEN, kernel)
cv2.imshow('open_sun', open_sun)

# 顶帽:原图 – 开运算,提取亮色细节
tophat = cv2.morphologyEx(sun, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat', tophat)

# 闭运算:先膨胀后腐蚀,填充小空洞
close_sun = cv2.morphologyEx(sun, cv2.MORPH_CLOSE, kernel)
cv2.imshow('close_sun', close_sun)

# 黑帽:闭运算 – 原图,提取暗色细节
blackhat = cv2.morphologyEx(sun, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat', blackhat)

cv2.waitKey(0)
cv2.destroyAllWindows()

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

5.7 形态学操作速查

操作作用
腐蚀 缩小白色区域,去噪点
膨胀 扩大白色区域,填空洞
开运算 平滑轮廓,消除细突出物
闭运算 弥合间断,消除小孔洞
梯度运算 提取物体轮廓
顶帽 提取比周围区域亮的细节
黑帽 提取比周围区域暗的细节

六、总结

核心函数速查表

类别函数一句话用法
全局阈值 threshold() 手动设阈值二值化
均值滤波 blur() 快速去噪,会模糊
高斯滤波 GaussianBlur() 去噪首选,保留边缘好
中值滤波 medianBlur() 专治椒盐噪声
边界填充 copyMakeBorder() 添加图像边框
Sobel梯度 Sobel() 计算图像梯度
Canny边缘检测 Canny() 边缘检测标准方法
腐蚀 erode() 缩小白色区域
膨胀 dilate() 扩大白色区域
形态学 morphologyEx() 开/闭/梯度运算

参数快速参考

操作常用参数
高斯滤波核 (3,3)轻度、(5,5)中度、(7,7)重度
Canny阈值 (50,150)通用起始值
自适应阈值blockSize 11、15、21
形态学核 (3,3)轻度、(5,5)中度
边界填充像素数 视需求而定,通常5~50

常见陷阱

陷阱正确做法
滤波后丢失细节 尝试双边滤波或缩小核
阈值分割效果差 先增强对比度再分割
Canny检测到过多噪声 先高斯滤波再Canny
形态学操作过度 减少iterations或使用更小核
输入彩色图到阈值函数 先cvtColor转灰度图

系列直达

  • 上篇:OpenCV入门学习(一):图像的基本操作
  • 本篇:OpenCV入门学习(二):图像预处理(本文)
  • 下篇:OpenCV入门学习(三):特征提取与匹配
赞(0)
未经允许不得转载:171主机测评 » OpenCV入门学习(二):图像预处理
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址