第 9 章 描述和匹配兴趣点


本章包括以下内容:

  • 局部模板匹配;
  • 描述并匹配局部强度值模式;
  • 用二值描述子匹配关键点。


9.2 局部模板匹配

本节介绍的方案是对图像块中的像素进行逐个比较。这可能是最简单的特征点匹配方法了,但是并不是最可靠的。

最常见的图像块是边长为奇数的正方形,关键点的位置就是正方形的中心。可通过比较块内像素的强度值来衡量两个正方形图像块的相似度。

常见的方案是采用简单的差的平方和(Sum of Squared Differences,SSD)算法。下面是特征匹配策略的具体步骤。

首先检测每幅图像的关键点,这里使用FAST 检测器:

// 定义特征检测器
cv::Ptr<cv::FeatureDetector> ptrDetector; // 泛型检测器指针
ptrDetector = cv::FastFeatureDetector::create(80); // 这里选用FAST 检测器

// 检测关键点
ptrDetector->detect(image1, keypoints1);
ptrDetector->detect(image2, keypoints2);

这里采用了可以指向任何特征检测器的泛型指针类型cv::Ptr<cv::FeatureDetector>。上述代码可用于各种兴趣点检测器,只需在调用函数时更换检测器即可。

然后定义一个特定大小(例如11×11)的矩形,用于表示每个关键点周围的图像块:

// 定义正方形的邻域
const int nsize(11); // 邻域的尺寸
cv::Rect neighborhood(0, 0, nsize, nsize); // 11×11
cv::Mat patch1;
cv::Mat patch2;

将一幅图像的关键点与另一幅图像的全部关键点进行比较。在第二幅图像中找出与第一幅图像中的每个关键点最相似的图像块。这个过程用两个嵌套循环实现,代码如下所示:

// 在第二幅图像中找出与第一幅图像中的每个关键点最匹配的
cv::Mat result;
std::vector<cv::DMatch> matches;

// 针对图像一的全部关键点
for (int i = 0; i < keypoints1.size(); i++) {

    // 定义图像块
    neighborhood.x = keypoints1[i].pt.x - nsize / 2;
    neighborhood.y = keypoints1[i].pt.y - nsize / 2;

    // 如果邻域超出图像范围,就继续处理下一个点
    if (neighborhood.x < 0 || neighborhood.y < 0 ||
        neighborhood.x + nsize >= image1.cols ||
        neighborhood.y + nsize >= image1.rows)
        continue;

    // 第一幅图像的块
    patch1 = image1(neighborhood);

    // 存放最匹配的值
    cv::DMatch bestMatch;

    // 针对第二幅图像的全部关键点
    for (int j = 0; j < keypoints2.size(); j++) {

        // 定义图像块
        neighborhood.x = keypoints2[j].pt.x - nsize / 2;
        neighborhood.y = keypoints2[j].pt.y - nsize / 2;

        // 如果邻域超出图像范围,就继续处理下一个点
        if (neighborhood.x < 0 || neighborhood.y < 0 ||
            neighborhood.x + nsize >= image2.cols ||
            neighborhood.y + nsize >= image2.rows)
            continue;

        // 第二幅图像的块
        patch2 = image2(neighborhood);

        // 匹配两个图像块
        cv::matchTemplate(patch1, patch2, result, cv::TM_SQDIFF);

        // 检查是否为最佳匹配
        if (result.at<float>(0, 0) < bestMatch.distance) {
            bestMatch.distance = result.at<float>(0, 0);
            bestMatch.queryIdx = i;
            bestMatch.trainIdx = j;
        }
    }

    // 添加最佳匹配
    matches.push_back(bestMatch);
}

注意,这里用cv::matchTemplate 函数来计算图像块的相似度。找到一个可能的匹配项后,用一个cv::DMatch 对象来表示。这个工具类存储了两个被匹配关键点的序号和它们的相似度。

两个图像块越相似,它们对应着同一个场景点的可能性就越大。因此需要根据相似度对匹配结果进行排序:

// 提取25 个最佳匹配项
std::nth_element(matches.begin(), matches.begin() + 25, matches.end());
matches.erase(matches.begin() + 25, matches.end());

你可以用一个相似度阈值筛选这些匹配项,并得出筛选结果。这里保留相似度最高的N 个匹配项(为了方便显示结果,选用N = 25)

OpenCV 本身就带有一个能显示匹配结果的函数——它把两幅图像拼接起来,然后用线条连接每个对应的点。函数的用法如下所示:

// 画出匹配结果
cv::Mat matchImage;
cv::drawMatches(image1, keypoints1, // 第一幅图像
    image2, keypoints2, // 第二幅图像
    matches, // 匹配项的向量
    cv::Scalar(255, 255, 255), // 线条颜色
    cv::Scalar(255, 255, 255)); // 点的颜色

得到的结果如下所示。

result.jpg

模板匹配

图像分析中的一个常见任务是检测图像中是否存在特定的图案或物体。实现方法是把包含该物体的小图像作为模板,然后在指定图像上搜索与模板相似的部分。搜索的范围通常仅限于可能发现该物体的区域。在这个区域上滑动模板,并在每个像素位置计算相似度。执行这个操作的函数是cv::matchTemplate,函数的输入对象是一个小图像模板和一个被搜索的图像。

结果是一个浮点数型的cv::mat 函数,表示每个像素位置上的相似度。假设模板尺寸为M × N,图像尺寸为W × H,那么结果矩阵的尺寸就是(W - M + 1) × (H - N + 1)。我们通常只关注相似度最高的位置。

典型的模板匹配代码如下所示(假设目标变量就是这个模板):

// 定义搜索区域
cv::Mat roi(image2, // 这里用图像的上半部分
    cv::Rect(0, 0, image2.cols, image2.rows / 2));
// 进行模板匹配
cv::matchTemplate(roi, // 搜索区域
    target, // 模板
    result, // 结果
    cv::TM_SQDIFF); // 相似度
    // 找到最相似的位置
double minVal, maxVal;
cv::Point minPt, maxPt;
cv::minMaxLoc(result, &minVal, &maxVal, &minPt, &maxPt);
// 在相似度最高的位置绘制矩形
// 本例中为minPt
cv::rectangle(roi, cv::Rect(minPt.x, minPt.y,
    target.cols, target.rows), 255);

这个操作是非常耗时的,因此应该限制搜索的区域,并且模板的像素要少。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容