QT + opencv 实现形状(轮廓)模板匹配
实现思路
1.创建模板数据:主要是提取模板的轮廓信息,这一步通常通过边缘检测实现。将模板的轮廓信息存储起来。
代码:
//创建形状模板
bool cvLearnShapeMatchPattern_(Mat matDst, MyShapeUiParam param, MyShapeTemplData* pTemplData)
{
//图像不存在
if(matDst.empty())
return false;
//清除模板
pTemplData->clear();
//边缘检测
Mat canny, gray;
vector<Point> contours;
cv::Canny(matDst, canny, pTemplData->minThresh, pTemplData->maxThresh);
canny.copyTo(gray);
/*if(!(pTemplData->sample==0))
{
Mat element=getStructuringElement(MORPH_ELLIPSE, Size(pTemplData->dilate, pTemplData->dilate));
cv::dilate(canny, gray, element);
}*/
cvFindContour_(gray, contours, pTemplData->minLen, 10e9, pTemplData->sample);
if((int)contours.size()==0)
return false;
//压缩金字塔
int iTopLayer=param.m_iPyramid;
buildPyramid(matDst, pTemplData->vecPyramid, iTopLayer);
for(size_t i=0;i<pTemplData->vecPyramid.size();i++)
{
//轮廓信息
Mat src=pTemplData->vecPyramid[i];
vector<Point> pyrContour=cvPyrContour_(contours, matDst.size(), src.size());
pTemplData->vecPoints.push_back(pyrContour);
//显示
Mat dst=Mat::zeros(src.size(), CV_8UC1);
for(size_t j=0;j<pyrContour.size();j++)
dst.at<uchar>(pyrContour[j].y, pyrContour[j].x)=255;
imshow(QString("%1").arg(i).toStdString(), dst);
cv::waitKey(5);
//梯度信息
Mat gx, gy, mag, dir;
Sobel(src, gx, CV_32F, 1, 0);
Sobel(src, gy, CV_32F, 0, 1);
cartToPolar(gx, gy, mag, dir);
//提取梯度
vector<SPtin> vecSPtin;
for(size_t j=0;j<pyrContour.size();j++)
{
SPtin info;
Point p=pyrContour[j];
info.derivativeX=gx.at<float>(p.y, p.x);
info.derivativeY=gy.at<float>(p.y, p.x);
info.magnitude=mag.at<float>(p.y, p.x);
info.magnitudeN = info.magnitude==0 ? 0 : (1.0f / info.magnitude);
vecSPtin.push_back(info);
}
pTemplData->vecSPtins.push_back(vecSPtin);
}
//训练标记
pTemplData->bIsPatternLearned=true;
return true;
}
2.模板匹配:同样需要利用压缩金字塔的方法来提升搜索速度,减少参数量。然后取顶层金字塔进行旋转,和模板轮廓进行匹配,找到大致角度后再进行细致的搜索。
部分代码
//模板匹配
bool cvShapeMatch_(Mat matSrc, vector<MyShapeTemplData> templDatas, MyShapeUiParam param, vector<MySingleTargetMatch>& vecSingleTargetData)
{
//图像
if(matSrc.empty())
return false;
//模板
if((int)templDatas.size()==0)
return false;
//決定金字塔層數 總共為1 + iLayer層
int iTopLayer=param.m_iPyramid;
vector<Mat> vecMatSrcPyr;
buildPyramid(matSrc, OutputArrayOfArrays(vecMatSrcPyr), iTopLayer);
//取顶层图片的中心点
int iTopSrcW = vecMatSrcPyr[iTopLayer].cols, iTopSrcH = vecMatSrcPyr[iTopLayer].rows;
Point2f ptCenter((iTopSrcW - 1) / 2.0f, (iTopSrcH - 1) / 2.0f);
//Caculate lowest score at every layer
vector<double> vecLayerScore(iTopLayer + 1, param.m_dScore);
for (int iLayer = 1; iLayer <= iTopLayer; iLayer++)
vecLayerScore[iLayer] = vecLayerScore[iLayer - 1] * param.m_dGreed;
//clear
vecSingleTargetData.clear();
//并行加速
omp_set_num_threads(4);
#pragma omp parallel for
for(int k=0;k<(int)templDatas.size();k++)
{
MyShapeTemplData* pTemplData = &templDatas[k];
if((int)pTemplData->vecPyramid.size()==0 || !pTemplData->bIsPatternLearned)
continue;
Mat matDst=pTemplData->vecPyramid[0];
if (matDst.empty())
continue;
if ((matDst.cols<matSrc.cols && matDst.rows>matSrc.rows) || (matDst.cols>matSrc.cols && matDst.rows<matSrc.rows))
continue;
if (matDst.size().area()>=matSrc.size ().area())
continue;
//匹配轮廓
vector<Point> Contour=pTemplData->vecPoints[0];
cvAffineTrans_(Contour, Point3f((float)(matDst.cols - 1)/2.0f, (float)(matDst.rows - 1)/2.0f, 0), Point3f(0, 0, 0));
//第一階段以最頂層找出大致角度與ROI
double dAngleStep=atan(2.0 / max(pTemplData->vecPyramid[iTopLayer].cols, pTemplData->vecPyramid[iTopLayer].rows)) * R2D;
实现效果:实际匹配目标时,角度搜索范围可以不必像我这样设置这么大。在相机画面中进行检测时,通常是在ROI中进行检测,速度通常在100ms以内。