C++ OpenCV硬币的模板匹配
我正在进行一个项目,将自动计算从输入图像硬币的价值。到目前为止,我已经使用边缘检测和霍夫变换的一些预处理来分割硬币 我的问题是如何从这里开始?我需要做一些模板匹配分割图像的基础上,一些以前存储的特征。我该怎么做呢 我也读过一些叫做的东西,我觉得这是我应该使用的东西。但我不太确定如何使用它 我关注的研究文章如下:C++ OpenCV硬币的模板匹配,c++,opencv,feature-detection,feature-extraction,knn,C++,Opencv,Feature Detection,Feature Extraction,Knn,我正在进行一个项目,将自动计算从输入图像硬币的价值。到目前为止,我已经使用边缘检测和霍夫变换的一些预处理来分割硬币 我的问题是如何从这里开始?我需要做一些模板匹配分割图像的基础上,一些以前存储的特征。我该怎么做呢 我也读过一些叫做的东西,我觉得这是我应该使用的东西。但我不太确定如何使用它 我关注的研究文章如下: (1)使用霍夫变换算法查找硬币边缘。 (2) 确定硬币的原点。我不知道你会怎么做。 (3) 您可以使用KNN算法中的k来比较硬币的直径或直径。不要忘记设置偏差值。您可以尝试设置硬币
- (1)使用
霍夫变换算法查找硬币边缘。
(2) 确定硬币的原点。我不知道你会怎么做。
(3) 您可以使用KNN算法中的k
来比较硬币的直径或直径。不要忘记设置偏差值。您可以尝试设置硬币图像的训练集,并生成SIFT/SURF等描述符。(编辑:
使用这些数据,您可以设置kNN分类器,使用硬币值作为训练标签
一旦对分割的硬币图像执行kNN分类,分类结果将产生硬币值。如果正确检测到所有硬币,最好使用大小(径向)和RGB功能来识别其价值。连接这些功能不是一个好主意,因为它们的数量不相等(大小是一个数字,RGB功能的数量远远大于一个)。我建议您为此使用两个分类器。一个用于大小,另一个用于RGB功能
- 您必须将所有硬币分类为例如3(取决于类型
你可以用一个简单的1NN来实现
分类器(只需计算测试硬币的半径并将其分类为
最近的(径向)
- 然后你应该有一些不同大小的模板,并使用模板匹配来识别其价值。(所有模板和检测到的硬币都应该调整到特定的大小。例如,模板的(100100))
匹配您可以使用函数。我认为CV\u TM\u CCOEFF方法可能是最好的方法,但您可以测试所有方法
为了得到一个好的结果。(注意,您不需要在图像上搜索硬币,因为您之前在您的
问题。您只需使用此函数获取一个数字作为两幅图像之间的相似性/差异,并将测试硬币分类为相似性最大或差异最小的类别)
EDIT1:您应该在每个类的模板中进行所有旋转,以补偿测试币的旋转
EDIT2:如果所有硬币大小不同,第一步就足够了。否则,您应该将类似大小的硬币修补到一个类别,并使用第二步(RGB功能)对测试硬币进行分类。进行模式匹配的一种方法是使用cv::matchTemplate
它将输入图像和较小的图像作为模板。它将模板与重叠图像区域进行比较,计算模板与重叠区域的相似性。有几种计算比较的方法。
这种方法不直接支持缩放或方向不变性,但可以通过将候选对象缩放到参考大小并对多个旋转模板进行测试来克服这一点
该技术的一个详细示例显示了检测50摄氏度硬币的压力和位置。相同的程序可应用于其他硬币。
将建立两个程序。一个是从50c硬币的大图像模板创建模板。另一个是将这些模板以及带有硬币的图像作为输入,并将输出标记50c硬币的图像
模板制造商
在这里,我们加载将用于构建模板的图像。
将其分段以创建遮罩。
确定所述遮罩的质量中心。
我们重新缩放并复制面具和硬币,这样他们就可以看到一个固定大小的正方形,正方形的边缘与面具和硬币的圆周接触。也就是说,正方形的侧面与缩放后的面具或硬币图像的直径具有相同的像素长度。
最后,我们保存硬币的缩放和居中图像,并保存以固定角度增量旋转的进一步副本
cv::Mat loadImage(const char* name)
{
cv::Mat image;
image = cv::imread(name);
if ( image.data==NULL || image.channels()!=3 )
{
std::cout << name << " could not be read or is not correct." << std::endl;
exit(1);
}
return image;
}
createMask
对模板进行分割。它对每个BGR通道进行二值化,对这三个二值化图像进行AND运算,并执行闭合形态学操作以生成模板。
三个调试行使用计算出的掩码作为复制操作的掩码,将原始图像复制到黑色图像中。这有助于为阈值选择正确的值
在这里,我们可以看到由createMask
locate
计算掩模的质心及其半径。以{x,y,radius}的形式返回单行网格中的这3个值。
它使用cv::moments
计算多边形或光栅化形状三阶以下的所有力矩。在我们的例子中,光栅化形状。我们对所有这些力矩都不感兴趣。但其中三个力矩在这里很有用。M00是遮罩的面积。质心可以从M00、m10和m01计算出来
void centerAndScale(const cv::Mat& image, const cv::Mat& mask,
const cv::Mat& characteristics,
cv::Mat& imageCS, cv::Mat& maskCS)
{
float radius = characteristics.at<float>(0,2);
float xCenter = characteristics.at<float>(0,0);
float yCenter = characteristics.at<float>(0,1);
int diameter = round(radius*2);
int xOrg = round(xCenter-radius);
int yOrg = round(yCenter-radius);
cv::Rect roiOrg = cv::Rect( xOrg, yOrg, diameter, diameter );
cv::Mat roiImg = image(roiOrg);
cv::Mat roiMask = mask(roiOrg);
cv::Mat centered = cv::Mat::zeros( diameter, diameter, CV_8UC3);
roiImg.copyTo( centered, roiMask);
cv::imwrite( "centered.bmp", centered); // debug
imageCS.create( TEMPLATE_SIZE, TEMPLATE_SIZE, CV_8UC3);
cv::resize( centered, imageCS, cv::Size(TEMPLATE_SIZE,TEMPLATE_SIZE), 0, 0 );
cv::imwrite( "scaled.bmp", imageCS); // debug
roiMask.copyTo(centered);
cv::resize( centered, maskCS, cv::Size(TEMPLATE_SIZE,TEMPLATE_SIZE), 0, 0 );
}
saveRotatedTemplates
保存以前计算的模板。
但它保存了它的多个副本,每个副本旋转一个角度,在angle\u STEP
中定义。其目的是提供方向不变性。我们定义的stepAngle越低,我们得到的方向不变性越好,但这也意味着更高的计算成本
您可以下载整个模板制作程序。
W
#define THRESHOLD_BLUE 130
#define THRESHOLD_TYPE_BLUE cv::THRESH_BINARY_INV
#define THRESHOLD_GREEN 230
#define THRESHOLD_TYPE_GREEN cv::THRESH_BINARY_INV
#define THRESHOLD_RED 140
#define THRESHOLD_TYPE_RED cv::THRESH_BINARY
#define CLOSE_ITERATIONS 5
cv::Mat createMask(const cv::Mat& image)
{
cv::Mat channels[3];
cv::split( image, channels);
cv::Mat mask[3];
cv::threshold( channels[0], mask[0], THRESHOLD_BLUE , 255, THRESHOLD_TYPE_BLUE );
cv::threshold( channels[1], mask[1], THRESHOLD_GREEN, 255, THRESHOLD_TYPE_GREEN );
cv::threshold( channels[2], mask[2], THRESHOLD_RED , 255, THRESHOLD_TYPE_RED );
cv::Mat compositeMask;
cv::bitwise_and( mask[0], mask[1], compositeMask);
cv::bitwise_and( compositeMask, mask[2], compositeMask);
cv::morphologyEx(compositeMask, compositeMask, cv::MORPH_CLOSE,
cv::Mat(), cv::Point(-1, -1), CLOSE_ITERATIONS );
/// Next three lines only for debugging, may be removed
cv::Mat filtered;
image.copyTo( filtered, compositeMask );
cv::imwrite( "filtered.jpg", filtered);
return compositeMask;
}
cv::Mat locate( const cv::Mat& mask )
{
// Compute center and radius.
cv::Moments moments = cv::moments( mask, true);
float area = moments.m00;
float radius = sqrt( area/M_PI );
float xCentroid = moments.m10/moments.m00;
float yCentroid = moments.m01/moments.m00;
float m[1][3] = {{ xCentroid, yCentroid, radius}};
return cv::Mat(1, 3, CV_32F, m);
}
void centerAndScale(const cv::Mat& image, const cv::Mat& mask,
const cv::Mat& characteristics,
cv::Mat& imageCS, cv::Mat& maskCS)
{
float radius = characteristics.at<float>(0,2);
float xCenter = characteristics.at<float>(0,0);
float yCenter = characteristics.at<float>(0,1);
int diameter = round(radius*2);
int xOrg = round(xCenter-radius);
int yOrg = round(yCenter-radius);
cv::Rect roiOrg = cv::Rect( xOrg, yOrg, diameter, diameter );
cv::Mat roiImg = image(roiOrg);
cv::Mat roiMask = mask(roiOrg);
cv::Mat centered = cv::Mat::zeros( diameter, diameter, CV_8UC3);
roiImg.copyTo( centered, roiMask);
cv::imwrite( "centered.bmp", centered); // debug
imageCS.create( TEMPLATE_SIZE, TEMPLATE_SIZE, CV_8UC3);
cv::resize( centered, imageCS, cv::Size(TEMPLATE_SIZE,TEMPLATE_SIZE), 0, 0 );
cv::imwrite( "scaled.bmp", imageCS); // debug
roiMask.copyTo(centered);
cv::resize( centered, maskCS, cv::Size(TEMPLATE_SIZE,TEMPLATE_SIZE), 0, 0 );
}
void saveRotatedTemplates( const cv::Mat& image, const cv::Mat& mask, int stepAngle )
{
char name[1000];
cv::Mat rotated( TEMPLATE_SIZE, TEMPLATE_SIZE, CV_8UC3 );
for ( int angle=0; angle<360; angle+=stepAngle )
{
cv::Point2f center( TEMPLATE_SIZE/2, TEMPLATE_SIZE/2);
cv::Mat r = cv::getRotationMatrix2D(center, angle, 1.0);
cv::warpAffine(image, rotated, r, cv::Size(TEMPLATE_SIZE, TEMPLATE_SIZE));
sprintf( name, "template-%03d.bmp", angle);
cv::imwrite( name, rotated );
cv::warpAffine(mask, rotated, r, cv::Size(TEMPLATE_SIZE, TEMPLATE_SIZE));
sprintf( name, "templateMask-%03d.bmp", angle);
cv::imwrite( name, rotated );
}
}
#define INPUT_IMAGE "coins.jpg"
#define LABELED_IMAGE "coins_with50cLabeled.bmp"
#define LABEL "50c"
#define MATCH_THRESHOLD 0.065
#define ANGLE_STEP 30
int main()
{
vector<cv::Mat> templates;
loadTemplates( templates, ANGLE_STEP );
cv::Mat image = loadImage( INPUT_IMAGE );
cv::Mat mask = createMask( image );
vector<Candidate> candidates;
getCandidates( image, mask, candidates );
saveCandidates( candidates ); // debug
matchCandidates( templates, candidates );
for (int n = 0; n < candidates.size( ); ++n)
std::cout << candidates[n].score << std::endl;
cv::Mat labeledImg = labelCoins( image, candidates, MATCH_THRESHOLD, false, LABEL );
cv::imwrite( LABELED_IMAGE, labeledImg );
return 0;
}
void loadTemplates(vector<cv::Mat>& templates, int angleStep)
{
templates.clear( );
for (int angle = 0; angle < 360; angle += angleStep)
{
char name[1000];
sprintf( name, "template-%03d.bmp", angle );
cv::Mat templateImg = cv::imread( name );
if (templateImg.data == NULL)
{
std::cout << "Could not read " << name << std::endl;
exit( 1 );
}
templates.push_back( templateImg );
}
}
typedef struct Candidate
{
cv::Mat image;
float x;
float y;
float radius;
float score;
} Candidate;
void getCandidates(const cv::Mat& image, const cv::Mat& mask,
vector<Candidate>& candidates)
{
vector<vector<cv::Point> > contours;
vector<cv::Vec4i> hierarchy;
/// Find contours
cv::Mat maskCopy;
mask.copyTo( maskCopy );
cv::findContours( maskCopy, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point( 0, 0 ) );
cv::Mat maskCS;
cv::Mat imageCS;
cv::Scalar white = cv::Scalar( 255 );
for (int nContour = 0; nContour < contours.size( ); ++nContour)
{
/// Draw contour
cv::Mat drawing = cv::Mat::zeros( mask.size( ), CV_8UC1 );
cv::drawContours( drawing, contours, nContour, white, -1, 8, hierarchy, 0, cv::Point( ) );
// Compute center and radius and area.
// Discard small areas.
cv::Moments moments = cv::moments( drawing, true );
float area = moments.m00;
if (area < CANDIDATES_MIN_AREA)
continue;
Candidate candidate;
candidate.radius = sqrt( area / M_PI );
candidate.x = moments.m10 / moments.m00;
candidate.y = moments.m01 / moments.m00;
float m[1][3] = {
{ candidate.x, candidate.y, candidate.radius}
};
cv::Mat characteristics( 1, 3, CV_32F, m );
centerAndScale( image, drawing, characteristics, imageCS, maskCS );
imageCS.copyTo( candidate.image );
candidates.push_back( candidate );
}
}
void saveCandidates(const vector<Candidate>& candidates)
{
for (int n = 0; n < candidates.size( ); ++n)
{
char name[1000];
sprintf( name, "Candidate-%03d.bmp", n );
cv::imwrite( name, candidates[n].image );
}
}
void matchCandidates(const vector<cv::Mat>& templates,
vector<Candidate>& candidates)
{
for (auto it = candidates.begin( ); it != candidates.end( ); ++it)
matchCandidate( templates, *it );
}
void matchCandidate(const vector<cv::Mat>& templates, Candidate& candidate)
{
/// For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the higher the better
candidate.score;
if (MATCH_METHOD == CV_TM_SQDIFF || MATCH_METHOD == CV_TM_SQDIFF_NORMED)
candidate.score = FLT_MAX;
else
candidate.score = 0;
for (auto it = templates.begin( ); it != templates.end( ); ++it)
{
float score = singleTemplateMatch( *it, candidate.image );
if (MATCH_METHOD == CV_TM_SQDIFF || MATCH_METHOD == CV_TM_SQDIFF_NORMED)
{
if (score < candidate.score)
candidate.score = score;
}
else
{
if (score > candidate.score)
candidate.score = score;
}
}
}
float singleTemplateMatch(const cv::Mat& templateImg, const cv::Mat& candidateImg)
{
cv::Mat result( 1, 1, CV_8UC1 );
cv::matchTemplate( candidateImg, templateImg, result, MATCH_METHOD );
return result.at<float>( 0, 0 );
}
for (int n = 0; n < candidates.size( ); ++n)
std::cout << candidates[n].score << std::endl;
bool selected(const Candidate& candidate, float threshold)
{
/// For SQDIFF and SQDIFF_NORMED, the best matches are lower values. For all the other methods, the higher the better
if (MATCH_METHOD == CV_TM_SQDIFF || MATCH_METHOD == CV_TM_SQDIFF_NORMED)
return candidate.score <= threshold;
else
return candidate.score>threshold;
}
void drawLabel(const Candidate& candidate, const char* label, cv::Mat image)
{
int x = candidate.x - candidate.radius;
int y = candidate.y;
cv::Point point( x, y );
cv::Scalar blue( 255, 128, 128 );
cv::putText( image, label, point, CV_FONT_HERSHEY_SIMPLEX, 1.5f, blue, 2 );
}
cv::Mat labelCoins(const cv::Mat& image, const vector<Candidate>& candidates,
float threshold, bool inverseThreshold, const char* label)
{
cv::Mat imageLabeled;
image.copyTo( imageLabeled );
for (auto it = candidates.begin( ); it != candidates.end( ); ++it)
{
if (selected( *it, threshold ))
drawLabel( *it, label, imageLabeled );
}
return imageLabeled;
}
cv::imwrite( LABELED_IMAGE, labeledImg );