opencv edge based shape matching

opencv gradient-based shape template matching, the formula is this dry information | OpenCV implements edge template matching algorithm

Create template

  1. Canny gets the edge image.
  2. The padding of the template image and edge image ensures that the image is present when rotating.
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;
    };
  1. Rotate the padded template image and Sobel image. According to ImageShop, you can normalize the Gx and Gy images to get the NormGx and NormGy images.
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;
        	}
    	}
	}  
}
  1. Rotate the padded edge image and traverse it, and extract the corresponding gradient values ​​from the NormGx and NormGy images based on the points whose pixel value is 1.
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++;
       	}
     }
 }

Two matches

  1. After padding the target image, the padded target image is convolved with the template, which is the size of the original target image. In this way, during the convolution process, there is no need to judge whether the position is out of bounds, and the speed will be faster.
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));
  1. At high levels, it is faster to use dft. Compared with NCC, you only need to roll it twice. Finally, add the two convolutions and divide the points.
  	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;
  1. With candidate points, local matching is performed in the next layer. At this time, you can use greed to speed up. I calculated the scope of local search based on the number of pyramid levels: 2 + 2 * nLevel, and the bottom layer is 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;
        }
    }
}

Three test results

680x480, [-180,180], 4level, 14.7186ms Insert image description here
830x832, [-180,180], 4level, 31.43ms Insert image description here
646x492, [-180,180], 4level, 9.30ms Insert image description here
exe test program

Guess you like

Origin blog.csdn.net/weixin_43493903/article/details/132125104