opencv基于梯度的形状模板匹配,公式就是这个干货 | OpenCV实现边缘模板匹配算法
创建模板
- Canny得到边缘图像。
- 模板图像和边缘图像的padding,确保旋转的时候,图像都在。
auto PaddingTempl = [](Mat& mInput, Rect& rectRoi)
{
int nDiagonal = sqrt(pow(mInput.cols, 2) + pow(mInput.rows, 2)) + 1;
if ((nDiagonal - mInput.cols) % 2 != 0) {
nDiagonal++; }
Size paddingSize(nDiagonal, nDiagonal);
Mat paddingImg = mInput;
rectRoi = Rect(Point((paddingSize.width - mInput.cols) * 0.5, (paddingSize.height - mInput.rows) * 0.5), Size(mInput.cols, mInput.rows));
copyMakeBorder(paddingImg, mInput, rectRoi.tl().y, paddingSize.height - rectRoi.br().y, rectRoi.tl().x, paddingSize.width - rectRoi.br().x, BORDER_CONSTANT, Scalar::all(0));
return;
};
- 旋转padding后的模板图片,Sobel图像,根据大佬ImageShop,可以对Gx,Gy图像归一化拿到 NormGx,NormGy图像
void NormalizedSobel(const Mat mInput, Mat& mGx, Mat& mGy)
{
Sobel(mInput, mGx, CV_32F, 1, 0);
Sobel(mInput, mGy, CV_32F, 0, 1);
float* pGx = mGx.ptr<float>(0);
float* pGy = mGy.ptr<float>(0);
float fG = 0;
for(int row = 0; row < mGx.rows; row++)
{
pGx = mGx.ptr<float>(row);
pGy = mGy.ptr<float>(row);
for(int col = 0; col < mGx.cols; col++)
{
if(pGx[col]!=0 || pGy[col]!=0)
{
fG = 1./sqrtf(powf(pGx[col],2) + powf(pGy[col],2));
pGx[col] *=fG;
pGy[col] *=fG;
}
}
}
}
- 旋转padding后的边缘图像并遍历,根据其像素值为1的点,向NormGx,NormGy图像提取相应的梯度值。
for (int row = 0; row < mRotateEdge.rows; row++)
{
pGx = mGx.ptr<float>(row);
pGy = mGy.ptr<float>(row);
pEdge = mRotateEdge.ptr<uchar>(row);
for (int col = 0; col < mRotateEdge.cols; col++)
{
float fGx = pGx[col];
float fGy = pGy[col];
uchar uEdge = pEdge[col];
if (uEdge == 1 && fabs(fGx) >= 1e-6 && fabs(fGy) >= 1e-6)
{
templates[nId].m_vstGradientInfo.emplace_back(sGradient(fGx, fGy, Point(col, row)));
templates[nId].m_mNormGx.ptr<float>(row)[col] = fGx;
templates[nId].m_mNormGy.ptr<float>(row)[col] = fGy;
templates[nId].m_nCoordsNums++;
}
}
}
二 匹配
- 将目标图像padding,padding后的目标图像与模板卷积后,就是的原始目标图像的大小。这样在卷积的过程中,就不用判断位置是否越界,速度也会快点。
void PaddingSrc(Mat& mSrc, Size sTempl, Rect& rectRoi)
{
Size paddingSize(mSrc.cols + sTempl.width - 1, mSrc.rows + sTempl.height - 1);
rectRoi = Rect(sTempl.width * 0.5, sTempl.height * 0.5, mSrc.cols, mSrc.rows);
Mat paddingImg = mSrc.clone();
copyMakeBorder(paddingImg, mSrc, rectRoi.tl().y, paddingSize.height - rectRoi.br().y, rectRoi.tl().x, paddingSize.width - rectRoi.br().x, BORDER_CONSTANT, Scalar::all(0));
- 在高层的时候,还是用dft更快。比起NCC,卷两次就可以了。最后把两次卷积相加,再除点数。
Size size(mGx.cols - templ->m_mNormGx.cols + 1, mGx.rows - templ->m_mNormGx.rows + 1);
mResult = Mat::zeros(size, CV_32F);
Mat mCorrGx = Mat::zeros(size, CV_32F);
Mat mCorrGy = Mat::zeros(size, CV_32F);
ConvDFT(mGx, templ->m_mNormGx, mCorrGx);
ConvDFT(mGy, templ->m_mNormGy, mCorrGy);
mResult = (mCorrGx + mCorrGy) / templ->m_nCoordsNums;
- 有了候选点,在下一层进行局部匹配。这时候就可以用上贪婪度加快速度。局部search的范围,我是根据金字塔层数计算的:2 + 2 * nLevel,最底层就是3x3。
void EdgeShapeMatch::Match(const Mat mGx, const Mat mGy, const double dMinScore, const int nSearchSize,const double dGreediness, const sTemplate* templ,double& dMax, Point& pMax, Mat& mResult)
{
int nCoordsNums = templ->m_vstGradientInfo.size();
double dNormMinScore = dMinScore / nCoordsNums;
double dNormGreediness = ((1 - dGreediness * dMinScore) / (1 - dGreediness)) / nCoordsNums;
double dMinScore_1 = dMinScore - 1;
double dPartialScore = 0, dPartialSum = 0;
int nCoords = 0, curX = 0, curY = 0;
dMax = 0;
for (int row = 0; row < nSearchSize + 1; row++)
{
for (int col = 0; col < nSearchSize + 1; col++)
{
dPartialSum = 0;
for (int n = 0; n < nCoordsNums; n++)
{
nCoords = n + 1;
curX = col + templ->m_vstGradientInfo[n].m_pPosition.x;
curY = row + templ->m_vstGradientInfo[n].m_pPosition.y;
dPartialSum += mGx.ptr<float>(curY)[curX] * templ->m_vstGradientInfo[n].m_fGx
+ mGy.ptr<float>(curY)[curX] * templ->m_vstGradientInfo[n].m_fGy;
dPartialScore = dPartialSum / nCoords;
if (dPartialScore < min(dMinScore_1 + dNormGreediness * nCoords, dNormMinScore * nCoords))
{
break;
}
}
if (dPartialScore >= dMax)
{
dMax = dPartialScore;
pMax.x = col;
pMax.y = row;
}
mResult.ptr<float>(row)[col] = dPartialScore;
}
}
}
三 测试结果
680x480, [-180,180], 4level, 14.7186ms
830x832, [-180,180], 4level, 31.43ms
646x492, [-180,180], 4level, 9.30ms
exe测试程序