亚像素
在数字照片中,最小的可见单位是“像素”。尽管我们无法直接看到像素之间的信息,但有些应用场景需要比相机所提供的信息更加精确。例如,在从图像中重建三维(3D)对象时就需要进行精确的测量。为了提高角点识别的准确性,已经开发了一些数学策略。
观察这个放大的正方形图像,你将看到每个像素的详细信息。如果你尝试找到角点,可能会发现角点并不是由单一像素构成的。实际上,要让角点与像素完美对齐几乎是不可能的。
传统的角点检测方法,比如Shi-Tomasi和Harris方法,可能只能给出一个近似的像素坐标,例如(53, 786)。然而,科学家和研究人员通常需要更精细的结果,比如(53.786, 786.110)。
虽然无法真正达到(53.786, 786.110)这样的精确度,但通过这种方式,能够确认角点就位于这个近似位置。
为什么要如此细致地去确定小数点后的数值呢?原因在于角点是图像中极其关键的部分。在多种应用场景中,额外的精度是非常必要的,例如:
- 立体视觉
- 3D重建
- 相机校准
- 物体跟踪
因此,为了提升准确性和处理速度,科研人员已经投入了大量的工作。
OpenCV 中的亚像素角点
在OpenCV中,亚像素角点的检测是通过内置的cornerSubPix
函数实现的,该函数能够提高角点定位的精度,达到亚像素级别。这种精度的提升对于多种图像处理任务来说是非常关键。
cornerSubPix
函数使用点积方法对由角点Harris检测器或其他角点检测算法初步识别的角点进行迭代细化。这个过程会持续进行,直到满足预设的终止条件。
cornerSubPix
函数的基本使用
void cv::cornerSubPix(
InputArray image, InputOutputArray corners,
Size winSize, Size zeroZone, TermCriteria criteria
)
cornerSubPix
函数的基本参数和它们的用途:
image
:输入图像。corners
:正如名字所示,这个数组在过程开始时存储了大致的角点。作为函数的结果,这个数组会用修正后的角点位置进行修改。winSize
:这个函数依赖于一些方程来执行其工作。它还使用了几个像素围绕角点来获得这种效果。如果你使用 winSize,你可以控制从特定窗口中提取多少像素。zeroZone
:这个函数也使用同样的方式解决许多方程。当涉及到“解决”问题时,使用矩阵。为了找到这个问题的解决方案,这个矩阵会被求逆。但有些矩阵不能被求逆。为了避免这种情况,忽略了围绕角点的一些像素。那个区域就是 zeroZone。criteria
:停止迭代角点细化过程的条件。换句话说,角点角度的细化过程要么在满足一组条件后结束(使用 CV_TERMCRIT_ITER 或 CV_TERMCRIT_EPS 或两者)。
OpenCV - TermCriteria
在OpenCV中,TermCriteria
是一种特殊的结构,用于指定算法的终止条件。这在许多需要迭代计算的算法中非常有用,如优化问题、梯度下降、角点检测的亚像素精度细化等。使用TermCriteria
可以确保算法不会无限期地运行下去,而是在满足特定条件时停止,这些条件包括:
迭代次数(CV_TERMCRIT_ITER):算法运行的迭代次数达到用户指定的迭代次数时终止。
误差精度(CV_TERMCRIT_EPS):当连续迭代的解之间的差异小于用户指定的误差阈值时终止。
TermCriteria
结构可以通过多种方式组合这两种条件,以适应不同的应用场景。以下是如何使用TermCriteria
的一些示例:
构造TermCriteria
cv::TermCriteria criteria;
设置迭代次数和误差精度
// 只根据迭代次数终止
criteria = cv::TermCriteria(cv::TermCriteria::MAX_ITER, 100, 0.0);
// 只根据误差精度终止
criteria = cv::TermCriteria(cv::TermCriteria::EPS, 30, 0.01);
// 同时根据迭代次数和误差精度终止
criteria = cv::TermCriteria(cv::TermCriteria::MAX_ITER + cv::TermCriteria::EPS, 100, 0.01);
在上述代码中:
cv::TermCriteria::MAX_ITER
表示使用迭代次数作为终止条件。cv::TermCriteria::EPS
表示使用误差精度作为终止条件。- 第三个参数
100
和30
分别是迭代次数和最大迭代次数。 - 第四个参数
0.01
是误差精度阈值。
使用TermCriteria
的算法示例
以cornerSubPix
函数为例,可以在调用此函数时传入TermCriteria
对象,以控制亚像素角点检测的终止条件:
cv::Mat src_gray; // 假设这是已经加载的灰度图像
std::vector<cv::Point2f> corners; // 假设这是初步检测到的角点
cv::Size winSize(5, 5); // 搜索窗口大小
cv::Size zeroZone(-1, -1); // 通常设置为-1
cv::TermCriteria criteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 40, 0.001);
std::vector<cv::Point2f> refined_corners;
cv::cornerSubPix(src_gray, corners, winSize, zeroZone, criteria);
在上面的代码中,cornerSubPix
函数将在满足以下任一条件时停止:迭代次数达到40次,或者连续迭代的角点位置变化小于0.001。这种灵活的终止条件设置对于确保算法的效率和准确性至关重要。
C++ 代码计算亚像素角点
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
cv::Mat src, src_gray;
int maxCorners = 10;
const int MAXTRACKBAR = 25;
const std::string source_window = "Source Image";
void trackFeatures( int, void* );
int main( int argc, char** argv )
{
if(argc < 2 ) {
std::cout << "Usage: " << argv[0] << " <Input image>\n";
return -1;
}
src = cv::imread(argv[1]) ;
if( src.empty() )
{
std::cout << "Could not open or find the image!\n\n" ;
std::cout << "Usage: " << argv[0] << " <Input image>\n";
return -1;
}
cv::cvtColor( src, src_gray, cv::COLOR_BGR2GRAY );
cv::namedWindow( source_window );
cv::createTrackbar( "Max corners:", source_window, &maxCorners, MAXTRACKBAR, trackFeatures );
trackFeatures( 0, 0 );
cv::waitKey();
return 0;
}
void trackFeatures( int, void* )
{
maxCorners = MAX(maxCorners, 1);
std::vector<cv::Point2f> corners;
double qualityLevel = 0.01;
double minDistance = 10;
int blockSize = 3, gradientSize = 3;
bool useHarrisDetector = true;
double k = 0.04;
cv::Mat copy = src.clone();
cv::goodFeaturesToTrack( src_gray,
corners,
maxCorners,
qualityLevel,
minDistance,
cv::Mat(),
blockSize,
gradientSize,
useHarrisDetector,
k );
std::cout << "** Number of corners detected: " << corners.size() << "\n";
int radius = 8;
for( size_t i = 0; i < corners.size(); i++ )
{
std::cout << " -- Original Corner Detected [" << i << "] (" << corners[i].x << "," << corners[i].y << ")\n" ;
cv::circle( copy, corners[i], radius, cv::Scalar(0,255,0), cv::FILLED );
}
cv::Size winSize = cv::Size( 5, 5 );
cv::Size zeroZone = cv::Size( -1, -1 );
cv::TermCriteria criteria = cv::TermCriteria( cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 40, 0.001 );
std::vector<cv::Point2f> refinedCorners(corners);
cv::cornerSubPix( src_gray, refinedCorners, winSize, zeroZone, criteria );
for( size_t i = 0; i < refinedCorners.size(); i++ )
{
std::cout << " -- Refined Corner [" << i << "] (" << refinedCorners[i].x << "," << refinedCorners[i].y << ")\n" ;
cv::circle( copy, refinedCorners[i], radius, cv::Scalar(0,0,255), cv::FILLED );
}
cv::namedWindow( source_window.c_str() );
cv::imshow( source_window, copy );
}
代码解释
首先使用 cv::imread() 读取输入图像,并使用 cv::cvtColor() 将其转换为灰度。
然后使用 goodFeaturesToTrack() 函数在图像中查找角点。在这个函数中,使用了角点 Harris 检测算法来检测图像中的角点。图像角点的坐标存储在 corners 变量中。
在 corner 变量中的这些坐标中,使用 cv::cornerSubPix() 函数在亚像素级别上找到角点。