opencv模版匹配

  1. 概述

Opencv提供的可用于模板匹配的方式有三种类型

(1) matchTemplate

(2) matchShapes

(3) keyPointMatch

网上也上这些方式的实现代码,但在各种环境下使用的效果并不怎么好,特别是匹配角度。网上的代码要么不完整,要么在特定的环境下表现还行,但换一种应用环境情况很糟糕。

这里的目标就是构建一种通用的模板匹配,能在大多数环境下适应。

  1. 构建思路

构思的思路是要找出matchTemplate或matchShapes的在常规应用中的问题,改善这些问题。

直接使用matchTemplate,在简单环境中(图片中轮廓和形体比较简单)位置匹配还不错,但角度结果很糟糕,根本不能使用。复杂环境中位置匹配(轮廓、形体比较复杂,灰度阶差较小)经常出错不够准确。

matchShapes在复杂轮廓环境中情况比matchTemplate的表现还差。但若只找出模版中最大的一个轮廓,使用这个轮廓作为模版匹配,情况能改善很多,但也只有90%左右的准确率。

分析matchTemplete位置匹配失败的原因大多是因为使用整个模版匹配导致的特征权重不够引起的,所以使用mask图片只选择目标特种能改善不小。matchShapes使用了多种方式试图改善匹配结果,但收效甚微。

对于角度匹配,实现思路也很简单。对每个角度做一个模板,进行匹配找到最优的角度。

  1. 代码实现

3.1. 创建模板

创建模板的代码核心是使用轮廓处理cv::findContour函数找到ROI的轮廓,过滤出参数设定的面积作为有效轮廓,依此轮廓区域做为模版。然后对此模板选择360度创建各个角度的模板并保存

复制代码
void QGrayMatch::createTemp(const cv::Mat &img)
{
    m_countourMap.clear();
    m_maskMap.clear();
    m_templateMap.clear();

    IVisionWidget * pWnd = ::getCurVisionWnd();
    if(pWnd)pWnd->clear();

    double thres = this->propValue(1).toDouble();
    QString binType = this->propValue(2).toString();
    double minArea = this->propValue(3).toDouble();
    double maxArea = this->propValue(4).toDouble();
    int core = this->propValue(6).toInt();
    double gan = this->propValue(7).toDouble();
    double startAngle = this->propValue(8).toDouble();
    double endAngle = this->propValue(9).toDouble();
    double angleStep = this->propValue(10).toDouble();

    cv::Point2f roiPt;
    QROIVal *pRoi = (QROIVal*)prop(0);
    if(pRoi)
    {
        int n = pRoi->getSubItemCount();
        for(int k =0; k<n; k++)
        {
            const QROIVal * subRoi = pRoi->getSubItem(k);
            if(subRoi->getRoiType() == QROIVal::RECTROI)
            {
                double r0,c0,r1,c1;
                subRoi->getItemRect(r0,c0,r1,c1);
                cv::Rect modelROI(c0,r0,c1-c0,r1-r0);
                cv::Mat modelImg = img(modelROI);
                debugDispImage(modelImg,"roi");

                roiPt = cv::Point2f(c0,r0);

                cv::Mat grayImage;
                if(modelImg.channels() > 1)cv::cvtColor(modelImg, grayImage, cv::COLOR_BGR2GRAY);
                else grayImage = modelImg;
                debugDispImage(grayImage,"gray");

                cv::Mat gaussImage;
                cv::GaussianBlur(grayImage, gaussImage, cv::Size(core, core), gan, 0);
                debugDispImage(gaussImage,"gaussImage");

                cv::Mat binary;
                if("THRESH_BINARY" == binType)cv::threshold(gaussImage, binary, thres, 255, cv::THRESH_BINARY);
                else cv::threshold(gaussImage, binary, thres, 255, cv::THRESH_BINARY_INV);
                debugDispImage(binary,"threshold");

                m_roiSize = binary.size();

                int n = (endAngle - startAngle)/angleStep;
                for(int i=0; i<n; i++)
                {
                    double angle = startAngle + i*angleStep;
                    cv::Mat rotImage;
                    rotateImage(binary,rotImage,angle);
                    debugDispImage(rotImage,QString("angle %0").arg(angle));

                    QCountourList countours;
                    cv::findContours(rotImage, countours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);

                    //选择符合条件的区域
                    QList<int> ids = select(countours,minArea,maxArea);
                    if(ids.size() > 0)
                    {
                        //创建一个全黑的掩码
                        cv::Mat mask = cv::Mat::zeros(rotImage.size(), CV_8UC1);
                        for(int k=0; k<ids.size(); k++)
                        {
                            cv::drawContours(mask,countours,ids[k],cv::Scalar(255),-1);
                            //保存要显示的模版轮廓
                            if(0 == angle)m_countourMap.insert(std::make_pair(ids[k],countours[ids[k]]));
                        }
                        m_maskMap.insert(std::make_pair(angle,mask));
                        debugDispImage(mask,"mask");

                        cv::Mat mm;
                        rotateImage(modelImg,mm,angle);
                        cv::Mat tmpImg;
                        mm.copyTo(tmpImg,mask);
                        m_templateMap.insert(std::make_pair(angle,tmpImg));
                        m_templateSize = tmpImg.size();
                        debugDispImage(tmpImg,"temp");
                    }
                }

                //没有0度是的模版信息,创建0度时的模版信息
                if(m_countourMap.empty())
                {
                    double angle = 0;
                    cv::Mat rotImage = binary;
                    debugDispImage(rotImage,QString("angle %0").arg(angle));

                    QCountourList countours;
                    cv::findContours(rotImage, countours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);

                    //选择符合条件的区域
                    QList<int> ids = select(countours,minArea,maxArea);
                    if(ids.size() > 0)
                    {
                        //创建一个全黑的掩码
                        cv::Mat mask = cv::Mat::zeros(rotImage.size(), CV_8UC1);
                        for(int k=0; k<ids.size(); k++)
                        {
                            cv::drawContours(mask,countours,ids[k],cv::Scalar(255),-1);
                            //保存要显示的模版轮廓
                            m_countourMap.insert(std::make_pair(ids[k],countours[ids[k]]));
                        }
                        m_maskMap.insert(std::make_pair(angle,mask));
                        debugDispImage(mask,"mask");

                        cv::Mat mm;
                        rotateImage(modelImg,mm,angle);
                        cv::Mat tmpImg;
                        mm.copyTo(tmpImg,mask);
                        m_templateMap.insert(std::make_pair(angle,tmpImg));
                        m_templateSize = tmpImg.size();
                        debugDispImage(tmpImg,"temp");
                    }
                }
            }
        }
    }

    if(pWnd)
    {
        pWnd->clear();
        //显示示教的模版轮廓
        QCountourMap::iterator it = m_countourMap.begin();
        for(; it != m_countourMap.end(); ++it)
        {
            this->dispCountour(it->second,roiPt);
        }
    }

    if(pWnd)pWnd->updataView();
}

