cv(五)
1.图像二值化

# opencv中计算图像二值化阈值方法 # 1.OTSU类内方差最小,类外方差最大化,基于直方图的统计 # 2.Triangle三角二值化,基于直方图的统计,在只有一个波峰的时候效果好,有多个波峰效果很不好 # 3.自动与手动 # 4.自适应阈值 import cv2 as cv import numpy as np import matplotlib.pyplot as plt # 二值图像就是将灰度图转化成黑白图,没有灰,在一个值之前为黑,之后为白 # 有全局和局部两种 # 在使用全局阈值时,我们就是随便给了一个数来做阈值,那我们怎么知道我们选取的这个数的好坏呢?答案就是不停的尝试。 # 如果是一副双峰图像(简 单来说双峰图像是指图像直方图中存在两个峰)呢? # 我们岂不是应该在两个峰之间的峰谷选一个值作为阈值?这就是 Otsu 二值化要做的。 # 简单来说就是对 一副双峰图像自动根据其直方图计算出一个阈值。 # (对于非双峰图像,这种方法 得到的结果可能会不理想)。 def threshold_demo(image): gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 这个函数的第一个参数就是原图像,原图像应该是灰度图。 # 第二个参数就是用来对像素值进行分类的阈值。 # 第三个参数就是当像素值高于(有时是小于)阈值时应该被赋予的新的像素值 # 第四个参数来决定阈值方法,见threshold_simple() # ret, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY) 127是自己指定的阈值 ret, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY | cv.THRESH_OTSU) # 第四个参数如果指定了阈值计算方法,127就不会起作用 print("threshold value: %s"%ret) # 大于阈值是白色,小于阈值是黑色 cv.imshow("threshold_demo", binary) # 全局二值化 def threshold_simple(image): img = cv.cvtColor(image, cv.COLOR_BGR2GRAY) ret, thresh1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY) # 比较常用 ret, thresh2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV) # 二值化取反 ret, thresh3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC) # 截断,把大于127的阈值都变成127,小于的保留 ret, thresh4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO) # 把小于127的都变为0,大于127的保留 ret, thresh5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV) titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV'] images = [img, thresh1, thresh2, thresh3, thresh4, thresh5] for i in range(6): plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray') # 将图像按2x3铺开 plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() # 在前面的部分我们使用是全局阈值,整幅图像采用同一个数作为阈值。 # 当时这种方法并不适应与所有情况,尤其是当同一幅图像上的不同部分的具有不同亮度时。 # 这种情况下我们需要采用自适应阈值。此时的阈值是根据图像上的 每一个小区域计算与其对应的阈值。 # 因此在同一幅图像上的不同区域采用的是不同的阈值,从而使我们能在亮度不同的情况下得到更好的结果。 # 这种方法需要我们指定三个参数,返回值只有一个 # _MEAN_C:阈值取自相邻区域的平均值,_GAUSSIAN_C:阈值取值相邻区域 的加权和,权重为一个高斯窗口。 # Block Size - 邻域大小(用来计算阈值的区域大小)。 # C - 这就是是一个常数,阈值就等于的平均值或者加权平均值减去这个常数。 # 局部二值化 def threshold_adaptive(image): img = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 中值滤波 img = cv.medianBlur(img, 5) ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY) # 11 为 Block size必须是基数, 2 为 C 值是常量,如果原值比均值大2就设为白色,否则黑色 # ADAPTIVE_THRESH_MEAN_C 把图像分成一个一个小的方格,求每个方格中的均值,大于均值用白色,小于均值用黑色 # ADAPTIVE_THRESH_GAUSSIAN_C在求均值的时候会带上高斯权重,最中心的权重会更大,会考虑到像素空间因素更适合做二值化 th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2) th3 = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2) titles = ['Original Image', 'Global Threshold (v = 127)', 'Adaptive Mean Threshold', 'Adaptive Gaussian Threshold'] images = [img, th1, th2, th3] for i in range(4): plt.subplot(2, 2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() # 自己求均值 def threshold_custom(image): gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) h, w = gray.shape[:2] m = np.reshape(gray, [1, w*h]) mean = m.sum() / (w*h) # 求出整个灰度图像的平均值 print("mean:", mean) # 均值作为阈值 ret, binary = cv.threshold(gray, mean, 255, cv.THRESH_BINARY) cv.imshow("threshold_custom", binary) # 超大图像二值化方法 # 先分块然后局部二值化 # 将大图片拆分成小图片后再用自适应局部阈值比较好 def big_image_demo(image): print(image.shape) cw = 200 ch = 200 h, w = image.shape[:2] gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) cv.imshow("big_image_demo_gray", gray) # 将一张图片每隔ch * cw分成一份 for row in range(0, h, ch): for col in range(0, w, cw): roi = gray[row:row+ch, col:col+cw] # 分块 dst = cv.adaptiveThreshold(roi, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 127, 2) gray[row:row + ch, col:col + cw] = dst print(np.std(dst), np.mean(dst)) # 标准差或者均值为255,则说明该部分是空白图像 cv.imwrite("../images/result_big_image.png", gray) def main(): img = cv.imread("../images/02.jpg") # threshold_demo(img) # threshold_simple(img) # threshold_adaptive(img) # threshold_custom(img) src = cv.imread("../images/big_image.jpg") big_image_demo(src) cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口 cv.destroyAllWindows() # 关闭所有窗口 if __name__ == '__main__': main()
2.图像金字塔

