本文基于《OpenCV轻松入门:面向Python》,作者李立宗。
在RGB图像中,图像是由R、G、B三个通道构成的,但是在OpenCV中,通道是按照B、G、R的顺序存储的。
img = cv2.imread('lena.png')
cv2.imshow('show', img)
cv2.imwrite('new_lena.png', img)
若没有按键被按下,则返回-1;如果有按键被按下,则返回该按键的ASCII码。
cv2.namedWindow('window')
销毁指定窗口
cv2.destroyWindow('window') # 销毁指定窗口
cv2.destroyAllWindows() # 销毁所有窗口
用于访问图像的像素点。
对于灰度图
img.item(row, col)
对于RGB图像
img.item(row, col, channel)
用于修改图像的像素值。
对于灰度图
img.itemset((row, col), value)
对于RGB图像
img.itemset((row, col, channel), value)
b, g, r = cv2.split(img)
bgr = cv2.merge([b, g, r])
img.shape # 获取行数、列数、通道数
img.size # 行数×列数×通道数
img.dtype # 图像的数据类型
使用"+"对图像a,b进行求和运算时,若两个图像对应像素值的和小于或等于255时,则直接得到运算结果;若两个图像对应像素值的和大于255,则将运算结果对256取模。
使用cv2.add()函数进行求和运算时,若两个图像对应像素值的和小于或等于255时,则直接得到运算结果;若两个图像对应像素值的和大于255,则将运算结果为饱和值255。
cv2.add()参数的三种形式
cv2.add(img1,img2)
cv2.add(img,value)
cv2.add(value,img)
output=img1×alpha+img2×beta+gamma
output = cv2.addWeighted(img1, alpha, img2, beta, gamma)
cv2.bitwise_and(img1, img2) # 按位与
cv2.bitwise_or(img1, img2) # 按位或
cv2.bitwise_xor(img1, img2) # 按位异或
cv2.bitwise_not(img) # 按位取反
img = cv2.imread('lena.png', 0) # 将图像调整为灰度图
cv2.imshow('lena', img)
row, col = img.shape
x = np.zeros((row, col, 8), dtype=np.uint8) # 用于提取各个位平面
for i in range(8):
x[:, :, i] = 2 ** i
r = np.zeros((row, col, 8), dtype=np.uint8)
for i in range(8):
r[:, :, i] = cv2.bitwise_and(img, x[:, :, i]) # 提取位平面
mask = r[:, :, i] > 0 # 若值大于0则为True,否则为False
r[mask] = 255 # 若为True则将值替换为255
cv2.imshow(str(i), r[:, :, i])
通过对原始图像与密钥图像进行按位异或,可以实现加密;将加密后的图像与密钥图像再进行按位异或,可以实现解密。
img = cv2.imread('lena.png', 0) # 将图像调整为灰度图
key = np.random.randint(0, 256, size=[512, 512], dtype=np.uint8) # 生成密钥图像
encryption = cv2.bitwise_xor(img, key) # 加密
decryption = cv2.bitwise_xor(encryption, key) # 解密
cv2.imshow('encryption', encryption)
cv2.imshow('decryption', decryption)
将图像的最低有效位层替换为需要隐藏的二值图像,可以实现将二值图像隐藏的目的。由于二值图像处于图像的最低有效位上,所以对图像的影响非常不明显。
img = cv2.imread('image/lena.png', 0) # 将图像调整为灰度图
waterMark = cv2.imread('image/cmh.png', 0) # 读取水印图像
w = waterMark[:, :] > 0 # 若值大于0则为True,否则为False
waterMark[w] = 1 # 若为True则将值替换为1
row, col = img.shape
array1 = np.ones([row, col], np.uint8) # 构造值为1的矩阵
array254 = np.ones([row, col], np.uint8) * 254 # 构造值为254的矩阵
imgHigh7 = cv2.bitwise_and(img, array254) # 取出图像的高7位
encryption = cv2.bitwise_or(imgHigh7, waterMark) # 加密
decryption = cv2.bitwise_and(encryption, array1) # 解密
d = decryption[:, :] > 0 # 若值大于0则为True,否则为False
decryption[d] = 255 # 若为True则将值替换为255
cv2.imshow('encryption', encryption)
cv2.imshow('decryption', decryption)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将图像转换为灰度图
HSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 将图像转换为HSV空间
通过函数cv2.inRange()来判断图像内像素点的像素值是否在指定的范围内。如果处于该指定区间内,则对应位置上的值为255,如果不处于该指定区间内,则对应位置上的值为0。
mask = cv2.inRange(img, 100, 200) # 单通道图像
minValue = np.array([100, 100, 100])
maxValue = np.array([200, 200, 200])
mask = cv2.inRange(img, minValue, maxValue) # 多通道图像
H:色调(Hue,也成为色相)
S:饱和度(Saturation)
V:亮度(Value)
色调H:取值范围是[0,360],在OpenCV中,可以直接把色调的值除以2,得到[0,180]之间的值,以适应8位二进制的存储和表示范围。
饱和度S:取值范围是[0,1],灰度颜色的饱和度值为0。
亮度V:取值范围是[0,1]。
A通道,也叫alpha通道,表示透明度,取值范围是[0,255],表示从透明到不透明。
bgra255 = cv2.cvtColor(img, cv2.COLOR_BGR2BGRA) # 转换为BGRA色彩空间
b, g, r, a = cv2.split(bgra255) # 分离b,g,r,a
a[:, :] = 125
bgra125 = cv2.merge([b, g, r, a])
a[:, :] = 0
bgra0 = cv2.merge([b, g, r, a])
cv2.imwrite('image/bgra255.png', bgra255)
cv2.imwrite('image/bgra125.png', bgra125)
cv2.imwrite('image/bgra0.png', bgra0)
cv2.resize(img, dsize, fx, fy, interpolation)
在cv2.resize()函数中,图像的大小可以通过"dsize"或者"fx"、"fy"二者之一来指定。
如果指定参数dsize的值,无论是否指定了参数fx和fy的值,都由dsize来决定目标图像的大小。需要注意的是,dsize内的第一个参数对应缩放后图像的宽度(width,即列数col,与参数fx相关),第二个参数对应缩放后图像的高度(height,即行数row,与参数fy相关)。
如果参数dsize的值是None,那么目标图像的大小通过参数fx和fy来决定。
当缩小图像时,使用区域插值方式(cv2.INTER_AREA)能够得到最好的效果。当放大图像时,使用三次样条插值(cv2.INTER_CUBIC)方式和双线性插值(cv2.INTER_LINEAR)方式都能取得较好的效果。三次样条插值方式速度较慢。双线性插值方式速度相对较快且效果并不逊色,双线性插值方式是默认方式。
cv2.flip(img, flipCode)
flipCode参数的意义:
0:绕x轴旋转
正数:绕y轴旋转
负数:绕x轴,y轴同时旋转
cv2.warpAffine(img, M, dsize)
M代表一个2×3的变换矩阵
dsize代表输出图像的尺寸大小
output(x,y)=img(M11x+M12y+M13,M21x+M22y+M23)
平移:
将原始图像img向右移动100个像素,向下移动200个像素,则其对应关系为:
output(x,y)=img(x+100,y+200),得到M=[[1,0,100],[0,1,200]]
height, width = img.shape[:2]
M = np.float32([[1, 0, 100], [0, 1, 200]]) # 构造平移矩阵
move = cv2.warpAffine(img, M, (width, height)) # 平移
cv2.imshow('original', img)
cv2.imshow('move', move)
旋转:
进行旋转之前可以先通过**cv2.getRotationMatrix2D()**函数获取转换矩阵。该函数语法格式为:
retval = cv2.getRotationMatrix2D(center, angle, scale)
center:旋转的中心点
angle:旋转的角度,正数代表逆时针旋转,负数代表顺时针旋转
scale:变换尺度
以图像中心为原点,逆时针旋转45°,并缩小为原始图像的0.6倍,代码如下:
height, width = img.shape[:2]
M = cv2.getRotationMatrix2D((height / 2, width / 2), 45, 0.6) # 构造旋转矩阵
rotate = cv2.warpAffine(img, M, (width, height))
cv2.imshow('original', img)
cv2.imshow('rotate', rotate)
更复杂的仿射变换:
对于更复杂的仿射变换,可以用**cv2.getAffineTransform()**函数生成转换矩阵M。该函数语法格式为:
retval = cv2.getAffineTransform(pts1, pts2)
pts1代表输入图像的三个点坐标
pts2代表输出图像的三个点坐标
在该函数中,pts1、pts2都是包含三个二维数组(x,y)点的数组。上述参数通过函数定义了两个平行四边形。pts1、pts2的三个点分别对应平行四边形的左上角、右上角、左下角三个点。
row, col = img.shape[:2]
pts1 = np.float32([[0, 0], [col - 1, 0], [0, row - 1]])
pts2 = np.float32([[0, row * 0.33], [col * 0.85, row * 0.25], [col * 0.15, row * 0.7]])
M = cv2.getAffineTransform(pts1, pts2)
output = cv2.warpAffine(img, M, (col, row))
cv2.imshow('original', img)
cv2.imshow('result', output)
仿射可以将矩形映射为任意平行四边形,透视变换可以将矩形映射为任意四边形。
cv2.warpPerspective(img, M, dsize)
可以使用**cv2.getPerspectiveTransform()**函数生成转换矩阵M。该函数语法格式为:
retval = cv2.getPerspectiveTransform(pts1, pts2)
img = cv2.imread('image/perspective.bmp')
row, col = img.shape[:2]
pts1 = np.float32([[150, 50], [400, 50], [60, 450], [310, 450]])
pts2 = np.float32([[50, 50], [row - 50, 50], [50, col - 50], [row - 50, col - 50]])
M = cv2.getPerspectiveTransform(pts1, pts2) # 构造透视变换矩阵
output = cv2.warpPerspective(img, M, (col, row)) # 透视
cv2.imshow('original', img)
cv2.imshow('perspective', output)
把一幅图像内的像素点放置到另外一幅图像内的指定位置,这个过程叫做重映射。
cv2.remap(img, map1, map2, interpolation)
map1参数有两种可能的值:
表示(x,y)点的一个映射
表示CV_16SC2,CV_32FC1,CV_32FC2类型(x,y)点的x值
map2参数同样有两种可能的值:
interpolation表示插值方式
retval, output = cv2.threshold(img, thresh, maxval, type)
img = cv2.imread('image/lena.png', 0)
thresh1, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 二值化阈值处理
thresh2, binary_inv = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV) # 反二值化阈值处理
thresh3, trunc = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC) # 截断阈值化处理
thresh4, toZero_inv = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV) # 超阈值零处理
thresh5, toZero = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO) # 低阈值零处理
cv2.imshow('BINARY', binary)
cv2.imshow('BINARY_INV', binary_inv)
cv2.imshow('TRUNC', trunc)
cv2.imshow('TOZERO_INV', toZero_inv)
cv2.imshow('TOZERO', toZero)
可以使用**cv2.adaptiveThreshold()**来实现自适应阈值处理,该函数的语法格式为:
cv2.adaptiveThreshold(img, maxValue, adaptiveMethod, thresholdType, blockSize, C)
maxValue代表最大值
adaptiveMethod代表自适应方法
thresholdType代表阈值处理方式,该值必须是cv2.THRESH_BINARY或者cv2.THRESH_BINARY_INV中的一个
blockSize代表块大小。表示一个像素在计算其阈值时所使用的邻域尺寸。
C是常量
自适应阈值等于每个像素由参数blockSize所指定邻域的加权平均值减去常量C。adaptiveMethod参数包含两种不同的方法:
img = cv2.imread('image/computer.jpg', 0)
thresh1, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY) # 二值化阈值处理
mean = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 3)
gaussian = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 3)
cv2.imshow('BINARY', binary)
cv2.imshow('MEAN', mean)
cv2.imshow('GAUSSIAN', gaussian)
Otsu方法能根据当前图像给出最佳的类间分割阈值。Otsu方法会遍历所有可能阈值,从而找到最佳的阈值。
通过在函数cv2.threshold()中对参数type的类型多传递一个参数cv2.THRESH_OTSU,即可实现Otsu方法的阈值分割。在使用Otsu方法时,要把阈值设为0
img = cv2.imread('image/lena.png', 0)
thresh, otsu = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # Otsu处理
cv2.imshow('OTSU', otsu)
均值滤波是指用当前像素点周围N×N个像素值的均值来代替当前的像素值。
cv2.blur(img, ksize, anchor, borderType)
函数**cv2.blur()**的一般形式为:cv2.blur(img, ksize)
img = cv2.imread('image/lenaNoise.png')
output = cv2.blur(img, (5, 5)) # 均值滤波
cv2.imshow('original', img)
cv2.imshow('blur', output)
方框滤波可以自由选择是否对均值滤波的结果进行归一化,即可以自由选择滤波结果是邻域像素值之和的平均值,还是邻域像素值之和。
cv2.boxFilter(img, ddepth, ksize, anchor, normalize, borderType)
ddepth是处理结果图像的图像深度,一般使用-1表示与原始图像使用相同的图像深度。
ksize是滤波核的大小。滤波核大小是指在滤波处理中,其邻域图像的高度和宽度。
anchor是锚点,其默认值为(-1,-1),表示当前计算均值的点位于核的中心点位置。
normalize表示在滤波时是否进行归一化处理,默认值为1
borderType是边界样式,该值决定了以何种方式处理边界。
函数**cv2.boxFilter()**的一般形式为:cv2.boxFilter(img, ddepth, ksize)
img = cv2.imread('image/lenaNoise.png')
output = cv2.boxFilter(img, -1, (5, 5)) # 方框滤波
cv2.imshow('original', img)
cv2.imshow('boxFilter', output)
在进行均值滤波和方框滤波时,其邻域内每个像素的权值是相等的。在高斯滤波中,会将中心点的权重值加大,远离中心点的权重值减小。
cv2.GaussianBlur(img, ksize, sigmaX, sigmaY, borderType)
ksize是滤波核的大小。滤波核大小是指在滤波处理中,其邻域图像的高度和宽度。ksize的宽度和高度可以不相同,但是它们必须是奇数
sigmaX是卷积核在X轴方向(水平方向)的标准差
sigmaY是卷积核在Y轴方向(垂直方向)的标准差。如果将该值设置为0,则只采用sigmaX的值;如果sigmaX和sigmaY都是0,则通过ksize.width和ksize.height计算得到
borderType是边界样式,该值决定了以何种方式处理边界
函数**cv2.GaussianBlur()**的一般形式为:cv2.GaussianBlur(img, ksize, 0, 0)
img = cv2.imread('image/lenaNoise.png')
output = cv2.GaussianBlur(img, (5, 5), 0, 0) # 高斯滤波
cv2.imshow('original', img)
cv2.imshow('GaussianBlur', output)
中值滤波用邻域内所有像素值的中间值来代替当前像素点的像素值。
cv2.medianBlur(img, ksize)
img = cv2.imread('image/lenaNoise.png')
output = cv2.medianBlur(img, 3) # 中值滤波
cv2.imshow('original', img)
cv2.imshow('medianBlur', output)
前述滤波方式基本只考虑了空间的权重信息,计算起来比较方便,但是在边缘信息的处理上存在较大的问题。双边滤波在计算某一个像素点的新值时,不仅考虑距离信息(距离越远,权重越小),还考虑色彩信息(色彩差别越大,权重越小)。双边滤波综合考虑距离和色彩的权重结果,既能够有效的去除噪声,又能够较好的保护边缘信息。
cv2.bilateralFilter(img, d, sigmaColor, sigmaSpace)
为了简单起见,可以将sigmaColor和sigmaSpace的值设置为相同的。一般情况下,10 我们有时希望使用特定的卷积核实现卷积操作,OpenCV允许用户自定义卷积核实现卷积操作,使用OpenCV的自定义卷积核实现卷积操作的函数是cv2.filter2D()。 cv2.filter2D(img, ddepth, kernel) 腐蚀能够将图像的边界点消除,使图像沿着边界向内收缩,也可以将小于指定结构体元素的部分去除。 在腐蚀过程中,通常使用一个结构元来逐个像素地扫描要被腐蚀的图像,并根据结构元和被腐蚀图像的关系来确定腐蚀结果。 cv2.erode(img, kernel) 膨胀操作能对图像的边界进行扩张。 cv2.morphologyEx(img, op, kernel) 开运算先将图像腐蚀,再将所得图像膨胀。开运算可以用于去噪、计数等。 cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) 闭运算是先膨胀、后腐蚀的运算,它有助于关闭前景物体内部的小孔,或去除物体上的小黑点,还可以将不同的前景图像进行连接。 cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) 形态学梯度运算是用图像的膨胀图像减腐蚀图像的操作,可以获取原始图像中前景图像的边缘。 顶帽运算是用原始图像减去开运算所得图像的操作,能够获取图像的噪声信息,或者得到比原始图像的边缘更亮的边缘信息。 cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) 黑帽运算是用闭运算图像减去原始图像的操作,能够获取图像内部的小孔,或者前景色中的小黑点,或者得到比原始图像的边缘更暗的边缘部分。 在进行形态学操作时,必须使用一个特定的核,该核可以自定义生成,也可以通过函数**cv2.getStructuringElement()**生成。该函数的语法格式为: cv2.getStructuringElement(shape, ksize) 在OpenCV中,使用函数**cv2.Sobel()**实现Sobel算子运算,该函数的语法格式为: cv2.Sobel(img, ddepth, dx, dy, [ksize]) ddepth代表输出图像的深度,通常设置为cv2.CV_64F dx代表x方向上的求导阶数 dy代表y方向上的求导阶数 ksize代表Sobel核的大小,该值为-1时,则会使用Scharr算子进行运算 在实际操作中,计算梯度值可能会出现负数,如果处理的图像是8位图类型,则在ddepth为-1时,意味着指定运算结果也是8位图类型,那么所有负数会自动截断为0,发生信息丢失。所以,在运算时要先使用更高的数据类型cv2.CV_64F,再通过取绝对值将其映射为cv2.CV_8U(8位图)类型。所以通常将ddepth设置为cv2.CV_64F。 在OpenCV中,使用函数**cv2.convertScaleAbs()**对参数取绝对值,该函数的语法格式为: cv2.convertScaleAbs(img[, alpha[, beta]]) 参数dx和dy通常的值为0或1,最大值为2。如果是0,表示在该方向上没有求导。dx和dy不能同时为0。 Scharr算子具有和Sobel算子同样的速度,且精度更高。OpenCV提供了函数**cv2.Scharr()**来计算Scharr算子。其语法格式为: cv2.Scharr(img, ddepth, dx, dy) ddepth代表输出图像的深度,应该设置为cv2.CV_64F dx代表x方向上的求导阶数 dy代表y方向上的求导阶数 **cv2.Scharr(img, ddepth, dx, dy)和cv2.Sobel(img, ddepth, dx, dy, -1)**是等价的。 **cv2.Scharr()**中,要求dx和dy满足:dx>=0 && dy>=0 && dx+dy==1 Laplacian算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。通常情况下,其算子的系数之和需要为0。 cv2.Laplacian(img, ddepth) 图像金字塔是由一幅图像的多个不同分辨率的子图所构成的图像集合。该组图像是由单个图像通过不断地降采样所产生的。图像金字塔的底部是待处理的高分辨率图像(原始图像),而顶部则是其低分辨率的近似图像。 最简单的图像金字塔可以通过不断地删除图像的偶数行和偶数列得到,也可以先对原始图像滤波,得到原始图像的近似图像,然后将近似图像的偶数行和偶数列删除以获取向下采样的结果,滤波器可以选择邻域滤波器、高斯滤波器。 OpenCV提供了函数**cv2.pyrDown()**用于实现图像金字塔操作中的向下采样,其语法形式为: cv2.pyrDown(img) 输出图像的大小为Size((img.col+1)/2, (img.row+1)/2) 向上采样时,先对像素点以补零的方式完成插值,将图像变为原来的两倍宽、两倍高,再将图像用高斯滤波器进行卷积运算,最后将图像内的每个像素点的值乘以4,以保证得到的像素值的范围依旧在[0,255]内。 OpenCV提供了函数**cv2.pyrUp()**用于实现图像金字塔操作中的向下采样,其语法形式为: cv2.pyrUp(img) 输出图像的大小为Size(img.col×2, img.row×2) Canny边缘检测分为以下几个步骤: OpenCV提供了函数**cv2.Canny()**来实现Canny边缘检测,其语法形式为: cv2.Canny(img, threshold1, threshold2) 在OpenCV中,函数cv2.findContours()用于查找图像的轮廓,并能够根据参数返回特定表示方式的轮廓。函数cv2.drawContours()能够将查找到的轮廓绘制到图像上,该函数可以根据参数在图像上绘制不同式样的轮廓。在OpenCV中,都是从黑色背景中查找白色对象。 **cv2.findContours()**的语法格式为: contours, hierarchy = cv2.findContours(img, mode, method) 返回值: 参数: 在OpenCV中,可以使用函数**cv2.drawContours()**绘制图像轮廓。该函数的语法格式为: image = cv2.drawContours(image, contours, contourIdx, color[, thickness]) image:待绘制轮廓的图像。函数会在图像image上直接绘制轮廓,参数image和返回值image在函数运算后的值是相同的,所以,如果图像image还有其他用途的话,则需要预先复制一份。 contours:需要绘制的轮廓 contourIdx:需要绘制的边缘索引,告诉函数要绘制某一条轮廓函数全部轮廓。如果该参数是一个正数或者零,则表示绘制对应索引号的轮廓;如果该值是负数,则表示绘制全部轮廓。 color:绘制的颜色,用BGR格式表示 thickness:可选参数,表示绘制轮廓时所用画笔的粗细。如果将该值设置为-1,则表示绘制实心轮廓。 OpenCV提供了函数**cv2.moments()**来获取图像的轮廓矩,其语法形式为: cv2.moments(array) 该函数的返回值是矩特征,主要包括: 函数**cv2.contourArea()**用于计算轮廓的面积,该函数的语法格式为: cv2.contourArea(contour) 函数**cv2.arcLength()**用于计算轮廓的面积,该函数的语法格式为: cv2.arcLength(curve, closed) Hu矩是归一化中心矩的线性组合。Hu矩在图像旋转、缩放、平移等操作后,仍能保持矩的不变性,所以经常会使用Hu矩来识别图像的特征。 在OpenCV中,使用函数**cv2.HuMoments()**可以得到Hu矩,该函数的语法格式为: cv2.HuMoments(m) 利用Hu矩判断两个对象的一致性结果比较抽象。为了更直观方便地比较Hu矩值,OpenCV提供了函数**cv2.matchShapes()**对两个对象的Hu矩进行比较,该函数的语法格式为: cv2.matchShapes(contour1, contour2, method, parameter) contour1:第一个轮廓或灰度图像 contour2:第二个轮廓或灰度图像 method:比较两个对象的Hu矩的方法 parameter:应用于method的特定参数。设置为0。 相同图像的matchShapes= 0.0 同一幅图的Hu矩是不变的,二者差值为0;相似的图像返回值的差较小;不相似图像返回值的差较大。 在计算轮廓时,可能并不需要实际的轮廓,而仅需要一个接近于轮廓的近似多边形。OpenCV提供了多种计算轮廓近似多边形的方法。 函数**cv2.boundingRect()**能够绘制轮廓的矩形边界,该函数的语法格式为: retval = cv2.boundingRect(points) 该函数还可以是具有4个返回值的形式:x, y, w, h = cv2.boundingRect(points) 函数**cv2.minAreaRect()**能够绘制轮廓的最小包围矩形框,该函数的语法格式为: retval = cv2.minAreaRect(points) 返回值retval的结构不符合函数cv2.drawContours()的参数结构,所以需要用函数**cv2.boxPoints()**将retval转换成符合要求的结构,该函数的语法格式为: points = cv2.boxPoints(box) 函数**cv2.minEnclosingCircle()**通过迭代算法构造一个对象的面积最小包围圆形,该函数的语法格式为: **center, radius = cv2.minEnclosingCircle(points) ** 函数**cv2.fitEllipse()**可以用来构造最优拟合椭圆,该函数的语法格式为: retval = cv2.fitEllipse(points) 函数**cv2.fitLine()**可以用来构造最优拟合直线,该函数的语法格式为: line = cv2.fitLine(points, distType, param, reps, aeps) line:对于二维直线,输出为4维,前两维代表拟合出的直线的方向,后两位代表直线上的一点。(即点斜式) points:轮廓 distType:距离类型。拟合直线时,要使输入点到拟合直线的距离之和最小,其类型如下表所示 param:距离参数,与所选的距离类型有关。当此参数设置为0时,会自动选择最优值 reps:拟合直线所需要的径向精度,通常设置为0.01 aeps:拟合直线所需要的角度精度,通常设置为0.01 函数**cv2.minEnclosingTriangle()**可以用来构造最小外包三角形,该函数的语法格式为: retval, triangle = cv2.minEnclosingTriangle(points) 函数**cv2.approxPolyDP()**可以用来构造指定精度的逼近多边形曲线,该函数的语法格式为: approxCurve = cv2.approxPolyDP(curve, epsilon, closed) 凸包指的是完全包含原有轮廓,并且仅由轮廓上的点所构成的多边形。凸包的每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部。 函数**cv2.convexHull()**可以用来获取轮廓的凸包,该函数的语法格式为: hull = cv2.convexHull(points[, clockwise[, returnPoints]]) points:轮廓 clockwise:布尔值。该值为True时,凸包角点将按顺时针方向排列;该值为False时,凸包角点将按逆时针方向排列。 returnPoints:布尔值。默认为True,函数返回凸包角点的x、y轴坐标;当为False时,函数返回轮廓中凸包角点的索引。 凸包与轮廓之间的部分称为凸缺陷。函数**cv2.convexityDefects()**可以用来获取凸缺陷,该函数的语法格式为: convexityDefects = cv2.convexityDefects(contour, convexhull) convexityDefects为凸缺陷点集。它是一个数组,每一行包含的值是[起点,终点,轮廓上距离凸包最远的点,最远点到凸包的近似距离],前三个值是轮廓点的索引,需要到轮廓点中去找它们 contour是轮廓 convexhull是凸包 需要注意的是,用cv2.convexityDefects()计算凸缺陷时,要使用凸包作为参数。在查找凸包时,所使用函数cv2.convexHull()的参数returnPoints的值必须是False。 判断轮廓是否是凸形的 函数**cv2.isContourConvex()**可以用来判断轮廓是否是凸形的,该函数的语法格式为: retval = cv2.isContourConvex(contour) 点到轮廓的距离 函数**cv2.pointPolygonTest()**可以用来计算点到多边形(轮廓)的最短距离,该函数的语法格式为: retval = cv2.pointPolygonTest(contour, pt, measureDist) 函数**cv2.createShapeContextDistanceExtractor()**可以用来计算形状场景距离,该函数的语法格式为: retval = cv2.createShapeContextDistanceExtractor() 该结果可以通过函数cv2.ShapeDistanceExtractor.computeDistance(contour1, contour2) 函数**cv2.createHausdorffDistanceExtractor()**可以用来计算形状场景距离,该函数的语法格式为: retval = cv2.createHausdorffDistanceExtractor() 宽高比 = 宽度(width) / 高度(height) 可以使用轮廓面积与矩形边界面积之比Extend来描述图像及其轮廓特征。 Extend= 轮廓面积(对象面积) / 矩形边界面积 可以使用轮廓面积与凸包面积之比Solidity来衡量图像、轮廓及凸包的特征。 Solidity= 轮廓面积(对象面积) / 凸包面积 该值是与轮廓面积相等的圆形的直径。 等效直径 = sqrt(4 × 轮廓面积 / π) 使用Numpy函数获取轮廓像素点: **numpy.nonzero()函数能够找出数组内非零元素的位置,但是其返回值是将行、列分别显示的。使用numpy.transpose()**函数处理上述返回值则可以得到这些点的(x,y)形式的坐标。 使用OpenCV函数获取轮廓像素点: **cv2.findNonZero()**函数可以用于查找非零元素的索引。该函数的语法格式为: idx = cv2.findNonZero(src) 函数**cv2.mean()**可以用来计算一个对象的平均颜色或平均灰度,该函数的语法格式为: mean_val = cv2.mean(img) 可以使用**matplotlib.pyplot.hist()**函数绘制直方图。此函数的作用是根据数据源和灰度级分组绘制直方图。其基本语法格式为: matplotlib.pyplot.hist(x, bins) 函数**cv2.calcHist()**可以用来计算图像的统计直方图,该函数能统计各个灰度级的像素点个数。该函数的语法格式为: hist = cv2.calcHist(img, channels, mask, histSize, ranges) 直方图均衡化的算法主要包括两个步骤: 在累计直方图的基础上,对原有灰度级空间进行转换可以在原有范围内对灰度级实现均衡化,也可以在更广泛的灰度空间范围内对灰度级实现均衡化。 在原有范围内实现直方图均衡化时,用当前灰度级的累计概率乘以当前灰度级的最大值,得到新的灰度级,并作为均衡化结果。 在更广泛的范围内实现直方图均衡化时,用当前灰度级的累计概率乘以更广泛的范围灰度级的最大值,得到新的灰度级,并作为均衡化结果。 函数**cv2.equalizeHist()**可以用来实现直方图均衡化。该函数的语法格式为: output = cv2.equalizeHist(img) Numpy提供函数**numpy.fft.fft2()**可以用来实现傅里叶变换。该函数的语法格式为: fft= numpy.fft.fft2(img) 图像频谱中的零频率分量位于频谱图像的左上角,为了便于观察通常会使用**numpy.fft.fftshift()**函数将零频率成分移动到频率图像的中心位置。该函数的语法格式为: fftShift = numpy.fft.fftshift(fft) 对图像进行傅里叶变换后,得到的是一个复数数组,为了显示为图像,需要将它们的值调整到[0, 255]的灰度空间内,使用公式为: output = 20×numpy.log(numpy.abs(fftShift)) 如果在傅里叶变换过程中使用了**numpy.fft.fftshift()函数移动零频率分量,那么在傅里叶逆变换过程中需要先使用numpy.fft.ifftshift()**函数将零频率分量移到原来的位置,再进行傅里叶逆变换。该函数的语法格式为: ifftShift = numpy.fft.ifftshift(原始频谱) 函数**numpy.fft.ifft2()**可以用来实现傅里叶逆变换,返回空间域复数数组。该函数的语法格式为: ifft = numpy.fft.ifft2(ifftShift) 傅里叶逆变换得到的空间域信息是一个复数数组需要将该信息调整到[0, 255]的灰度空间内,使用公式为: img = numpy.abs(ifft) 函数**cv2.dft()**可以用来实现傅里叶变换,返回空间域复数数组。该函数的语法格式为: dft= cv2.dft(img, flags) 此时,零频率分量并不在中心位置,为了处理方便需要将其移到中心位置,可以用**numpy.fft.fftshift()**实现。 dftShift = numpy.fft.fftshift(dft) 经过上述处理后,频谱图像还只是一个由实部和虚部构成的值。要将其显示出来,还要用**cv2.magnitude()**函数处理,该函数可以计算频谱信息的幅度,该函数的语法格式为: output = cv2.magnitude(x, y) output:频谱信息的幅度 x:实部 y:虚部 得到频谱信息的幅度后,还需要将幅度值映射到灰度空间[0, 255]内,使其以灰度图像的形式显示出来。 output = 20 * np.log(cv2.magnitude(x, y)) 如果在傅里叶变换过程中使用了**numpy.fft.fftshift()函数移动零频率分量,那么在傅里叶逆变换过程中需要先使用numpy.fft.ifftshift()**函数将零频率分量移到原来的位置,再进行傅里叶逆变换。该函数的语法格式为: idftShift = numpy.fft.ifftshift(原始频谱) 函数**cv2.idft()**可以用来实现傅里叶逆变换。该函数的语法格式为: idft= cv2.idft(idftShift) 进行傅里叶逆变换后得到的仍旧是复数,需要使用函数**cv2.magnitude()**计算其幅度。 模板匹配是指在当前图像A内寻找与图像B最相似的部分。模板匹配的操作方法是将模板图像B在图像A上滑动,遍历所有像素以完成匹配。 函数**cv2.matchTemplate()**可以用来实现模板匹配。该函数的语法格式为: cv2.matchTemplate(img, templ, method) 用cv2.TM_SQDIFF方法进行模板匹配 用cv2.TM_CCOEFF方法进行模板匹配 多模板匹配分为以下5个步骤: 函数**numpy.where()**能够获取模板匹配位置的集合。对于不同的输入,其返回值是不同的。 所以,函数np.where()可以找出在函数cv2.matchTemplate()的返回值中,哪些位置上的值是大于阈值threshold的。可以采用语句: loc = np.where(res >= threshold) loc是满足"res >= threshold"的像素点的索引集合 res是函数**cv2.matchTemplate()**进行模板匹配后的返回值 threshold是阈值 在获取匹配值的索引集合后,可以采用如下语句遍历所有匹配的位置,对这些位置做标记: 函数zip()用可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的元素。因此,如果希望循环遍历由np.where()返回的模板匹配索引集合,可以采用语句: 使用**np.where()得到的形式为"(行号,列号)"的位置索引,但是函数cv2.rectangle()**中指定顶点的参数的形式为"(列号,行号)"的位置索引。所以要进行行列互换,可以使用语句:loc[::-1] 函数**cv2.rectangle()**可以标记匹配图像的位置。 与笛卡尔坐标系对应,在霍夫坐标系中,横坐标采用笛卡尔坐标系中直线的斜率k,纵坐标使用笛卡尔坐标系中直线的截距b。 笛卡尔空间内的一条直线确定了霍夫空间内的一个点 霍夫空间内的一个点确定了笛卡尔空间内的一条直线 笛卡尔空间内的两个点会映射为霍夫空间内两条相交于(k, b)的直线 笛卡尔空间内的两个点对应的直线会映射为霍夫空间内的点(k, b) 在霍夫空间内,经过一个点的直线越多,说明其在笛卡尔空间内映射的直线是由越多的点所穿过的,那么它实际存在的可能性就越大,它的可靠性也越高。 假如有垂直于x轴的直线,它的斜率k为无穷大,截距b无法取值,可以将笛卡尔坐标系映射到极坐标系上。 函数**cv2.HoughLines()**可以用来实现霍夫直线变换。该函数要求所操作的原图像是一个二值图像,所以在进行霍夫变换之前要先将原图像进行二值化或者进行Canny边缘检测。该函数的语法格式为: lines = cv2.HoughLines(img, rho, theta, threshold) 为了更好地判断直线(线段),概率霍夫变换算法还对选取直线的方法作了两点改进: 接受直线的最小长度。如果有超过阈值个数的像素点构成了一条直线,但是这条直线很短,那么就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原图中并不存在这条直线。 接受直线时允许的最大像素点间距。如果有超过阈值个数的像素点构成了一条直线,但是这组像素点之间的距离都很远,就不会接受该直线作为判断结果,而认为这条直线仅仅是图像中的若干个像素点恰好随机构成了一种算法上的直线关系而已,实际上原图中并不存在这条直线。 函数**cv2.HoughLinesP()**可以用来实现霍夫直线变换。该函数要求所操作的原图像是一个二值图像,所以在进行霍夫变换之前要先将原图像进行二值化或者进行Canny边缘检测。该函数的语法格式为: lines = cv2.HoughLinesP(img, rho, theta, threshold, minLineLength, maxLineGap) lines中的每个元素都是一对浮点数,表示检测到的直线的参数,即(r, θ) img为原图像,必须是8位单通道二值图像 rho是以像素为单位的距离r的精度,一般情况下设置为1 theta是角度θ的精度,一般情况下设置为π/180,表示要搜索所有可能的角度 threshold是阈值。该值越小,判断出的直线就越多 minLineLength用来控制"接受直线的最小长度"的值 maxLineGap用来控制"接受直线时允许的最大像素点间距" 在霍夫圆变换中需要考虑圆半径和圆心(x坐标、y坐标)共三个参数。在OpenCV中采用的策略是两轮筛选,第一轮筛选找出可能存在的圆的位置(圆心),第二轮再根据第一轮的结果筛选出半径大小。 函数**cv2.HoughCircles()**可以用来实现霍夫圆变换。该函数要求所操作的原图像是一个二值图像,所以在进行霍夫变换之前要先将原图像进行二值化或者进行Canny边缘检测。该函数的语法格式为: circles = cv2.HoughCircles(img, method, dp, minDist, param1, param2, minRadius, maxRadius) circles :由圆心坐标和半径构成 img:原图像,类型为8位单通道灰度图像 method:检测方法,设置为cv2.HOUGH_GRADIENT dp:累计器分辨率,它是一个分割比率,用来指定图像分辨率与圆心累加器分辨率的比例 minDist:圆心间的最小间距,该值被当作阈值使用 param1:它对应的是Canny边缘检测器的高阈值,在缺省时默认值为100 param2:圆心位置必须收到的投票数。只有在第一轮筛选过程中投票数超过该值的圆才有资格进入第二轮的筛选,在缺省时默认值为100 minRadius:圆半径的最小值,小于该值的圆不会被检测出来。在缺省时默认值为0,该参数不起作用 maxRadius:圆半径的最大值,大于该值的圆不会被检测出来。在缺省时默认值为0,该参数不起作用 当图像内的各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景对象,但是如果图像内的子图连接在一起时,就很难确定前景对象了。此时借助距离变换函数**cv2.distanceTransform()**可以方便地将前景对象提取出来。 距离变换函数**cv2.distanceTransform()**计算二值图像内任意点到最近背景点的距离。一般情况下,该函数计算的是图像内非零值像素点到最近的零值像素点的距离,即计算二值图像中所有像素点距离其最近的值为0的像素点的距离。如果像素点本身的值为0,则这个距离也为0。 距离变换函数**cv2.distanceTransform()**的计算结果反映了各个像素与背景(值为0的像素点)的距离关系。通常情况下: 如果前景对象的中心(质心)距离值为0的像素点距离较远,会得到一个较大的值。 如果前景对象的边缘距离值为0的像素点较近,会得到一个较小的值。 如果对上述计算结果进行阈值化,就可以得到图像内子图的中心、骨架等信息。距离变换函数可以用于计算对象的中心,还能细化轮廓、获取图像前景等,有多种功能。 距离变换函数**cv2.distanceTransform()**的语法格式为: cv2.distanceTransform(img, distanceType, maskSize) 函数**cv2.connectedComponents()**可以用来标注。该函数会将背景标注为0,将其他的对象使用从1开始的正整数标注。该函数的语法格式为: retval, labels = cv2.connectedComponents(img) 函数**cv2.connectedComponents()在标注图像时,会将背景标注为0,将其他的对象用从1开始的正整数标注。在分水岭算法中,标注值0代表未知区域,所以我们要对函数cv2.connectedComponents()**标注的结果进行调整:将标注的结果都加上数值1。为了能够使用分水岭算法,还需要对原始图像内的未知区域进行标注,将已经计算出来的未知区域标注为0即可。这里的关键代码为: 步骤: 函数**cv2.watershed()**可以用来实现分水岭算法。该函数的语法格式为: cv2.watershed(img, markers) img是8位三通道的图像。在对图像使用**cv2.watershed()**函数处理之前,必须先用正数大致勾画出图像中的期望分割区域。每一个分割的区域会被标注为1、2、3等。对于尚未确定的区域,需要将它们标注为0,我们可以将标注区域理解为进行分水岭算法分割的"种子"区域。 markers是32位单通道的标注结果。在markers中,每一个像素要么被设置为初期的种子值,要么被设置为"-1"表示边界。markers可以省略。 函数**cv2.grabCut()**可以用来实现交互式前景提取。该函数的语法格式为: mask, bgdModel, fgdModel = cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount[, mode]) img是8位3通道的图像 mask是8位单通道的掩模图像,用于确定前景区域、背景区域和不确定区域,可以设置为4种形式: rect是包含前景对象的区域,该区域外的部分被认为是确定背景。只有当参数mode的值设置为矩形模式cv2.GC_INIT_WITH_RECT时,参数rect才有意义。使用掩模模式时,将该值设置为None即可。 bgdModel为算法内部使用的数组,只需要创建大小为(1, 65)的numpy.float64数组 fgdModel为算法内部使用的数组,只需要创建大小为(1, 65)的numpy.float64数组 iterCount表示迭代的次数 mode表示迭代模式 GrabCut算法提取图像的前景(不使用掩模) GrabCut算法提取图像的前景(使用标记的掩模) **cv2.VideoCapture()**函数可以用于打开摄像头并初始化。 初始化当前的摄像头 初始化视频文件 可以使用函数**cv2.VideoCapture.isOpened()**检查初始化是否成功。 如果初始化失败,可以使用函数**cv2.VideoCapture.open()**打开摄像头。 函数**cv2.VideoCapture.isOpened()和函数cv2.VideoCapture.open()**也能用于处理视频文件。 若当前有一个VideoCapture类的对象cap,要将其释放,可以使用语句 函数**cv2.VideoCapture.get()**用于获取cv2.VideoCapture类对象的属性。该函数的语法格式为: retval = cv2.VideoCapture.get(propId) 函数**cv2.VideoCapture.set()**用于设置cv2.VideoCapture类对象的属性。该函数的语法格式为: retval = cv2.VideoCapture.set(propId, value) OpenCV中的cv2.VideoWriter类可以将图片序列保存为视频文件,也可以修改视频的各种属性,还可以完成对视频类型的转换。 cv2.VideoWriter(filename, fourcc, fps, frameSize) filename指定输出目标视频的存放路径和文件名 fourcc表示视频编解码格式 fps为帧速率 frameSize为每一帧的长和宽 **cv2.VideoWriter.write(img)**用于写入下一帧视频,若当前有一个VideoWriter类的对象out,要将一个视频帧frame写入,可以使用语句 若当前有一个VideoWriter类的对象out,要将其释放,可以使用语句 img = cv2.line(img, pt1, pt2, color, thickness[, lineType]) img = cv2.rectangle(img, pt1, pt2, color, thickness[, lineType]) img = cv2.circle(img, center, radius, color, thickness[, lineType]) img = cv2.ellipse(img, center, axes, angle, startAngle, endAngle, color, thickness[, lineType]) img = cv2.polylines(img, pts, isClosed, color, thickness[, lineType]) img = cv2.putText(img, text, org, fontFace, fontScale, color) 函数**cv2.createTrackbar()**用来定义滚动条,其语法格式为: cv2.createTrackbar(trackbarName, windowName, value, count, onChange) 滚动条的值可以通过函数**cv2.getTrackbarPos()**获取,其语法格式为: retval = cv2.getTrackbarPos(trackbarname, winname) 用滚动条实现调色板img = cv2.imread('image/lenaNoise.png')
output = cv2.bilateralFilter(img, 25, 100, 100) # 双边滤波
cv2.imshow('original', img)
cv2.imshow('bilateralFilter', output)
2D卷积
kernel = np.ones((9, 9), np.float32) / 81
output = cv2.filter2D(img, -1, kernel)
cv2.imshow('original', img)
cv2.imshow('filter2D', output)
形态学操作
腐蚀
img = cv2.imread('image/erode.bmp')
kernel = np.ones((10, 10), np.uint8)
output = cv2.erode(img, kernel) # 腐蚀
cv2.imshow('original', img)
cv2.imshow('erode', output)
膨胀
img = cv2.imread('image/dilation.bmp')
kernel = np.ones((10, 10), np.uint8)
output = cv2.dilate(img, kernel) # 膨胀
cv2.imshow('original', img)
cv2.imshow('dilate', output)
通用形态学函数
类型
说明
含义
操作
cv2.MORPH_ERODE
腐蚀
腐蚀
erode(img)
cv2.MORPH_DILATE
膨胀
膨胀
dilate(img)
cv2.MORPH_OPEN
开运算
先腐蚀后膨胀
dilate(erode(img))
cv2.MORPH_CLOSE
闭运算
先膨胀后腐蚀
erode(dilate(img))
cv2.MORPH_GRADIENT
形态学梯度运算
膨胀图减腐蚀图
dilate(img)-erode(img)
cv2.MORPH_TOPHAT
顶帽运算
原始图像减开运算所得图像
img-open(img)
cv2.MORPH_BLACKHAT
黑帽运算
闭运算所得图像减原始图像
close(img)-img
cv2.MORPH_HITMISS
击中击不中
前景背景腐蚀运算的交集
intersection(erode(img),erode(img))
开运算
img1 = cv2.imread('image/opening1.bmp')
img2 = cv2.imread('image/opening2.bmp')
kernel = np.ones((10, 10), np.uint8)
output1 = cv2.morphologyEx(img1, cv2.MORPH_OPEN, kernel) # 开运算
output2 = cv2.morphologyEx(img2, cv2.MORPH_OPEN, kernel) # 开运算
cv2.imshow('img1', img1)
cv2.imshow('OPEN1', output1)
cv2.imshow('img2', img2)
cv2.imshow('OPEN2', output2)
闭运算
img1 = cv2.imread('image/closing1.bmp')
img2 = cv2.imread('image/closing2.bmp')
kernel = np.ones((10, 10), np.uint8)
output1 = cv2.morphologyEx(img1, cv2.MORPH_CLOSE, kernel) # 闭运算
output2 = cv2.morphologyEx(img2, cv2.MORPH_CLOSE, kernel, iterations=3) # 闭运算,迭代3次
cv2.imshow('img1', img1)
cv2.imshow('CLOSE1', output1)
cv2.imshow('img2', img2)
cv2.imshow('CLOSE2', output2)
形态学梯度运算
img = cv2.imread('image/gradient.bmp')
kernel = np.ones((5, 5), np.uint8)
output = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) # 形态学梯度运算
cv2.imshow('img', img)
cv2.imshow('GRADIENT', output)
顶帽运算
img1 = cv2.imread('image/tophat.bmp')
img2 = cv2.imread('image/lena.png')
kernel = np.ones((5, 5), np.uint8)
output1 = cv2.morphologyEx(img1, cv2.MORPH_TOPHAT, kernel) # 顶帽运算
output2 = cv2.morphologyEx(img2, cv2.MORPH_TOPHAT, kernel) # 顶帽运算
cv2.imshow('img1', img1)
cv2.imshow('TOPHAT1', output1)
cv2.imshow('img2', img2)
cv2.imshow('TOPHAT2', output2)
黑帽运算
img1 = cv2.imread('image/blackhat.bmp')
img2 = cv2.imread('image/lena.png')
kernel = np.ones((5, 5), np.uint8)
output1 = cv2.morphologyEx(img1, cv2.MORPH_BLACKHAT, kernel) # 黑帽运算
output2 = cv2.morphologyEx(img2, cv2.MORPH_BLACKHAT, kernel) # 黑帽运算
cv2.imshow('img1', img1)
cv2.imshow('BLACKHAT1', output1)
cv2.imshow('img2', img2)
cv2.imshow('BLACKHAT2', output2)
核函数
类型
说明
cv2.MORPH_RECT
矩形结构元素。所有元素值都是1
cv2.MORPH_CROSS
十字形结构元素。对角线元素值为1
cv2.MORPH_ELLIPSE
椭圆形结构元素
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
kernel2 = cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))
kernel3 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
print('kernel1=', kernel1)
print('kernel2=', kernel2)
print('kernel3=', kernel3)
图像梯度
Sobel算子
img = cv2.imread('image/sobel.bmp')
SobelX = cv2.Sobel(img, cv2.CV_64F, 1, 0) # x方向边缘(梯度)
SobelX = cv2.convertScaleAbs(SobelX) # x方向边缘(梯度)
SobelY = cv2.Sobel(img, cv2.CV_64F, 0, 1) # y方向边缘(梯度)
SobelY = cv2.convertScaleAbs(SobelY) # y方向边缘(梯度)
SobelXY = cv2.Sobel(img, cv2.CV_64F, 1, 1) # dx和dy的值均为1
SobelXY = cv2.convertScaleAbs(SobelXY) # dx和dy的值均为1
SobelXYAdd = cv2.addWeighted(SobelX, 0.5, SobelY, 0.5, 0) # x方向和y方向的边缘叠加
cv2.imshow('original', img)
cv2.imshow('SobelX', SobelX)
cv2.imshow('SobelY', SobelY)
cv2.imshow('SobelXY', SobelXY)
cv2.imshow('SobelXYAdd', SobelXYAdd)
Scharr算子
img = cv2.imread('image/scharr.bmp')
ScharrX = cv2.Scharr(img, cv2.CV_64F, 1, 0) # x方向边缘(梯度)
ScharrX = cv2.convertScaleAbs(ScharrX) # x方向边缘(梯度)
ScharrY = cv2.Scharr(img, cv2.CV_64F, 0, 1) # y方向边缘(梯度)
ScharrY = cv2.convertScaleAbs(ScharrY) # y方向边缘(梯度)
ScharrXYAdd = cv2.addWeighted(ScharrX, 0.5, ScharrY, 0.5, 0) # x方向和y方向的边缘叠加
cv2.imshow('original', img)
cv2.imshow('ScharrX', ScharrX)
cv2.imshow('ScharrY', ScharrY)
cv2.imshow('ScharrXYAdd', ScharrXYAdd)
Laplacian算子
img = cv2.imread('image/laplacian.bmp')
Laplacian = cv2.Laplacian(img, cv2.CV_64F)
Laplacian = cv2.convertScaleAbs(Laplacian) # Laplacian算子
cv2.imshow('original', img)
cv2.imshow('ScharrX', Laplacian)
图像金字塔
向下采样
img = cv2.imread('image/lena.png', 0)
output = cv2.pyrDown(img) # 向下采样
cv2.imshow('original', img)
cv2.imshow('pyrDown', output)
print("shape:", output.shape)
向上采样
img = cv2.imread('image/lena.png', 0)
output = cv2.pyrUp(img) # 向上采样
cv2.imshow('original', img)
cv2.imshow('pyrUp', output)
print("shape:", output.shape)
图像轮廓
Canny边缘检测
img = cv2.imread('image/lena.png', 0)
output = cv2.Canny(img, 128, 200) # Canny边缘检测
cv2.imshow('original', img)
cv2.imshow('Canny', output)
查找并绘制轮廓
img = cv2.imread('image/contours.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
image = cv2.drawContours(imgCopy, contours, -1, (0, 0, 255), 5) # 绘制轮廓
cv2.imshow('original', img)
cv2.imshow('drawContours', image)
矩特征
img = cv2.imread('image/moments.bmp')
cv2.imshow('original', img)
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
n = len(contours) # 轮廓的个数
contoursImg = []
for i in range(n):
temp = np.zeros(img.shape, np.uint8)
contoursImg.append(temp)
contoursImg[i] = cv2.drawContours(contoursImg[i], contours, i, (255, 0, 0), 3)
cv2.imshow("contours[" + str(i) + "]", contoursImg[i]) # 显示轮廓
print("轮廓" + str(i) + "的矩:\n", cv2.moments(contours[i])) # 轮廓的矩
print("轮廓" + str(i) + "的面积:%d" % cv2.moments(contours[i])['m00']) # 轮廓的面积
img = cv2.imread('image/moments.bmp')
cv2.imshow('original', img)
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
n = len(contours) # 轮廓的个数
contoursImg = []
for i in range(n):
temp = np.zeros(img.shape, np.uint8)
contoursImg.append(temp)
contoursImg[i] = cv2.drawContours(contoursImg[i], contours, i, (255, 0, 0), 3)
cv2.imshow("contours[" + str(i) + "]", contoursImg[i]) # 显示轮廓
print("轮廓" + str(i) + "的面积:%d" % cv2.contourArea(contours[i])) # 轮廓的面积
img = cv2.imread('image/moments.bmp')
cv2.imshow('original', img)
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
n = len(contours) # 轮廓的个数
contoursImg = []
for i in range(n):
temp = np.zeros(img.shape, np.uint8)
contoursImg.append(temp)
contoursImg[i] = cv2.drawContours(contoursImg[i], contours, i, (255, 0, 0), 3)
cv2.imshow("contours[" + str(i) + "]", contoursImg[i]) # 显示轮廓
print("轮廓" + str(i) + "的长度:%d" % cv2.arcLength(contours[i], True)) # 轮廓的长度
Hu矩
img = cv2.imread('image/cs.bmp', 0)
HuM1 = cv2.HuMoments(cv2.moments(img)).flatten() # 将数组变为一维
print("HuM1:", HuM1)
img1 = cv2.imread('image/cs1.bmp', 0)
img2 = cv2.imread('image/cs2.bmp', 0)
img3 = cv2.imread('image/cc.bmp', 0)
ret0 = cv2.matchShapes(img1, img1, 1, 0) # 相同图像
ret1 = cv2.matchShapes(img1, img2, 1, 0) # 相似图像
ret2 = cv2.matchShapes(img1, img3, 1, 0) # 不相似图像
print("相同图像的matchShapes=", ret0)
print("相似图像的matchShapes=", ret1)
print("不相似图像的matchShapes=", ret2)
相似图像的matchShapes= 0.0001154058519395873
不相似图像的matchShapes= 0.012935752303635195轮廓拟合
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
x, y, w, h = cv2.boundingRect(contours[0]) # 矩形包围框
cv2.rectangle(imgCopy, (x, y), (x + w, y + h), (255, 255, 255), 2) # 绘制矩形框
cv2.imshow('original', img)
cv2.imshow('boundingRect', imgCopy)
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
rect = cv2.minAreaRect(contours[0]) # 最小包围矩形框
points = cv2.boxPoints(rect) # 转换格式
points = np.int0(points) # 取整
cv2.drawContours(imgCopy, [points], 0, (255, 255, 255), 2)
cv2.imshow('original', img)
cv2.imshow('minAreaRect', imgCopy)
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
(x, y), radius = cv2.minEnclosingCircle(contours[0]) # 最小包围圆形
center = (int(x), int(y)) # 取整
radius = int(radius) # 取整
cv2.circle(imgCopy, center, radius, (255, 255, 255), 2)
cv2.imshow('original', img)
cv2.imshow('minEnclosingCircle', imgCopy)
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
ellipse = cv2.fitEllipse(contours[0]) # 最优拟合椭圆
cv2.ellipse(imgCopy, ellipse, (255, 0, 0), 2)
cv2.imshow('original', img)
cv2.imshow('fitEllipse', imgCopy)
值
含义
cv2.DIST_USER
用户定义距离
cv2.DIST_L1
distance = |x1-x2|+|y1-y2|
cv2.DIST_L2
欧式距离,此时与最小二乘法相同
cv2.DIST_C
distance =max(|x1-x2|, |y1-y2|)
cv2.DIST_L12
distance = 2(sqrt(1+x*x/2) - 1))
cv2.DIST_FAIR
distance = c2(|x|/c-log(1+|x|/c)), c = 1.3998
cv2.DIST_WELSCH
distance =c2/2(1-exp(-(x/c)2)), c = 2.9846
cv2.DIST_HUBER
distance =|x|
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
row, col = img.shape[:2]
[vx, vy, x, y] = cv2.fitLine(contours[0], cv2.DIST_L2, 0, 0.01, 0.01) # 最优拟合直线
left_y = int((-x * vy / vx) + y)
right_y = int(((col - x) * vy / vx) + y)
cv2.line(imgCopy, (col - 1, right_y), (0, left_y), (255, 0, 0), 2)
cv2.imshow('original', img)
cv2.imshow('fitLine', imgCopy)
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
area, triangle = cv2.minEnclosingTriangle(contours[0]) # 最小外包三角形
for i in range(3):
cv2.line(imgCopy, tuple(triangle[i][0]), tuple(triangle[(i + 1) % 3][0]), (255, 0, 0), 2)
cv2.imshow('original', img)
cv2.imshow('minEnclosingTriangle', imgCopy)
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
epsilon = 0.01 * cv2.arcLength(contours[0], True) # 设置精度
approxCurve = cv2.approxPolyDP(contours[0], epsilon, True) # 逼近多边形曲线
cv2.drawContours(imgCopy, [approxCurve], 0, (255, 0, 0), 2)
cv2.imshow('original', img)
cv2.imshow('minEnclosingTriangle', imgCopy)
凸包
img = cv2.imread('image/hand.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
cnt = contours[0]
hull = cv2.convexHull(cnt, returnPoints=False) # 凸包
defects = cv2.convexityDefects(cnt, hull) # 凸缺陷
for i in range(defects.shape[0]):
s, e, f, d = defects[i, 0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
cv2.line(imgCopy, start, end, (255, 0, 0), 2)
cv2.circle(imgCopy, far, 5, (255, 0, 0), -1)
cv2.imshow('original', img)
cv2.imshow('convexityDefects', imgCopy)
img = cv2.imread('image/hand.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
hull = cv2.convexHull(contours[0]) # 凸包
cv2.polylines(imgCopy, [hull], True, (255, 0, 0), 2)
result = cv2.isContourConvex(hull) # 判断轮廓是否是凸形的
print(result)
cv2.imshow('original', img)
cv2.imshow('isContourConvex', imgCopy)
img = cv2.imread('image/cs1.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
hull = cv2.convexHull(contours[0]) # 凸包
cv2.polylines(imgCopy, [hull], True, (255, 0, 0), 2)
distA = cv2.pointPolygonTest(hull, (300, 150), True) # 点到轮廓的距离
distB = cv2.pointPolygonTest(hull, (300, 250), True) # 点到轮廓的距离
distC = cv2.pointPolygonTest(hull, (423, 112), True) # 点到轮廓的距离
cv2.putText(imgCopy, "A", (300, 150), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 3)
cv2.putText(imgCopy, "B", (300, 250), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 3)
cv2.putText(imgCopy, "C", (423, 112), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 3)
print("distA:", distA)
print("distB:", distB)
print("distC:", distC)
cv2.imshow('original', img)
cv2.imshow('pointPolygonTest', imgCopy)
利用形状场景算法比较轮廓
img1 = cv2.imread('image/cs1.bmp', 0)
img2 = cv2.imread('image/cs2.bmp', 0)
img3 = cv2.imread('image/hand.bmp', 0)
ret, binary1 = cv2.threshold(img1, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
ret, binary2 = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
ret, binary3 = cv2.threshold(img3, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours1, hierarchy = cv2.findContours(binary1, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
contours2, hierarchy = cv2.findContours(binary2, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
contours3, hierarchy = cv2.findContours(binary3, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
cnt1 = contours1[0]
cnt2 = contours2[0]
cnt3 = contours3[0]
result = cv2.createShapeContextDistanceExtractor() # 计算形状场景距离
d1 = result.computeDistance(cnt1, cnt1)
d2 = result.computeDistance(cnt1, cnt2)
d3 = result.computeDistance(cnt1, cnt3)
print("相同图像的距离=", d1)
print("相似图像的距离=", d2)
print("不相似图像的距离=", d3)
img1 = cv2.imread('image/cs1.bmp', 0)
img2 = cv2.imread('image/cs3.bmp', 0)
img3 = cv2.imread('image/hand.bmp', 0)
ret, binary1 = cv2.threshold(img1, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
ret, binary2 = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
ret, binary3 = cv2.threshold(img3, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours1, hierarchy = cv2.findContours(binary1, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
contours2, hierarchy = cv2.findContours(binary2, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
contours3, hierarchy = cv2.findContours(binary3, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
cnt1 = contours1[0]
cnt2 = contours2[0]
cnt3 = contours3[0]
result = cv2.createHausdorffDistanceExtractor() # 计算Hausdorff距离
d1 = result.computeDistance(cnt1, cnt1)
d2 = result.computeDistance(cnt1, cnt2)
d3 = result.computeDistance(cnt1, cnt3)
print("相同图像的距离=", d1)
print("相似图像的距离=", d2)
print("不相似图像的距离=", d3)
轮廓的特征值
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
x, y, w, h = cv2.boundingRect(contours[0])
aspectRatio = float(w) / h # 宽高比
print("宽高比=", aspectRatio)
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
x, y, w, h = cv2.boundingRect(contours[0])
rectArea = w * h
cntArea = cv2.contourArea(contours[0])
Extend = float(cntArea) / rectArea # Extend
print("Extend=", Extend)
img = cv2.imread('image/hand.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
cntArea = cv2.contourArea(contours[0])
hull = cv2.convexHull(contours[0])
hullArea = cv2.contourArea(hull)
Solidity = float(cntArea) / hullArea # Solidity
print("Solidity=", Solidity)
img = cv2.imread('image/cc.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
cntArea = cv2.contourArea(contours[0])
equivalentDiameter = np.sqrt(4 * cntArea / np.pi) # 等效直径
print("等效直径=", equivalentDiameter)
img = cv2.imread('image/boat.jpg')
cv2.imshow("original", img)
plt.hist(img.ravel(), 256) # 使用Numpy绘制直方图
plt.show()
img = cv2.imread('image/ct.png')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
cnt = contours[2]
mask = np.zeros(gray.shape, np.uint8)
mask = cv2.drawContours(mask, [cnt], -1, (255, 0, 0), -1)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(gray, mask) # 最大值和最小值及它们的位置
print("min_val:", min_val)
print("max_val:", max_val)
print("min_loc:", min_loc)
print("max_loc:", max_loc)
maskImg = np.zeros(img.shape, np.uint8)
maskImg = cv2.drawContours(maskImg, [cnt], -1, (255, 255, 255), -1)
loc = cv2.bitwise_and(img, maskImg)
cv2.imshow("original", img)
cv2.imshow("mask", loc)
img = cv2.imread('image/ct.png')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
cnt = contours[2]
mask = np.zeros(gray.shape, np.uint8)
mask = cv2.drawContours(mask, [cnt], -1, (255, 0, 0), -1)
meanVal = cv2.mean(img, mask) # 平均颜色及平均灰度
print("meanVal:", meanVal)
img = cv2.imread('image/cs1.bmp')
imgCopy = img.copy() # 复制图像
gray = cv2.cvtColor(imgCopy, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 将图像二值化
contours, hierarchy = cv2.findContours(binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 查找轮廓
mask = np.zeros(gray.shape, np.uint8)
cnt = contours[0]
cv2.drawContours(mask, [cnt], 0, 255, -1)
leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0]) # 最左端
rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0]) # 最右端
topmost = tuple(cnt[cnt[:, :, 1].argmin()][0]) # 最上端
bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0]) # 最下端
print("leftmost=", leftmost)
print("rightmost=", rightmost)
print("topmost=", topmost)
print("bottommost=", bottommost)
cv2.putText(imgCopy, 'A', leftmost, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.putText(imgCopy, 'B', rightmost, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.putText(imgCopy, 'C', topmost, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.putText(imgCopy, 'D', bottommost, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.imshow("result", imgCopy)
直方图处理
绘制直方图
img = cv2.imread('image/boat.jpg')
cv2.imshow("original", img)
plt.hist(img.ravel(), 256) # 使用Numpy绘制直方图
plt.show()
img = cv2.imread('image/girl.bmp')
cv2.imshow("original", img)
histB = cv2.calcHist([img], [0], None, [256], [0, 255]) # 得到直方图数据
histG = cv2.calcHist([img], [1], None, [256], [0, 255]) # 得到直方图数据
histR = cv2.calcHist([img], [2], None, [256], [0, 255]) # 得到直方图数据
plt.plot(histB, color='b', label='B')
plt.plot(histG, color='g', label='G')
plt.plot(histR, color='r', label='R')
plt.show()
直方图均衡化
img = cv2.imread('image/equ.bmp', 0)
output = cv2.equalizeHist(img) # 直方图均衡化
cv2.imshow("original", img)
cv2.imshow("equalizeHist", output)
plt.figure("原始图像直方图")
plt.hist(img.ravel(), 256)
plt.figure("均衡化图像直方图")
plt.hist(output.ravel(), 256)
plt.show()
傅里叶变换
用Numpy实现傅里叶变换
img = cv2.imread('image/lena.png', 0)
fft = np.fft.fft2(img) # 傅里叶变换
fftShift = np.fft.fftshift(fft)
output = 20 * np.log(np.abs(fftShift))
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122)
plt.imshow(output, cmap='gray')
plt.title('result'), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png', 0)
fft = np.fft.fft2(img) # 傅里叶变换
fftShift = np.fft.fftshift(fft)
ifftShift = np.fft.ifftshift(fftShift)
ifft = np.fft.ifft2(ifftShift) # 傅里叶逆变换
output = np.abs(ifft)
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122)
plt.imshow(output, cmap='gray')
plt.title('output'), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png', 0)
fft = np.fft.fft2(img) # 傅里叶变换
fftShift = np.fft.fftshift(fft)
row, col = img.shape
r, c = int(row / 2), int(col / 2) # 图像中心点
fftShift[r - 30:r + 30, c - 30:c + 30] = 0 # 去除低频信息
ifftShift = np.fft.ifftshift(fftShift)
ifft = np.fft.ifft2(ifftShift) # 傅里叶逆变换
output = np.abs(ifft)
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122)
plt.imshow(output, cmap='gray')
plt.title('output'), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png', 0)
fft = np.fft.fft2(img) # 傅里叶变换
fftShift = np.fft.fftshift(fft)
row, col = img.shape
r, c = int(row / 2), int(col / 2) # 图像中心点
mask = np.zeros((row, col), np.uint8)
mask[r - 30:r + 30, c - 30:c + 30] = 1
fftShift = fftShift * mask
ifftShift = np.fft.ifftshift(fftShift)
ifft = np.fft.ifft2(ifftShift) # 傅里叶逆变换
output = np.abs(ifft)
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122)
plt.imshow(output, cmap='gray')
plt.title('output'), plt.axis('off')
plt.show()
用OpenCV实现傅里叶变换
img = cv2.imread('image/lena.png', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT) # 傅里叶变换
dftShift = np.fft.fftshift(dft)
output = 20 * np.log(cv2.magnitude(dftShift[:, :, 0], dftShift[:, :, 1]))
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122), plt.imshow(output, cmap='gray')
plt.title('result'), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT) # 傅里叶变换
dftShift = np.fft.fftshift(dft)
idftShift = np.fft.ifftshift(dftShift)
idft = cv2.idft(idftShift) # 傅里叶逆变换
output = cv2.magnitude(idft[:, :, 0], idft[:, :, 1])
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122), plt.imshow(output, cmap='gray')
plt.title('result'), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT) # 傅里叶变换
dftShift = np.fft.fftshift(dft)
row, col = img.shape
r, c = int(row / 2), int(col / 2) # 图像中心点
dftShift[r - 30:r + 30, c - 30:c + 30] = 0 # 去除低频信息
idftShift = np.fft.ifftshift(dftShift)
idft = cv2.idft(idftShift) # 傅里叶逆变换
output = cv2.magnitude(idft[:, :, 0], idft[:, :, 1])
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122), plt.imshow(output, cmap='gray')
plt.title('result'), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT) # 傅里叶变换
dftShift = np.fft.fftshift(dft)
row, col = img.shape
r, c = int(row / 2), int(col / 2) # 图像中心点
mask = np.zeros((row, col, 2), np.uint8)
mask[r - 30:r + 30, c - 30:c + 30] = 1
dftShift = dftShift * mask
idftShift = np.fft.ifftshift(dftShift)
idft = cv2.idft(idftShift) # 傅里叶逆变换
output = cv2.magnitude(idft[:, :, 0], idft[:, :, 1])
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('original'), plt.axis('off')
plt.subplot(122), plt.imshow(output, cmap='gray')
plt.title('result'), plt.axis('off')
plt.show()
模板匹配
模板匹配基础
参数
对应数值
说明
CV_TM_SQDIFF
0
以方差为依据进行匹配;若完全匹配,则结果为0;若不匹配,则会得到一个很大的值
CV_TM_SQDIFF_NORMED
1
归一化平方差匹配
CV_TM_CCORR
2
该方法将模板图像与输入图像相乘;数值越大表明匹配程度越好
CV_TM_CCORR_NORMED
3
归一化相关匹配
CV_TM_CCOEFF
4
1表示完美匹配,-1表示最差匹配,0表示没有任何相关性
CV_TM_CCOEFF_NORMED
5
归一化相关系数匹配
img = cv2.imread('image/lena.png', 0)
template = cv2.imread('image/lenaEye.bmp', 0)
templateH, templateW = template.shape # 模板的高、宽
match = cv2.matchTemplate(img, template, cv2.TM_SQDIFF) # 模板匹配(cv2.TM_SQDIFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(match) # 最大值和最小值及它们的位置
topLeft = min_loc # 左上角
bottomRight = (topLeft[0] + templateW, topLeft[1] + templateH) # 右下角
cv2.rectangle(img, topLeft, bottomRight, (255, 0, 0), 2)
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('result'), plt.axis('off')
plt.subplot(122), plt.imshow(match, cmap='gray')
plt.title('Match Result'), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png', 0)
template = cv2.imread('image/lenaEye.bmp', 0)
templateH, templateW = template.shape # 模板的高、宽
match = cv2.matchTemplate(img, template, cv2.TM_CCOEFF) # 模板匹配(cv2.TM_CCOEFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(match) # 最大值和最小值及它们的位置
topLeft = max_loc # 左上角
bottomRight = (topLeft[0] + templateW, topLeft[1] + templateH) # 右下角
cv2.rectangle(img, topLeft, bottomRight, (255, 0, 0), 2)
plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('result'), plt.axis('off')
plt.subplot(122), plt.imshow(match, cmap='gray')
plt.title('Match Result'), plt.axis('off')
plt.show()
多模板匹配
for i in 匹配位置集合:
标记匹配位置
for i in zip(*模板匹配索引集合):
标记处理
img = cv2.imread('image/lena4.bmp', 0)
template = cv2.imread('image/lena4Temp.bmp', 0)
templateH, templateW = template.shape # 模板的高、宽
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED) # 模板匹配(cv2.TM_CCOEFF_NORMED)
threshold = 0.9 # 阈值
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(img, pt, (pt[0] + templateW, pt[1] + templateH), (255, 0, 0), 2)
plt.imshow(img, cmap='gray')
plt.xticks([]), plt.yticks([])
plt.show()
霍夫变换
霍夫变换原理
霍夫直线变换
img = cv2.imread('image/computer.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edge = cv2.Canny(gray, 50, 150, apertureSize=3) # Canny边缘检测
rgbImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
rgbCopy = rgbImg.copy()
lines = cv2.HoughLines(edge, 1, np.pi / 180, 140) # 霍夫直线变换
for line in lines:
rho, theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * a)
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * a)
cv2.line(rgbImg, (x1, y1), (x2, y2), (0, 0, 255), 2)
plt.subplot(121)
plt.imshow(rgbCopy), plt.axis('off')
plt.subplot(122)
plt.imshow(rgbImg), plt.axis('off')
plt.show()
概率霍夫变换
img = cv2.imread('image/computer.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edge = cv2.Canny(gray, 50, 150, apertureSize=3) # Canny边缘检测
rgbImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
rgbCopy = rgbImg.copy()
lines = cv2.HoughLinesP(edge, 1, np.pi / 180, 160, minLineLength=100, maxLineGap=10) # 概率霍夫变换
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(rgbImg, (x1, y1), (x2, y2), (255, 255, 255), 2)
plt.subplot(121)
plt.imshow(rgbCopy), plt.axis('off')
plt.subplot(122)
plt.imshow(rgbImg), plt.axis('off')
plt.show()
霍夫圆变换
img = cv2.imread('image/chess.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
rgbImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
rgbCopy = rgbImg.copy()
gray = cv2.medianBlur(gray, 5)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 300, param1=50, param2=30, minRadius=100, maxRadius=200) # 霍夫圆变换
circles = np.uint16(np.around(circles))
for i in circles[0, :]:
cv2.circle(rgbImg, (i[0], i[1]), i[2], (255, 0, 0), 12)
cv2.circle(rgbImg, (i[0], i[1]), 2, (255, 0, 0), 12)
plt.subplot(121)
plt.imshow(rgbCopy), plt.axis('off')
plt.subplot(122)
plt.imshow(rgbImg), plt.axis('off')
plt.show()
图像分割与提取
距离变换函数
值
含义
cv2.DIST_USER
用户定义距离
cv2.DIST_L1
distance = |x1-x2|+|y1-y2|
cv2.DIST_L2
欧式距离,此时与最小二乘法相同
cv2.DIST_C
distance =max(|x1-x2|, |y1-y2|)
cv2.DIST_L12
distance = 2(sqrt(1+x*x/2) - 1))
cv2.DIST_FAIR
distance = c2(|x|/c-log(1+|x|/c)), c = 1.3998
cv2.DIST_WELSCH
distance =c2/2(1-exp(-(x/c)2)), c = 2.9846
cv2.DIST_HUBER
distance =|x| img = cv2.imread('image/water_coins.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
imgCopy = img.copy()
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 开运算
distanceTransform = cv2.distanceTransform(opening, cv2.DIST_L2, 5) # 距离变换
ret, fore = cv2.threshold(distanceTransform, 0.7 * distanceTransform.max(), 255, 0)
plt.subplot(131)
plt.imshow(imgCopy), plt.axis('off')
plt.subplot(132)
plt.imshow(distanceTransform), plt.axis('off')
plt.subplot(133)
plt.imshow(fore), plt.axis('off')
plt.show()
标注图像
img = cv2.imread('image/water_coins.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
imgCopy = img.copy()
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 开运算
sure_bg = cv2.dilate(opening, kernel, iterations=3) # 确定背景
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, fore = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
fore = np.uint8(fore) # 前景图像的中心点图像
ret, markers = cv2.connectedComponents(fore) # 对中心点图像进行标注
plt.subplot(131)
plt.imshow(imgCopy), plt.axis('off')
plt.subplot(132)
plt.imshow(fore), plt.axis('off')
plt.subplot(133)
plt.imshow(markers), plt.axis('off')
plt.show()
ret, markers = cv2.connectedComponents(fore)
markers = markers + 1
markers[未知区域] = 0
img = cv2.imread('image/water_coins.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 开运算
sure_bg = cv2.dilate(opening, kernel, iterations=3) # 确定背景
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, fore = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
fore = np.uint8(fore)
ret, markers1 = cv2.connectedComponents(fore)
foreAdv = fore.copy()
unknown = cv2.subtract(sure_bg, foreAdv) # 未知区域
ret, markers2 = cv2.connectedComponents(foreAdv)
markers2 = markers2 + 1 # 修正标注结果
markers2[unknown == 255] = 0
plt.subplot(121)
plt.imshow(markers1), plt.axis('off')
plt.subplot(122)
plt.imshow(markers2), plt.axis('off')
plt.show()
分水岭算法实现图像分割与提取
img = cv2.imread('image/water_coins.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
imgCopy = img.copy()
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) # 开运算
sure_bg = cv2.dilate(opening, kernel, iterations=3) # 确定背景
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg) # 确定前景
unknown = cv2.subtract(sure_bg, sure_fg) # 未知区域
ret, markers = cv2.connectedComponents(sure_fg)
markers = markers + 1 # 修正标注结果
markers[unknown == 255] = 0
markers = cv2.watershed(img, markers) # 分水岭算法
img[markers == -1] = [0, 255, 0]
plt.subplot(121)
plt.imshow(imgCopy), plt.axis('off')
plt.subplot(122)
plt.imshow(img), plt.axis('off')
plt.show()
交互式前景提取
img = cv2.imread('image/lena.png')
rgbImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = np.zeros(img.shape[:2], np.uint8) # 将掩模设置为0
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = (50, 50, 400, 400)
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
ogc = img * mask2[:, :, np.newaxis]
ogc = cv2.cvtColor(ogc, cv2.COLOR_BGR2RGB)
plt.subplot(121)
plt.imshow(rgbImg), plt.axis('off')
plt.subplot(122)
plt.imshow(ogc), plt.axis('off')
plt.show()
img = cv2.imread('image/lena.png')
rgbImg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mask = np.zeros(img.shape[:2], np.uint8)
bgd = np.zeros((1, 65), np.float64)
fgd = np.zeros((1, 65), np.float64)
rect = (50, 50, 400, 500)
cv2.grabCut(img, mask, rect, bgd, fgd, 5, cv2.GC_INIT_WITH_RECT)
mask2 = cv2.imread('image/lenaMask.png', 0)
mask2Show = cv2.imread('image/lenaMask.png')
m2rgb = cv2.cvtColor(mask2Show, cv2.COLOR_BGR2RGB)
mask[mask2 == 0] = 0 # 将黑色值映射到掩模上
mask[mask2 == 255] = 1 # 将白色值映射到掩模上
mask, bgd, fgd = cv2.grabCut(img, mask, None, bgd, fgd, 5, cv2.GC_INIT_WITH_MASK)
mask = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
ogc = img * mask[:, :, np.newaxis]
ogc = cv2.cvtColor(ogc, cv2.COLOR_BGR2RGB)
plt.subplot(121)
plt.imshow(m2rgb), plt.axis('off')
plt.subplot(122)
plt.imshow(ogc), plt.axis('off')
plt.show()
视频处理
VideoCapture类
cap = cv2.VideoCapture(0)
cap = cv2.VideoCapture("test.avi")
retval = cv2.VideoCapture.isOpened()
retval = cv2.VideoCapture.open(0)
retval, image = cv2.VideoCapture.read()
cap.release()
值
propId
含义
cv2.CV_CAP_PROP_FRAME_WIDTH
3
帧的宽度
cv2.CV_CAP_PROP_FRAME_HEIGHT
4
帧的高度
cv2.CV_CAP_PROP_FPS
5
帧速
cap = cv2.VideoCapture(0)
while True:
success, img = cap.read()
cv2.imshow("Video", img)
if cv2.waitKey(1) & 0XFF == 27:
break
VideoWriter类
out.write(frame)
out.release()
cap = cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*'XVID') # 保存视频的编码
out = cv2.VideoWriter('output.avi', fourcc, 20, (640, 480))
while cap.isOpened():
success, img = cap.read()
if success:
out.write(img)
cv2.imshow('frame', img)
if cv2.waitKey(1) == 27:
break
else:
break
cap.release()
out.release()
cv2.destroyAllWindows()
绘画及交互
绘画基础
img = cv2.line(img, (50, 50), (300, 300), (255, 0, 0), 2)
img = cv2.rectangle(img, (50, 50), (300, 300), (255, 0, 0), 2)
img = cv2.circle(img, (200, 200), 50, (255, 0, 0), 2)
img = cv2.ellipse(img, (200, 200), (200, 100), 30, 0, 360, (255, 0, 0), 2)
pts = np.array([[200, 50], [300, 200], [200, 350], [100, 200]], np.int32)
pts = pts.reshape((-1, 1, 2))
img = cv2.polylines(img, [pts], True, (255, 0, 0), 2)
img = cv2.putText(img, "OpenCV", (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 3, (255, 0, 0), 2)
滚动条
def changeColor(x):
r = cv2.getTrackbarPos('R', 'image')
g = cv2.getTrackbarPos('G', 'image')
b = cv2.getTrackbarPos('B', 'image')
img[:] = [b, g, r]
img = np.zeros((100, 500, 3), np.uint8)
cv2.namedWindow('image')
cv2.createTrackbar('R', 'image', 0, 255, changeColor)
cv2.createTrackbar('G', 'image', 0, 255, changeColor)
cv2.createTrackbar('B', 'image', 0, 255, changeColor)
while True:
cv2.imshow('image', img)
if cv2.waitKey(0) & 0XFF == 27:
cv2.destroyAllWindows()