3.2. 匹配

实现的核心是先使用带mask的matchTemplate方法先获得配置的位置,然后截取ROI大小的区域与各个角度的模版匹配获得准确的角度,但角度的匹配不能使用matchTemplate或matchShapes,这两个函数角度匹配的准确性太差,我使用图像差值后求取差值的图片的平均值作为参考,找差值平均值最小的模版即为当前的角度,角度准确率比较高接近100%

复制代码
void QGrayMatch::match(const cv::Mat &im)
{
    IVisionWidget * pWnd = ::getCurVisionWnd();
    if(pWnd)pWnd->clear();

    //double thres = this->propValue(1).toDouble();
    //QString binType = this->propValue(2).toString();
    //int core = this->propValue(6).toInt();
    double minSocre = this->propValue(5).toDouble();
    //double gan = this->propValue(7).toDouble();
    double startAngle = this->propValue(8).toDouble();
    //double endAngle = this->propValue(9).toDouble();
    double angleStep = this->propValue(10).toDouble();

    cv::Mat grayImage;
    if(im.channels() > 1)cv::cvtColor(im, grayImage, cv::COLOR_BGR2GRAY);
    else grayImage = im;
    debugDispImage(grayImage,"gray");

    //匹配位置
    cv::Mat mask = m_maskMap[0];
    cv::Mat tmpp = m_templateMap[0];
    debugDispImage(mask,"mask");
    debugDispImage(tmpp,"tempp");

    cv::Rect rect;
    cv::Point center;
    double score = this->templateMatch(grayImage,tmpp,mask,rect,center);
    if(score > 0.2)return;

    //根据位置匹配结果获取区域图片
    cv::Mat imm = grayImage(rect);
    debugDispImage(imm,"imm");

    //匹配角度
    double angle = 0;
    double angleScore = 100;
    for(int i=0; i<m_templateMap.size(); i++)
    {
         double ang = startAngle + i*angleStep;
         cv::Mat mask = m_maskMap[ang];
         cv::Mat tmpp = m_templateMap[ang];
         debugDispImage(mask,"mask");
         debugDispImage(tmpp,"tempp");

         cv::Mat rrs;
         cv::absdiff(imm,tmpp,rrs);
         cv::Mat rrt;
         rrs.copyTo(rrt,mask);

         cv::Scalar s = cv::mean(rrt);
         double d = s[0];
         if(d < angleScore)
         {
             angleScore = d;
             angle = ang;
         }

         debugDispImage(rrt,QString("t-m %0 %1").arg(d).arg(ang));
    }

    if(score < minSocre && angleScore < 10)
    {
         QString msg = QString("match res : %0,  angle : %1").arg(score).arg(angle);
         if(pWnd)
         {
             pWnd->dispText(msg);
             pWnd->dispCross(center.y,center.x,20,45);
             pWnd->dispRect(rect.y,rect.x,rect.y+rect.height,rect.x+rect.width);
         }
    }
    if(pWnd)pWnd->updataView();
}