1 # 图像金字塔的原理:reduce = 高斯模糊+下采样 2 # 高斯金字塔 3 # 拉普拉斯金字塔:通过高斯金字塔可以得到拉普拉斯金字塔 4 5 import cv2 as cv 6 import numpy as np 7 8 9 # 图像金字塔和拉普拉斯金字塔(L1 = g1 - expand(g2)):reduce:高斯模糊+降采样,expand:扩大+卷积 10 # PyrDown降采样,PyrUp还原 11 def pyramid_demo(image): 12 level = 4 # 定义金字塔层数 13 temp = image.copy() 14 pyramid_images = [] 15 16 for i in range(level): 17 dst = cv.pyrDown(temp) 18 pyramid_images.append(dst) 19 cv.imshow("pyramid_down_"+str(i+1), dst) 20 temp = dst.copy() 21 return pyramid_images 22 23 24 # 拉普拉斯金字塔 25 def laplace_demo(image): # 注意:图片必须是满足2^n这种分辨率 26 pyramid_images = pyramid_demo(image) 27 level = len(pyramid_images) 28 + 29 for i in range(level-1, -1, -1): 30 if i-1 < 0: 31 expand = cv.pyrUp(pyramid_images[i], dstsize=image.shape[:2]) 32 lpls = cv.subtract(image, expand) 33 cv.imshow("laplace_demo"+str(i), lpls) 34 else: 35 expand = cv.pyrUp(pyramid_images[i], dstsize=pyramid_images[i-1].shape[:2]) 36 lpls = cv.subtract(pyramid_images[i-1], expand) 37 cv.imshow("laplace_demo"+str(i), lpls) 38 39 40 if __name__ == '__main__': 41 42 src = cv.imread(r"./images/lena.jpg") # 读入图片放进src中 43 cv.imshow("demo", src) # 将src图片放入该创建的窗口中 44 # pyramid_demo(src) 45 laplace_demo(src) 46 cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口 47 cv.destroyAllWindows() # 关闭所有窗口
3.图像梯度

1 # 图像的梯度:对一幅图像的每两个像素进行相减然后把赋给原来相似,然后把这个差值形成的图称为原图的梯度图像 2 # 一阶导数与Soble算子:找到变化最大的值 3 # 二阶导数与拉普拉斯算子:拉普拉斯算子的每个值加起来值为0 4 5 6 import cv2 as cv 7 import numpy as np 8 9 10 # 图像梯度(由x,y方向上的偏导数和偏移构成),有一阶导数(sobel算子)和二阶导数(Laplace算子) 11 # 用于求解图像边缘,一阶的极大值,二阶的零点 12 # 一阶偏导在图像中为一阶差分,再变成算子(即权值)与图像像素值乘积相加,二阶同理 13 def sobel_demo(image): 14 grad_x = cv.Sobel(image, cv.CV_32F, 1, 0) # CV_32F是图像的深度,采用Scharr边缘更突出,Scharr是Sobel算子的增强版本对噪声比较敏感 15 grad_y = cv.Sobel(image, cv.CV_32F, 0, 1) 16 17 gradx = cv.convertScaleAbs(grad_x) # 由于算完的图像有正有负,所以对其取绝对值,可以可以把其变为8位的单通道0-255图像 18 grady = cv.convertScaleAbs(grad_y) 19 20 # 计算两个图像的权值和,dst = src1*alpha + src2*beta + gamma 21 gradxy = cv.addWeighted(gradx, 0.5, grady, 0.5, 0) 22 23 cv.imshow("gradx", gradx) # x方向梯度 24 cv.imshow("grady", grady) # y方向梯度 25 cv.imshow("gradient", gradxy) 26 27 28 def laplace_demo(image): # 二阶导数,边缘更细 29 dst = cv.Laplacian(image, cv.CV_32F) 30 lpls = cv.convertScaleAbs(dst) 31 cv.imshow("laplace_demo", lpls) 32 33 # 自定义拉普拉斯算子 34 def custom_laplace(image): 35 # 以下算子与上面的Laplace_demo()是一样的,增强采用np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) 36 kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) 37 dst = cv.filter2D(image, cv.CV_32F, kernel=kernel) 38 lpls = cv.convertScaleAbs(dst) 39 cv.imshow("custom_laplace", lpls) 40 41 42 def main(): 43 src = cv.imread("../images/lena.jpg") 44 cv.imshow("lena", src) 45 # sobel_demo(src) 46 laplace_demo(src) 47 custom_laplace(src) 48 cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口 49 cv.destroyAllWindows() # 关闭所有窗口 50 51 52 if __name__ == '__main__': 53 main()
4.图像边缘提取

