环境配置
Pycharm直接pip install安装opencv-python或opencv-contrib-python,注意的是安装opencv-python-headless会导致imshow等涉及UI的方法不能用。
其中,opencv-python只包含了OpenCV的主要模块,而opencv-contrib-python还包含了一些拓展模块,两者都适用于桌面环境,而opencv-python-headless是用于服务器环境的无头软件包,无UI界面,看需求安装即可。
轮廓检测
先上代码
import cv2
import numpy as np
img = cv2.imread("img.png", cv2.IMREAD_UNCHANGED)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
img = cv2.erode(img, kernel)
img = cv2.dilate(img, kernel)
img = cv2.medianBlur(img, 3)
ret, thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh , cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.drawContours(img, contours, -1, (0, 255, 0), 2)
for c in contours:
x, y, w, h = cv2.boundingRect(c)
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
for c in contours:
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img, [box], 0, (0, 0, 255), 2)
for c in contours:
(x, y), radius = cv2.minEnclosingCircle(c)
center = (int(x), int(y))
radius = int(radius)
cv2.circle(img, center, radius, (255, 0, 0), 2)
cv2.imshow("title", img)
cv2.imwrite("img2.png", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
如上就可以完成简单的轮廓检测,再来细看涉及的具体方法。
图像基本操作
读取:imread()
cv2.imread(filepath, flags)
- filepath: 图片路径
- flags: 读入图片类型
- cv2.IMREAD_COLOR: 默认参数,读入一副彩色图片,忽略alpha通道,可用1作为实参替代。
- cv2.IMREAD_GRAYSCALE: 读入灰度图片,可用0作为实参替代。
- cv2.IMREAD_UNCHANGED: 读入完整图片,包括alpha通道,可用-1作为实参替代。
保存:imwrite()
cv2.imwrite(filepath,img,num=None)
- filepath: 保存路径
- img: 需保存图片
- num: 针对特定的格式。对于JPEG,其表示的是图像的质量,用0 - 100的整数表示,默认95;对于png ,第三个参数表示的是压缩级别,默认为3。
例:
cv2.imwrite('img.jpg', img, [int(cv2.IMWRITE_JPEG_QUALITY), 95])
cv2.imwrite('img.png', img, [int(cv2.IMWRITE_PNG_COMPRESSION), 5])
由于cv2.IMWRITE_JPEG_QUALITY和cv2.IMWRITE_PNG_COMPRESSION类型为Long,所以必须转换成int型。
显示:imshow()
cv2.imshow(wname, img)
cv2.waitKey(0)
cv2.destroyAllWindows() # 或 dv2.destroyWindow(wname)
- wname: 窗口名
- img: 显示图像
- cv2.waitKey: 等待键盘输入。单位为毫秒,即等待指定的毫秒数看是否有键盘输入,若在等待时间内按下任意键则返回按键的ASCII码,程序继续运行。若没有按下任何键,超时后返回-1。参数为0表示无限等待。不调用waitKey的话,窗口会一闪而逝,看不到显示的图片。
显示视频时,延迟时间需要设置为 大于0的参数。delay > 0时 , 延迟 ”delay”ms, 在显示视频时这个函数是有用的,用于设置在显示完一帧图像后程序等待 ”delay”ms 再显示下一帧视频;如果使用 waitKey(0) 则只会显示第一帧视频。
- cv2.destroyAllWindow(): 销毁所有窗口
- cv2.destroyWindow(wname): 销毁指定窗口
翻转:flip()
img = cv2.flip(img, flipcode)
- img: 待翻转图像
- flipcode: 翻转效果
- flipcode = 0: 沿x轴翻转
- flipcode > 0: 沿y轴翻转
- flipcode < 0: x, y轴同时翻转
复制:copy()
img2 = img.copy()
颜色类型转换:cvtColor()
img = cv2.cvtColor(img, flag)
- flag: 待转换图片
- flag: 转换类型
- cv2.COLOR_BGR2GRAY: BGR彩图转换为灰度图
- cv2.COLOR_BGR2HSV: BGR彩图转换为HSV彩图
- ……更多类型格式类似,不赘述
补充:HSV空间中,H表示色彩/色度,取值范围 [0,179],S表示饱和度,取值范围 [0,255],V表示亮度,取值范围 [0,255]。但是不同的软件使用值不同。
使用如下命令可以得到可以使用的flags:
import cv2
flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
print(flags)
轮廓检测相关
轮廓检测:findContours()
countours, hierarchy = cv2.findContours(image, mode, method, contours=None, hierarchy=None, offset=None)
- image: 原图
- mode: 轮廓的检索模式,有四种,常用的是 cv2.RETR_EXTERNAL
- cv2.RETR_EXTERNAL: 表示只检测外轮廓
- cv2.RETR_LIST: 检测的轮廓不建立等级关系
- cv2.RETR_CCOMP: 建立两个等级的轮廓,上面的一层为外边界,里面的一层为内孔的边界信息。如果内孔内还有一个连通物体,这个物体的边界也在顶层
- cv2.RETR_TREE: 建立一个等级树结构的轮廓
- method: 轮廓的近似办法,常用 cv2.CHAIN_APPROX_SIMPLE
- cv2.CHAIN_APPROX_NONE: 存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
- cv2.CHAIN_APPROX_SIMPLE: 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
注意:
OpenCV2和OpenCV4中,findContours这个轮廓提取函数会返回两个值:
轮廓的点集(contours),各层轮廓的索引(hierarchy)
OpenCV3中则会返回三个值:
处理的图像(image),轮廓的点集(contours),各层轮廓的索引(hierarchy)
轮廓绘制:drawContours()
cv2.drawContours(image, contours, contourIdx, color, thickness=None, lineType=None, hierarchy=None, maxLevel=None, offset=None)
- image: 绘制于哪张图像,图像为三通道图时才能显示轮廓
- contours: 轮廓本身,在Python中是一个list
- contourIdx: 指定绘制轮廓list中的哪条轮廓。如果是-1,则绘制其中的所有轮廓
- color: 轮廓绘制颜色
- thickness: 轮廓线的宽度,如果是-1(cv2.FILLED),则为填充模式
图像处理相关
二值化:threshold ()
retval, dst = cv2.threshold (src, thresh, maxval, type)
- retval: 阈值
- dst: 返回图片
- src: 源图片
- thresh: 阈值
- maxval: 填充色
- type: 阈值类型
- THRESH_BINARY: 二进制阈值化,也可以用0表示
- THRESH_BINARY_INV: 反二进制阈值化
- THRESH_TRUNC: 截断阈值化
- THRESH_TOZERO: 阈值化为0
- THRESH_TOZERO_INV: 反阈值化为0
- ……
二值化:inRange()
img = cv2.inRange(src, lowerb, upperb, dst=None)
即介于lowerb/upperb之间的为白色,其余黑色
- src: 输入要处理的图像,可以为单通道或多通道。
- lowerb: 包含下边界的数组或标量。
- upperb: 包含上边界数组或标量。
- dst: 输出图像,与输入图像src 尺寸相同且为CV_8U 类型。该函数输出的dst是一幅二值化之后的图像。
例:
lower_val = np.array([20, 20, 20])
upper_val = np.array([200, 200, 200])
img = cv2.inRange(img, lower_val, upper_val)
不难发现,比起threshold,inRange可以针对多通道进行操作。
位与运算:bitwise_and()
dst = cv.bitwise_and(src1, src2, dst=None, mask=None)
- src1, src2: 为输入图像或标量,标量可以为单个数值或一个四元组
- dst: 可选输出变量,如果需要使用非None则要先定义,且其大小与输入变量相同
- mask: 掩码图像,可选参数,为8位单通道的灰度图像,用于指定要更改的输出图像数组的元素,即输出图像像素只有mask对应位置元素不为0的部分才输出,否则该位置像素的所有通道分量都设置为0。
噪声与滤波
添加噪声:random_noise()
为浮点型图片添加各种随机噪声。
用到了scikit-image:import skimage
random_noise(image, mode='gaussian', seed=None, clip=True, **kwargs)
- image: 输入图片(将会被转换成浮点型),ndarray型
- mode: 可选择,str型,表示要添加的噪声类型
- gaussian: 高斯噪声
- localvar: 高斯分布的加性噪声,在“图像”的每个点处具有指定的局部方差。
- poisson: 泊松再生
- salt: 盐噪声,随机将像素值变成1
- pepper: 椒噪声,随机将像素值变成0或-1,取决于矩阵的值是否带符号
- s&p: 椒盐噪声
- speckle: 均匀噪声(均值mean方差variance),out=image+n*image
- seed: 可选的,int型,如果选择的话,在生成噪声前会先设置随机种子以避免伪随机
- clip: 可选的,bool型,如果是True,在添加均值,泊松以及高斯噪声后,会将图片的数据裁剪到合适范围内。如果谁False,则输出矩阵的值可能会超出[-1,1]
- mean: 可选的,float型,高斯噪声和均值噪声中的mean参数,默认值=0
- va: 可选的,float型,高斯噪声和均值噪声中的方差,默认值=0.01(注:不是标准差)
- local_vars: 可选的,ndarry型,用于定义每个像素点的局部方差,在localvar中使用
- amount: 可选的,float型,是椒盐噪声所占比例,默认值=0.05
- salt_vs_pepper: 可选的,float型,椒盐噪声中椒盐比例,值越大表示盐噪声越多,默认值=0.5,即椒盐等量
- 返回值: ndarry型,且值在[0,1]或者[-1,1]之间,取决于是否是有符号数
均值滤波:blur()
均值滤波是指任意一点的像素值,都是周围N*M个像素值的均值。
result = cv2.blur(img, kernel)
- img: 当前的图片
- kernel: 进行均值滤波的矩阵(核)的大小
方框滤波:boxFilter()
方框滤波和均值滤波核基本一致,区别是需不需要均一化处理。
result = cv2.boxFilter(src, ddepth, kernel, normalize)
- img: 当前的图片
- kernel: 进行均值滤波的矩阵(核)的大小
- ddepth: int类型,通常用-1表示与原始图像一致
- normalize: 表示是否对目标图像进行归一化处理。当normalize为true时需要执行均值化处理,当normalize为false时,不进行均值化处理,实际上为求周围各像素的和,很容易发生溢出,溢出时均为白色,对应像素值为255。如果省略参数normalize,则默认是进行归一化处理。如果normalize=0则不进行归一化处理,像素值为周围像素之和,图像更多为白色。
中值滤波:medianBlur()
中值滤波是非线性的图像处理方法,在去噪的同时可以兼顾到边界信息的保留。选一个含有奇数点的窗口W,将这个窗口在图像上扫描,把窗口中所含的像素点按灰度级的升或降序排列,取位于中间的灰度值来代替该点的灰度值。
result = cv2.medianBlur(src, kernel)
- img: 当前的图片
- kernel: 进行均值滤波的矩阵(核)的大小
双边滤波:bilateralFilter()
双边滤波是一个非线性滤波,采用的也是加权求和的方法,其权值矩阵由一个与空间距离相关的高斯函数和一个与灰度距离相关的高斯函数相乘得到。它可以达到保持边缘、降噪平滑的效果。
dst = cv.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]])
- d: 过滤期间使用的各像素邻域的直径
- 过大的滤波器(d>5)执行效率低。
- 对于实时应用,建议取d=5
- 对于需要过滤严重噪声的离线应用,可取d=9
- d>0时,由d指定邻域直径
- d<=0时,d会自动由sigmaSpace的值确定,且d与sigmaSpace成正比
- sigmaColor: 色彩空间的sigma参数,该参数较大时,各像素邻域内相距较远的颜色会被混合到一起,从而造成更大范围的半相等颜色
- sigmaSpace: 坐标空间的sigma参数,该参数较大时,只要颜色相近,越远的像素会相互影响
- 简单起见,可以令2个sigma的值相等;
- 如果他们很小(小于10),那么滤波器几乎没有什么效果;
- 如果他们很大(大于150),那么滤波器的效果会很强,使图像显得非常卡通化
高斯滤波:GaussianBlur()
图像高斯平滑也是邻域平均的思想对图像进行平滑的一种方法,在图像高斯平滑中,对图像进行平均时,不同位置的像素被赋予了不同的权重。高斯平滑与简单平滑不同,它在对邻域内像素进行平均时,给予不同位置的像素不同的权值。高斯滤波让临近的像素具有更高的重要度,对周围像素计算加权平均值,较近的像素具有较大的权重值。
result = cv2.GaussianBlur(src, kernel, sigmaX)
- img: 当前的图片
- kernel: 进行均值滤波的矩阵(核)的大小
- sigmaX: X方向方差,通常为0
形态学处理
结构元:getStructuringElement()
形态学处理的核心就是定义结构元素,在OpenCV-Python中,可以使用其自带的getStructuringElement函数,也可以直接使用NumPy的ndarray来定义一个结构元素。
kernel = getStructuringElement(shape, ksize, anchor)
- shape: 结构元形状
- MORPH_RECT: 表示产生矩形的结构元
- MORPH_ELLIPSEM: 表示产生椭圆形的结构元
- MORPH_CROSS: 表示产生十字交叉形的结构元
- ksize: 表示结构元的尺寸,即(宽,高),必须是奇数
- anchor: 表示结构元的锚点,即参考点。默认值Point(-1, -1)代表中心像素为锚点
膨胀:dilate()
跟卷积操作类似,假设有图像A和结构元素B,结构元素B在A上面移动,其中B定义其中心为锚点,计算B覆盖下A的最大像素值用来替换锚点的像素,其中B作为结构体可以是任意形状。我们回忆一下中值平滑操作——取每一个位置的矩形领域内值的中值作为该位置的输出灰度值,图像的膨胀操作与中值平滑操作类似,它是取每一个位置的矩形领域内值的最大值作为该位置的输出灰度值。
img = cv2.dilate(src, element)
- src: 表示输入矩阵
- elemen: 表示结构元,即 函数getStructuringElement( )的返回值
- anchor: 结构元的锚点,即参考点
- iterations: 膨胀操作的次数,默认为一次
- borderType: 边界扩充类型
- borderValue: 边界扩充值
腐蚀:erode()
腐蚀操作与膨胀操作类似,只是它取结构元所指定的领域内值的最小值作为该位置的输出灰度值。因为取每个位置领域内最小值,所以腐蚀后输出图像的总体亮度的平均值比起原图会有所降低,图像中比较亮的区域的面积会变小甚至消失,而较暗物体的尺寸会扩大。
img = cv2.erode(src, element)
- src: 表示输入矩阵
- elemen: 表示结构元,即 函数getStructuringElement( )的返回值
- anchor: 结构元的锚点,即参考点
- iterations: 膨胀操作的次数,默认为一次
- borderType: 边界扩充类型
- borderValue: 边界扩充值
形态学操作扩展:morphologyEx()
morphologyEx(src, op, kernel, dst=None, anchor=None, iterations=None, borderType=None, borderValue=None)
- src: 输入图像
- op: 处理操作
- cv2.MORPH_ERODE:腐蚀操作。与erode效果相同。
- cv2.MORPH_DILATE: 膨胀操作。与dilate效果相同。
- cv2.MORPH_OPEN: 开运算。用于除外部噪音。原理:开运算 = 膨胀(腐蚀(img))。腐蚀操作,去除了噪声,但是会压缩图像。对腐蚀过的图像进行膨胀处理,可去除噪声,并保持原形状。
- cv2.MORPH_CLOSE: 闭运算。用于除内部黑点。原理:闭运算 = 腐蚀(膨胀(img))。有助于去除前景物体内部的小孔,或者物体上的黑点。
- cv2.MORPH_GRADIENT: 梯度运算。用于获取边界。原理:梯度 = 膨胀(img) - 腐蚀(img)
- cv2.MORPH_TOPHAT: 顶帽操作,也叫礼帽操作。用于获取外部噪音。原理:礼帽图像 = 原始 - 开运算
- cv2.MORPH_BLACKHAT: 黑帽操作。用于获取内部黑点 。原理:礼帽图像 = 闭运算 - 原始
- kernel: 卷积核(结构元),与腐蚀和膨胀的卷积核一样
边缘和轮廓检测算法
Canny算法
Canny算法是John F. Canny in 1986发明的一个多级边缘检测算法,也被很多人认为是边缘检测的最优算法,最优边缘检测的三个主要评价标准是:
低错误率:标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
高定位性:标识出的边缘要与图像中的实际边缘尽可能接近。
最小响应:图像中的边缘只能标识一次。
实现步骤如下:
1、应用高斯滤波来平滑图像,目的是去除噪声
2、找寻图像的强度梯度(intensity gradients)
3、应用非最大抑制(non-maximum suppression)技术来消除边误检(本来不是但检测出来是)
4、应用双阈值的方法来决定可能的(潜在的)边界
5、利用滞后技术来跟踪边界
edges=cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])
edges=cv2.Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]])
- image: 输入图像
- dx: 16-bit 输入图像的x导数(CV_16SC1或CV_16SC3)
- dy: 16-bit 输入图像的y导数(和dx有相同类型)
- edges: 输出的边缘图。单通道8-bit 图像,与输入图像有相同大小。
- threshold1: 第一个阈值,低阈值
- threshold2: 第二个阈值,高阈值
- aperture_size: 寻找图像梯度的Sobel内核的大小,默认值是3
- L2gradient: 指定求梯度大小的方程。如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开方),否则使用L1范数(直接将两个方向导数的绝对值相加)
Sobel算法
Sobel 算子是一个离散微分算子 (discrete differentiation operator)。 它用来计算图像灰度函数的近似梯度。Sobel 算子结合了高斯平滑和微分求导,因此它的抗噪声能力比较好。
使用 Sobel算子产生的输出图像上,检测到的亮起的边缘相素散布在更暗的背景中。
dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])
- src: 需要处理的图像
- ddepth: 图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度
- dx, dy: 求导的阶数,0表示这个方向上没有求导,一般为0、1、2
- dst: 目标图像
- ksize: 是算子的大小,必须为1、3、5、7。默认为1
- scale: 是缩放导数的比例常数,默认情况下没有伸缩系数
- delta: 是一个可选的增量,将会加到最终的dst中,同样,默认情况下没有额外的值加到dst中
- borderType: 是判断图像边界的模式。这个参数默认值为cv2.BORDER_DEFAULT
Laplacian算法
Laplacian算子是二阶边缘检测的典型代表。
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
- src: 需要处理的图像
- ddepth: 图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度