3.3. 归档和载入

档的目标是模版信息保存在.dat文件中,其中包含了各个角度的模版信息和mask信息。

复制代码
void QGrayMatch::saveTemplate()
{
    QString fileName = QSystem::getCurrentProductDir() +"/" + id() + "_grayMatch.dat";
    QFile file(fileName);
    if(file.open(QIODevice::WriteOnly))
    {
        QDataStream stream(&file);
        //for(int i=0; i<100; i++)
        //    stream << "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

        int nAngle = m_templateMap.size();
        stream << 0xffff0000 << 0xffff0000;
        stream << m_nTemplateMethod << m_templateSize << m_roiSize << nAngle; //16个byte

        QTemplateMap::iterator it = m_templateMap.begin();
        for(; it != m_templateMap.end(); ++it)
        {
            stream << (unsigned int)0xffff0000 <<  (unsigned int)0xffff0000 << it->first;
            cv::Mat &im = it->second;
            stream << im;
        }

        QMaskMap::iterator it2 = m_maskMap.begin();
        for(; it2 != m_maskMap.end(); ++it2)
        {
            stream << (unsigned int)0xffff0000 <<  (unsigned int)0xffff0000 << it2->first;
            cv::Mat &im = it2->second;
            stream << im;
        }

        file.flush();
    }
}

void QGrayMatch::loadTemplate()
{
    QString fileName = QSystem::getCurrentProductDir() +"/" + id() + "_grayMatch.dat";
    QFile file(fileName);
    if(file.open(QIODevice::ReadOnly))
    {
        QDataStream stream(&file);
        unsigned int fl1,fl2;
        stream >> fl1 >> fl2;
        if(fl1 == fl2 &&  fl2== 0xffff0000)
        {

            if(stream.atEnd())return;

            int nAngle = 0;
            stream >> m_nTemplateMethod >> m_templateSize >> m_roiSize >> nAngle;
            if(stream.atEnd())return;

            for(int i=0; i<nAngle; i++)
            {
                if(stream.atEnd())return;

                unsigned int f1,f2;
                double angle = 0;
                stream >> f1 >> f2 >> angle;

                if(stream.atEnd())return;

                if(f1 == 0xffff0000 && f2 == 0xffff0000)
                {
                    cv::Mat im;
                    stream >> im;
                    m_templateMap.insert(std::make_pair(angle,im));
                    //QString path = QString("F:\\test\\template_%0.png").arg(i);
                    //cv::imwrite(path.toStdString(),im);
                }
            }

            for(int i=0; i<nAngle; i++)
            {
                if(stream.atEnd())return;

                unsigned int f1,f2;
                double angle = 0;
                stream >> f1 >> f2 >> angle;

                if(stream.atEnd())return;

                if(f1 == 0xffff0000 && f2 == 0xffff0000)
                {
                    cv::Mat im;
                    stream >> im;
                    m_maskMap.insert(std::make_pair(angle,im));
                    //QString path = QString("F:\\test\\mask_%0.png").arg(i);
                    //cv::imwrite(path.toStdString(),im);
                }
            }
        }
    }
}
  1. 后记

opencv模版匹配最大的为题是位置匹配的准确度和角度匹配的准确度。使用mask后模板匹配的准确度提升了不少,截取配置匹配后的区域,使用图像差值法匹配角度基本上能100%准确匹配角度。

但受限于matchTemplate函数的能力,目前还无法做到与halcon的模版匹配同样的准确率。

相关推荐
TechNomad3 小时前
十六、OpenCV中的图像文件处理
opencv
B站计算机毕业设计之家3 小时前
Python手势识别检测系统 基于MediaPipe的改进SSD算法 opencv+mediapipe 深度学习 大数据 (建议收藏)✅
python·深度学习·opencv·计算机视觉·1024程序员节
犽戾武3 小时前
1. 简单回顾Numpy神经网络
人工智能·神经网络·numpy
Lab4AI大模型实验室3 小时前
【每日Arxiv热文】还在为视频编辑发愁?港科大&蚂蚁集团提出Ditto框架刷新SOTA!
人工智能·计算机视觉·视频编辑·ai agent·智能体学习
阿里云大数据AI技术3 小时前
云栖实录 | 实时计算 Flink 全新升级 - 全栈流处理平台助力实时智能
大数据·人工智能
新加坡内哥谈技术3 小时前
谷歌Quantum Echoes算法:迈向量子计算现实应用的重要一步
人工智能
仰泳的熊猫4 小时前
LeetCode:773. 滑动谜题
数据结构·c++·算法·leetcode
Godspeed Zhao4 小时前
自动驾驶中的传感器技术70——Navigation(7)
人工智能·机器学习·自动驾驶
这儿有一堆花4 小时前
AI 翻译入门指南:机器如何理解语言
人工智能·web