1 # 图像边缘提取 2 # 边缘检测算法Canny的五步: 3 # 1.把彩色图像通过高斯模糊去掉噪声(canny对噪声敏感性强,所以第一步要做去噪) 4 # 2.转为灰度图像 5 # 3.计算梯度 6 # 4.非最大信号抑制 7 # 5.高低阈值输出二值图像 8 9 import cv2 as cv 10 import numpy as np 11 12 # 非极大值抑制: 13 # 算法使用一个3×3邻域作用在幅值阵列M[i,j]的所有点上; 14 # 每一个点上,邻域的中心像素M[i,j]与沿着梯度线的两个元素进行比较, 15 # 其中梯度线是由邻域的中心点处的扇区值ζ[i,j]给出。 16 # 如果在邻域中心点处的幅值M[i,j]不比梯度线方向上的两个相邻点幅值大,则M[i,j]赋值为零,否则维持原值; 17 # 此过程可以把M[i,j]宽屋脊带细化成只有一个像素点宽,即保留屋脊的高度值。 18 19 # 高低阈值连接 20 # T1,T2为阈值,凡是高于T2的都保留,凡是低于T1的都丢弃 21 # 从高于T2的像素出发,凡是大于T1而且相互连接的都保留。最终得到一个输出二值图像 22 # 推荐高低阈值比值为T2:T1 = 3:1/2:1,其中T2高阈值,T1低阈值 23 24 25 def edge_demo(image): 26 blurred = cv.GaussianBlur(image, (3, 3), 0) 27 gray = cv.cvtColor(blurred, cv.COLOR_BGR2GRAY) 28 29 grad_x = cv.Sobel(gray, cv.CV_16SC1, 1, 0) # canny要求像素值不能为浮点数,所以用CV_16SC1 30 grad_y = cv.Sobel(gray, cv.CV_16SC1, 0, 1) 31 32 # edge_output = cv.Canny(grad_x, grad_y, 50, 150) # 高阈值是低阈值的3倍 33 edge_output = cv.Canny(gray, 50, 150) # 也支持直接对灰度图像提取 34 cv.imshow("gray", gray) 35 cv.imshow("Canny demo", edge_output) 36 37 dst = cv.bitwise_and(image, image, mask=edge_output) 38 cv.imshow('Color Edge', dst) 39 40 41 def main(): 42 src = cv.imread("../images/Crystal.jpg") 43 cv.imshow("demo",src) 44 45 edge_demo(src) 46 cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口 47 cv.destroyAllWindows() # 关闭所有窗口 48 49 50 if __name__ == '__main__': 51 main()
5.霍夫直线检测和霍夫圆检测

