OpenCV学习笔记-亚像素级精确度的角点-Python版
在学习亚像素级精确度的角点的过程中,我在书中的代码里遇到了几个想不通的地方,在网上百度也都是C++版本的讲解,通过多番查找,总算是弄明白了一点,特此记录一下,以便日后需要时可以回来看看。同时也希望能对学习Python的小伙伴有帮助。
转载请注明出处,原创不易----zyh 2018-6-3
一、亚像素级精确度的角点
如果我们进行图像处理的目的不是用于识别特征点而是进行几何测量,则通常需要更高的精度,而cvGoodFeatureToTrack()只能提供简单的像素坐标值,但有时候我们会需要实际坐标值而不是整数坐标值,例如,我们想要确定图形中一个尖锐的峰值点的位置,但是峰值点的位置一般都不会位于一个像素点的正中心,使得确定其位置比较困难。要解决这个问题,需要先用一个曲线(例如一条抛物线)拟合图像的值,再用数学的方法计算位于像素之间的峰值点的位置。
亚像素级角点的位置在摄像机标定、跟踪并重建摄像机的轨迹或者重建被跟踪目标的三维结构时就是一个基本的测量值。通过cvGoodFeaturesToTrack()函数可以求得角点坐标值,接下来就要讨论如何将求得的坐标值精确到亚像素级精度。方法就是向量的点积理论:一个向量和其正交的向量的点积为0,角点我们之前有说过了,就是两个边缘的相交,可以满足这样的情况。如下图:
当要求的点P位于一个区域的内部时,点p边缘是平缓的,它的梯度值为0,此时向量 的与p的梯度点积为0,;
当点p位于区域的边缘的时候,向量 与区域平行,而p的梯度值则与边缘垂直,此时向量
的与p的梯度点积为0。
这两种情况下,向量 与P点的梯度都是正交的。先假设起始角点q在实际亚像素级角点p附近,则我们可以在要求的p点的周围取到很多p点的梯度和相关向量
令其点积为0,然后就可以通过求解方程组,方程组的解就是角点q的亚像素精度的位置,也就是精确角点的位置。 (学习OpenCV里有讲到)
二、求取亚像素精度的原理
三、函数及代码
求取角点的中心点,它旨在提取近乎白色背景中的黑暗区域。背景区域被跳过。
connectedComponentsWithStats(image, labels=None, stats=None, centroids=None, connectivity=None, ltype=None)
ret: 返回的标签的数量
labels: 输入图像的大小的矩阵,其中每个元素的值都等于其标签
Stats is a matrix of the stats that the function calculates. It has a length equal to the number of labels and a width equal to the number of stats.
stats: 函数计算的统计信息的矩阵。它的行等于标签的数量,列数等于统计到的信息数量(5个)。
每个标签的统计输出,包括背景标签。
输出格式为[cv2.CC_STAT_LEFT, CV.CC_STAT_TOP, CV.CC_STAT_WIDTH, CV.CC_STAT_HEIGHT, CV.CC_STAT_AREA]
CV.CC_STAT_LEFT: 最左边的(x)坐标,它是水平方向上边界框的包含开始
CV.CC_STAT_TOP: 最上边的(y)坐标,它是垂直方向上边界框的包含开始
CV.CC_STAT_TOP: 边界框的水平大小
CV.CC_STAT_TOP: 边界框的垂直大小
CV.CC_STAT_TOP: 连接组件的总面积(以像素为单位)
可以通过stats[label, column]访问
centroids: 具有每个质心的x和y位置的矩阵。此矩阵中的行对应于标签号。
ret: 返回的标签的数量
labels: 输入图像的大小的矩阵,其中每个元素的值都等于其标签
Stats is a matrix of the stats that the function calculates. It has a length equal to the number of labels and a width equal to the number of stats.
stats: 函数计算的统计信息的矩阵。它的行等于标签的数量,列数等于统计到的信息数量(5个)。
每个标签的统计输出,包括背景标签。
输出格式为[cv2.CC_STAT_LEFT, CV.CC_STAT_TOP, CV.CC_STAT_WIDTH, CV.CC_STAT_HEIGHT, CV.CC_STAT_AREA]
CV.CC_STAT_LEFT: 最左边的(x)坐标,它是水平方向上边界框的包含开始
CV.CC_STAT_TOP: 最上边的(y)坐标,它是垂直方向上边界框的包含开始
CV.CC_STAT_TOP: 边界框的水平大小
CV.CC_STAT_TOP: 边界框的垂直大小
CV.CC_STAT_TOP: 连接组件的总面积(以像素为单位)
可以通过stats[label, column]访问
centroids: 具有每个质心的x和y位置的矩阵。此矩阵中的行对应于标签号。
connectivity: 必须为4或者8
ltype: 默认为cv2.CV_32S
求取亚像素精度的角点位置
cornerSubPix(image, corners, winSize, zeroZone, criteria)
image: 8位单通道的灰度图像。
corners: 为整数值的像素位置
winSize: 指定了等式产生的窗口尺寸。
zeroZone: 定义了一个禁区(与win相似,但通常比win小),这个区域在方程组以及自相关矩阵中不被考虑。设置(-1,-1)表示不需要这个区域
criteria: 终止条件。可以是最大迭代次数cv.TERM_CRITERIA_MAX_ITER,或者设定的精度cv.TERM_CRITERIA_EPS类型,或者两者的组合。终止条件的设置在极大程度上影响最终得到的亚像素的精度。例如知道0.1,则求得的亚像素级精度为像素的十分之一。
image: 8位单通道的灰度图像。
corners: 为整数值的像素位置
winSize: 指定了等式产生的窗口尺寸。
zeroZone: 定义了一个禁区(与win相似,但通常比win小),这个区域在方程组以及自相关矩阵中不被考虑。设置(-1,-1)表示不需要这个区域
criteria: 终止条件。可以是最大迭代次数cv.TERM_CRITERIA_MAX_ITER,或者设定的精度cv.TERM_CRITERIA_EPS类型,或者两者的组合。终止条件的设置在极大程度上影响最终得到的亚像素的精度。例如知道0.1,则求得的亚像素级精度为像素的十分之一。
具体代码:
import cv2 as cv import numpy as np from matplotlib import pyplot as plt img = cv.imread('img/chessboard.png') img = cv.resize(img, (int(img.shape[1]*0.2), int(img.shape[0]*0.2))) gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) gray = np.float32(gray) dst = cv.cornerHarris(gray, 2, 3, 0.04) dst = cv.dilate(dst, None) ret, dst = cv.threshold(dst, 0.01*dst.max(), 255, cv.THRESH_BINARY) dst = np.uint8(dst) cv.imwrite('img/thresh3.png', dst) ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst) ''' connectedComponentsWithStats(image, labels=None, stats=None, centroids=None, connectivity=None, ltype=None) ret: 返回的标签的数量 labels: 输入图像的大小的矩阵,其中每个元素的值都等于其标签 Stats is a matrix of the stats that the function calculates. It has a length equal to the number of labels and a width equal to the number of stats. stats: 函数计算的统计信息的矩阵。它的行等于标签的数量,列数等于统计到的信息数量(5个)。 每个标签的统计输出,包括背景标签。 输出格式为[cv2.CC_STAT_LEFT, CV.CC_STAT_TOP, CV.CC_STAT_WIDTH, CV.CC_STAT_HEIGHT, CV.CC_STAT_AREA] CV.CC_STAT_LEFT: 最左边的(x)坐标,它是水平方向上边界框的包含开始 CV.CC_STAT_TOP: 最上边的(y)坐标,它是垂直方向上边界框的包含开始 CV.CC_STAT_TOP: 边界框的水平大小 CV.CC_STAT_TOP: 边界框的垂直大小 CV.CC_STAT_TOP: 连接组件的总面积(以像素为单位) 可以通过stats[label, column]访问 centroids: 具有每个质心的x和y位置的矩阵。此矩阵中的行对应于标签号。 ''' print(ret) # print(labels) # print(labels[2]) print(stats.shape) # print(centroids) #定义停止和细化角点的标准 criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 100, 0.001) corners = cv.cornerSubPix(gray, np.float32(centroids), (5, 5), (-1, -1), criteria) ''' cornerSubPix(image, corners, winSize, zeroZone, criteria) image: 8位单通道的灰度图像。 corners: 为整数值的像素位置 winSize: 指定了等式产生的窗口尺寸。 zeroZone: 定义了一个禁区(与win相似,但通常比win小),这个区域在方程组以及自相关矩阵中不被考虑。设置(-1,-1)表示不需要这个区域 criteria: 终止条件。可以是最大迭代次数cv.TERM_CRITERIA_MAX_ITER,或者设定的精度cv.TERM_CRITERIA_EPS类型,或者两者的组合。 ''' print(corners) res = np.hstack((centroids, corners)) res = np.int0(res) print(res) img[res[:, 1], res[:, 0]] = [0, 0, 255] img[res[:, 3], res[:, 2]] = [0, 255, 0] cv.imwrite('img/subpixel2.png', img) cv.waitKey(0) cv.destroyAllWindows()
效果
可以看到,在达到像素级别时候,二值化的图像中,角点并不是一个矩形。
红色像素是Harris角点,绿色像素是修正后的像素。
参考文章:
https://stackoverflow.com/questions/35854197/how-to-use-opencvs-connected-components-with-stats-in-python#
https://www.cnblogs.com/riddick/p/8476456.html