1 # 直线检测是基于霍夫变换来完成的 2 # 霍夫直线变换原理就是平面坐标和极坐标之间的转换 3 4 5 import cv2 as cv 6 import numpy as np 7 8 9 def line_detection(image): 10 gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) # 转灰度 11 edges = cv.Canny(gray, 50, 150, apertureSize=3) # 检测边缘 12 13 # cv2.HoughLines()返回值就是(ρ,θ)。ρ 的单位是像素,θ 的单位是弧度。 14 # 这个函数的第一个参数是一个二值化图像,所以在进行霍夫变换之前要首先进行二值化,或者进行 Canny 边缘检测。 15 # 第二和第三个值分别代表ρ半径的步长和θ角度。第四个参数是边缘提取的低值,高于该低值时才被认为是一条直线, 16 # 也可以把它看成能 检测到的直线的最短长度(以像素点为单位)。 17 18 lines = cv.HoughLines(edges, 1, np.pi / 180, 200) 19 20 for rho, theta in lines[0]: 21 a = np.cos(theta) 22 b = np.sin(theta) 23 x0 = a * rho 24 y0 = b * rho 25 # 直线的四个点 26 x1 = int(x0 + 1000 * (-b)) 27 y1 = int(y0 + 1000 * (a)) 28 x2 = int(x0 - 1000 * (-b)) 29 y2 = int(y0 - 1000 * (a)) 30 cv.line(image, (x1, y1), (x2, y2), (0, 0, 255), 2) 31 32 cv.imshow("line_detection", image) 33 34 35 # 直接得到线段 36 def line_detection_possible_demo(image): 37 gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY) 38 edges = cv.Canny(gray, 50, 150, apertureSize=3) 39 minLineLength = 100 # 最小线长度像素 40 maxLineGap = 10 # 如果线中间断开,断开像素小于等于该值还会将其认为是一天线 41 lines = cv.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength, maxLineGap) # 返回是直线的概率 42 for x1, y1, x2, y2 in lines[0]: 43 cv.line(image, (x1, y1), (x2, y2), (0, 255, 0), 2) 44 cv.imshow('hough_lines', image) 45 46 # 霍夫圆检测原理 47 # Hough Circle 在xy坐标系中一点对应Hough坐标系中的一个圆,xy坐标系中圆上各个点对应Hough坐标系各个圆, 48 # 相加的一点,即对应xy坐标系中圆心 49 # 现实考量:Hough圆对噪声比较敏感,所以做hough圆之前要中值滤波, 50 # 基于效率考虑,OpenCV中实现的霍夫变换圆检测是基于图像梯度的实现,分为两步: 51 # 1. 检测边缘,发现可能的圆心候选圆心开始计算最佳半径大小 52 # 2. 基于第一步的基础上,从 53 def detection_circles_demo(image): 54 dst = cv.pyrMeanShiftFiltering(image, 10, 100) # 均值迁移去噪,sp,sr为空间域核与像素范围域核半径 55 gray = cv.cvtColor(dst, cv.COLOR_BGR2GRAY) 56 57 """ 58 . @param image 8-bit, single-channel, grayscale input image. 59 . @param circles Output vector of found circles. Each vector is encoded as 3 or 4 element 60 . floating-point vector \f$(x, y, radius)\f$ or \f$(x, y, radius, votes)\f$ . 61 . @param method Detection method, see #HoughModes. Currently, the only implemented method is #HOUGH_GRADIENT 62 . @param dp Inverse ratio of the accumulator resolution to the image resolution. For example, if 63 . dp=1 , the accumulator has the same resolution as the input image. If dp=2 , the accumulator has 64 . half as big width and height. 65 累加器图像的分辨率。这个参数允许创建一个比输入图像分辨率低的累加器。 66 (这样做是因为有理由认为图像中存在的圆会自然降低到与图像宽高相同数量的范畴)。 67 如果dp设置为1,则分辨率是相同的;如果设置为更大的值(比如2),累加器的分辨率受此影响会变小(此情况下为一半)。 68 dp的值不能比1小。 69 . @param minDist Minimum distance between the centers of the detected circles. If the parameter is 70 . too small, multiple neighbor circles may be falsely detected in addition to a true one. If it is 71 . too large, some circles may be missed. 72 该参数是让算法能明显区分的两个不同圆之间的最小距离。 73 . @param param1 First method-specific parameter. In case of #HOUGH_GRADIENT , it is the higher 74 . threshold of the two passed to the Canny edge detector (the lower one is twice smaller). 75 用于Canny的边缘阀值上限,下限被置为上限的一半。 76 . @param param2 Second method-specific parameter. In case of #HOUGH_GRADIENT , it is the 77 . accumulator threshold for the circle centers at the detection stage. The smaller it is, the more 78 . false circles may be detected. Circles, corresponding to the larger accumulator values, will be 79 . returned first. 80 累加器的阀值。 81 . @param minRadius Minimum circle radius. 82 最小圆半径 83 . @param maxRadius Maximum circle radius. If <= 0, uses the maximum image dimension. If < 0, returns 84 . centers without finding the radius. 85 最大圆半径。 86 """ 87 circles = cv.HoughCircles(gray, cv.HOUGH_GRADIENT, 1, 20, param1=40, param2=30, minRadius=0, maxRadius=0) 88 circles = np.uint16(np.around(circles)) # 转换为整数 89 print(circles.shape) 90 for i in circles[0, :]: # draw the outer circle 91 cv.circle(image, (i[0], i[1]), i[2], (0, 255, 0), 2) # draw the center of the circle 92 cv.circle(image, (i[0], i[1]), 2, (0, 0, 255), 3) 93 cv.imshow('detected circles', image) 94 95 96 def main(): 97 src = cv.imread("../images/sudoku.png") 98 cv.imshow("demo", src) 99 100 line_detection(src) 101 # line_detection_possible_demo(src) 102 # img = cv.imread("../images/circle.png") 103 # detection_circles_demo(img) 104 cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口 105 cv.destroyAllWindows() # 关闭所有窗口 106 107 108 if __name__ == '__main__': 109 main()