[OpenCV实战]19 使用OpenCV实现基于特征的图像对齐
目录
1 背景
1.1 什么是图像对齐或图像对准?
1.2 图像对齐的应用
1.3 图像对齐基础理论
1.4 如何找到对应点
2 OpenCV的图像对齐
2.1 基于特征的图像对齐的步骤
2.2 代码
3 参考
在这篇文章中,我们将学习如何使用OpenCV执行基于特征的图像对齐。我们将使用移动电话拍摄的表格的照片与表格的模板对齐。我们将使用的技术通常被称为“基于特征图像对齐”,因为在该技术中,在一个图像中检测稀疏的特征集并且在另一图像中进行特征匹配。然后基于这些匹配特征将原图像映射到另一个图像,实现图像对齐。如下图所示:
1 背景
1.1 什么是图像对齐或图像对准?
在许多应用程序中,我们有两个相同场景或同一文档的图像,但它们没有对齐。换句话说,如果您在一个图像上选择一个特征(例如白纸的一个边角),则另一个图像中同一个边角的坐标会有很大差异。图像对齐(也称为图像配准)是使一个图像(或两个图像)进行变换的方法,使得两个图像中的特征完美地对齐。入戏
下面是一个例子,中间的表是手机拍摄的表格,左边的表是原始文档。中间的表在经过图像对齐技术处理之后结果如右图所示,可以和左边的模板一样。对齐之后就可以根据模板的格式对用户填写的内容进行分析了。
1.2 图像对齐的应用
图像对齐有许多应用。
在许多文档处理应用程序中,第一步是将扫描或拍摄的文档与模板对齐。例如,如果要编写自动表单阅读器,最好先将表单与其模板对齐,然后根据模板中的固定位置读取字段。
在一些医学应用中,可以把多次拍摄的照片拼接起来。
图像对齐最有趣的应用可能是创建全景图。在这种情况下,两个图像不是平面的图像而是3D场景的图像。通常,3D对齐需要深度信息。然而,当通过围绕其光轴旋转相机拍摄两个图像时(如全景图的情况),我们可以使用本教程中描述的技术来对齐全景图的两张图像。
1.3 图像对齐基础理论
图像对齐技术的核心是一个简单的3×3矩阵,称为Homography(单应性变换)。具体见:
https://blog.csdn.net/LuohenYJ/article/details/89334249
https://en.wikipedia.org/wiki/Homography
https://mp.weixin.qq.com/s/-XrjAjf8ItNMkQyqvcjATQ
我们来看看用法。
C ++
findHomography(points1, points2, h)
python
h, status = cv2.findHomography(points1, points2)
其中,points1和points2是矢量/对应点的阵列,以及ħ是单应性矩阵。
1.4 如何找到对应点
在许多计算机视觉应用中,我们经常需要识别图像中有趣的稳定点。这些点称为关键点或特征点。在OpenCV中实现了几个关键点检测器(例如SIFT,SURF和ORB)。在本教程中,我们将使用ORB特征检测器,因为SIFT和SURF已获得专利,如果您想在实际应用中使用它,则需要支付许可费。ORB快速,准确且无许可证!ORB关键点使用圆圈显示在下图中。
ORB代表Oriented FAST和Rotated BRIEF;让我们看看FAST和BRIEF是什么意思。
特征点检测器有两个部分
(1) 定位器
识别图像上在图像变换下稳定不变的点,如平移(移位),缩放(增大/减小)和旋转。定位器找到这些点的x,y坐标。ORB检测器使用的定位器称为FAST。详细信息见:
https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_feature2d/py_fast/py_fast.html
(2) 特征描述子
上述步骤中的定位器只能告诉我们有趣的点在哪里。特征检测器的第二部分是特征描述子,它对点的外观进行编码,以便我们可以分辨不同的特征点。在特征点评估的特征描述只是一个数字数组。理想情况下,两个图像中的相同物理点应具有相同的特征描述。ORB使用名为BRISK的特征描述子。详细信息见:
https://www.robots.ox.ac.uk/~vgg/rg/papers/brisk.pdf
定位器和特征描述子应用很广泛。计算机视觉的许多应用中,我们分两步解决识别问题a)定位;2)识别。例如,为了实现面部识别系统,我们首先需要一个面部检测器,其输出面部所在矩形的坐标。检测器不知道或不关心该人是谁。唯一的工作就是找到一张脸。系统的第二部分是识别算法。原始图像被裁剪为检测到的面部矩形,并且该裁剪的图像反馈送到最终识别该人的面部识别算法。特征检测器的定位器就像面部检测器。描述子类似识别器。
只有当我们知道两个图像中的对应特征时,才能计算出与两个图像相关的单应性。因此,使用匹配算法来查找一个图像中的哪些特征与另一图像中的特征匹配。为此,将一个图像中的每个特征的描述子与第二个图像中的每个特征的描述子进行比较,以找到良好的匹配点。也就是说我们可以通过描述子找到要匹配的特征点,然后根据这些匹配的特征点,计算两个图像相关的单应性,实现图像映射。
ORB其他信息可以见
https://www.jianshu.com/p/387b8ac04c94
2 OpenCV的图像对齐
2.1 基于特征的图像对齐的步骤
现在我们可以总结图像对齐所涉及的步骤。
Step1读图
我们首先在C ++中和Python中读取参考图像(或模板图像)和我们想要与此模板对齐的图像。
Step2寻找特征点
我们检测两个图像中的ORB特征。虽然我们只需要4个特征来计算单应性,但通常在两个图像中检测到数百个特征。我们使用Python和C
++代码中的参数MAX_FEATURES来控制功能的数量。
Step3 特征点匹配
我们在两个图像中找到匹配的特征,按匹配的评分对它们进行排序,并保留一小部分原始匹配。我们使用汉明距离(hamming
distance)作为两个特征描述符之间相似性的度量。请注意,我们有许多不正确的匹配。
Step4 计算Homography
当我们在两个图像中有4个或更多对应点时,可以计算单应性。上一节中介绍的自动功能匹配并不总能产生100%准确的匹配。20-30%的比赛不正确并不罕见。幸运的是,findHomography方法利用称为随机抽样一致性算法(RANSAC)的强大估计技术,即使在存在大量不良匹配的情况下也能产生正确的结果。RANSAC具体介绍见:
https://www.cnblogs.com/xingshansi/p/6763668.html
https://blog.csdn.net/zinnc/article/details/52319716
Step5 图像映射
一旦计算出准确的单应性,我可以应用于一个图像中的所有像素,以将其映射到另一个图像。这是使用OpenCV中的warpPerspective函数完成的。
2.2 代码
在本节中,我们将使用OpenCV呈现用于图像对齐的C++和Python代码。所处理的对象为对本文第二张图所示的三张图。其中第一张图为参考图像,第二张图为用于对齐的图,第三张图为结果图像。第一张图和第二张图特征点匹配的结果如下图所示:
所有代码见:
https://github.com/luohenyueji/OpenCV-Practical-Exercise
C++代码如下:
// OpenCV_Align.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include "opencv2/xfeatures2d.hpp"
#include "opencv2/features2d.hpp"
using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;
//最大特征点数
const int MAX_FEATURES = 500;
//好的特征点数
const float GOOD_MATCH_PERCENT = 0.15f;
/**
* @brief 图像对齐
*
* @param im1 对齐图像
* @param im2 模板图像
* @param im1Reg 输出图像
* @param h
*/
void alignImages(Mat &im1, Mat &im2, Mat &im1Reg, Mat &h)
{
// Convert images to grayscale
Mat im1Gray, im2Gray;
//转换为灰度图
cvtColor(im1, im1Gray, CV_BGR2GRAY);
cvtColor(im2, im2Gray, CV_BGR2GRAY);
// Variables to store keypoints and descriptors
//关键点
std::vector<KeyPoint> keypoints1, keypoints2;
//特征描述符
Mat descriptors1, descriptors2;
// Detect ORB features and compute descriptors. 计算ORB特征和描述子
Ptr<Feature2D> orb = ORB::create(MAX_FEATURES);
orb->detectAndCompute(im1Gray, Mat(), keypoints1, descriptors1);
orb->detectAndCompute(im2Gray, Mat(), keypoints2, descriptors2);
// Match features. 特征点匹配
std::vector<DMatch> matches;
//汉明距离进行特征点匹配
Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");
matcher->match(descriptors1, descriptors2, matches, Mat());
// Sort matches by score 按照特征点匹配结果从优到差排列
std::sort(matches.begin(), matches.end());
// Remove not so good matches 移除不好的特征点
const int numGoodMatches = matches.size() * GOOD_MATCH_PERCENT;
matches.erase(matches.begin() + numGoodMatches, matches.end());
// Draw top matches
Mat imMatches;
//画出特征点匹配图
drawMatches(im1, keypoints1, im2, keypoints2, matches, imMatches);
imwrite("matches.jpg", imMatches);
// Extract location of good matches
std::vector<Point2f> points1, points2;
//保存对应点
for (size_t i = 0; i < matches.size(); i++)
{
//queryIdx是对齐图像的描述子和特征点的下标。
points1.push_back(keypoints1[matches[i].queryIdx].pt);
//queryIdx是是样本图像的描述子和特征点的下标。
points2.push_back(keypoints2[matches[i].trainIdx].pt);
}
// Find homography 计算Homography,RANSAC随机抽样一致性算法
h = findHomography(points1, points2, RANSAC);
// Use homography to warp image 映射
warpPerspective(im1, im1Reg, h, im2.size());
}
int main()
{
// Read reference image 读取参考图像
string refFilename("./image/form.jpg");
cout << "Reading reference image : " << refFilename << endl;
Mat imReference = imread(refFilename);
// Read image to be aligned 读取对准图像
string imFilename("./image/scanned-form.jpg");
cout << "Reading image to align : " << imFilename << endl;
Mat im = imread(imFilename);
// Registered image will be resotred in imReg.
// The estimated homography will be stored in h.
//结果图像,单应性矩阵
Mat imReg, h;
// Align images
cout << "Aligning images ..." << endl;
alignImages(im, imReference, imReg, h);
// Write aligned image to disk.
string outFilename("aligned.jpg");
cout << "Saving aligned image : " << outFilename << endl;
imwrite(outFilename, imReg);
// Print estimated homography
cout << "Estimated homography : \n" << h << endl;
return 0;
}
Python代码如下:
from __future__ import print_function
import cv2
import numpy as np
MAX_MATCHES = 500
GOOD_MATCH_PERCENT = 0.15
def alignImages(im1, im2):
# Convert images to grayscale
im1Gray = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY)
im2Gray = cv2.cvtColor(im2, cv2.COLOR_BGR2GRAY)
# Detect ORB features and compute descriptors.
orb = cv2.ORB_create(MAX_MATCHES)
keypoints1, descriptors1 = orb.detectAndCompute(im1Gray, None)
keypoints2, descriptors2 = orb.detectAndCompute(im2Gray, None)
# Match features.
matcher = cv2.DescriptorMatcher_create(cv2.DESCRIPTOR_MATCHER_BRUTEFORCE_HAMMING)
matches = matcher.match(descriptors1, descriptors2, None)
# Sort matches by score
matches.sort(key=lambda x: x.distance, reverse=False)
# Remove not so good matches
numGoodMatches = int(len(matches) * GOOD_MATCH_PERCENT)
matches = matches[:numGoodMatches]
# Draw top matches
imMatches = cv2.drawMatches(im1, keypoints1, im2, keypoints2, matches, None)
cv2.imwrite("matches.jpg", imMatches)
# Extract location of good matches
points1 = np.zeros((len(matches), 2), dtype=np.float32)
points2 = np.zeros((len(matches), 2), dtype=np.float32)
for i, match in enumerate(matches):
points1[i, :] = keypoints1[match.queryIdx].pt
points2[i, :] = keypoints2[match.trainIdx].pt
# Find homography
h, mask = cv2.findHomography(points1, points2, cv2.RANSAC)
# Use homography
height, width, channels = im2.shape
im1Reg = cv2.warpPerspective(im1, h, (width, height))
return im1Reg, h
if __name__ == '__main__':
# Read reference image
refFilename = "./image/form.jpg"
print("Reading reference image : ", refFilename)
imReference = cv2.imread(refFilename, cv2.IMREAD_COLOR)
# Read image to be aligned
imFilename = "./image/scanned-form.jpg"
print("Reading image to align : ", imFilename);
im = cv2.imread(imFilename, cv2.IMREAD_COLOR)
print("Aligning images ...")
# Registered image will be resotred in imReg.
# The estimated homography will be stored in h.
imReg, h = alignImages(im, imReference)
# Write aligned image to disk.
outFilename = "aligned.jpg"
print("Saving aligned image : ", outFilename);
cv2.imwrite(outFilename, imReg)
# Print estimated homography
print("Estimated homography : \n", h)
3 参考
https://www.learnopencv.com/image-alignment-feature-based-using-opencv-c-python/
[OpenCV实战]19 使用OpenCV实现基于特征的图像对齐的更多相关文章
- [OpenCV实战]20 使用OpenCV实现基于增强相关系数最大化的图像对齐
目录 1 背景 1.1 彩色摄影的一个简短而不完整的历史 1.2 OpenCV中的运动模型 2 使用增强相关系数最大化(ECC)的图像对齐 2.1 findTransformECC在OpenCV中的示 ...
- [OpenCV实战]44 使用OpenCV进行图像超分放大
图像超分辨率(Image Super Resolution)是指从低分辨率图像或图像序列得到高分辨率图像.图像超分辨率是计算机视觉领域中一个非常重要的研究问题,广泛应用于医学图像分析.生物识别.视频监 ...
- [OpenCV实战]50 用OpenCV制作低成本立体相机
本文主要讲述利用OpenCV制作低成本立体相机以及如何使用OpenCV创建3D视频,准确来说是模仿双目立体相机,我们通常说立体相机一般是指双目立体相机,就是带两个摄像头的那种(目就是指眼睛,双目就是两 ...
- [OpenCV实战]23 使用OpenCV获取高动态范围成像HDR
目录 1 背景 1.1 什么是高动态范围(HDR)成像? 1.2 高动态范围(HDR)成像如何工作? 2 代码 2.1 运行环境配置 2.2 读取图像和曝光时间 2.3 图像对齐 2.4 恢复相机响应 ...
- [OpenCV实战]9 使用OpenCV寻找平面图形的质心
目录 1 名词解释 2 在OpenCV中查找Blob质心的步骤 3 图像多个blob下的质心获取 4 参考 在中学,我们学习了几何的中各种平面图形.找到标准平面图形的中心(几何中心)比较容易,如圆形, ...
- [OpenCV实战]46 在OpenCV下应用图像强度变换实现图像对比度均衡
本文主要介绍基于图像强度变换算法来实现图像对比度均衡.通过图像对比度均衡能够抑制图像中的无效信息,使图像转换为更符合计算机或人处理分析的形式,以提高图像的视觉价值和使用价值.本文主要通过OpenCV ...
- [OpenCV实战]24 使用OpenCV进行曝光融合
目录 1 什么是曝光融合 2 曝光融合的原理 3 代码与结果 4 参考 本教程中,我们将了解使用OpenCV的Exposure Fusion(曝光融合). 1 什么是曝光融合 曝光融合是一种将使用不同 ...
- [OpenCV实战]52 在OpenCV中使用颜色直方图
颜色直方图是一种常见的图像特征,顾名思义颜色直方图就是用来反映图像颜色组成分布的直方图.颜色直方图的横轴表示像素值或像素值范围,纵轴表示该像素值范围内像素点的个数或出现频率.颜色直方图属于计算机视觉中 ...
- [OpenCV实战]34 使用OpenCV进行图像修复
目录 1 什么是图像修复 1.1 INPAINT_NS : Navier-Stokes based Inpainting 1.2 INPAINT_TELEA : Fast Marching Metho ...
随机推荐
- 没有使用IaC的DevOps系统都是耍流氓
作为现代软件工程的基础实践,基础设施即代码(Infrastructure as Code, IaC)是云原生.容器.微服务以及DevOps背后的底层逻辑.应该说,以上所有这些技术或者实践都是以基础设施 ...
- git记不住用户名跟密码,每次提交拉取都需要再次输入
问题:之前为了测试git提交的一个问题,选择不记住用户名跟密码,输入如下命令即可不记住 git credential-manager uninstall git update-git-for-wind ...
- 6.RabbitMQ系列之direct直连交换器
我们通过fanout exchange(扇型交换机)实现生产者发送一个消息,这个消息同时被传送给所有队列.但是有时我们不希望所有的消息都被所有队列接收,我们希望可以指定类型为a的消息只能被队列A接收, ...
- .NET 6学习笔记(4)——如何在.NET 6的Desktop App中使用Windows Runtime API
Windows Runtime API是当初某软为了区别Win32 API,力挺UWP而创建的另一套Windows 10专用的API集合.后来因为一些原因,UWP没火.为了不埋没很有价值的Window ...
- 小米MIUI禁止系统更新
删除downloaded_rom的文件夹,随便找一个文件(文件,不是文件夹),重名为downloaded_rom(是把一个文件重命名),这样系统后台偷偷下载时,就不知道该存放更新包的文件,就无法偷偷更 ...
- 35.ListSerializer详解
ListSerializer继承了BaseSerializer 一般情况下我们不直接使用ListSerializer ListSerializer会自动调用,序列化many=True的字段时,会自动调 ...
- 如何用webgl(three.js)搭建一个3D库房,3D仓库3D码头,3D集装箱,车辆定位,叉车定位可视化孪生系统——第十五课
序 又是快两个月没写随笔了,长时间不总结项目,不锻炼文笔,一开篇,多少都会有些生疏,不知道如何开篇,如何写下去.有点江郎才尽,黔驴技穷的感觉. 写随笔,通常三步走,第一步,搭建框架,先把你要写的内容框 ...
- linux 2021
常用安装 sudo apt install openssh-server # 安装ssh service ssh # 查看ssh服务的 基础 Debian 是Ubuntu的母板,有强大的包管理功能,使 ...
- c语言内存四区、数据存储范围和内存存储方向
(1)代码区通常是共享只读(代码无法修改)的,即可以被其他的程序调用,例如运行两个qq,除了数据不一样,代码都是一样的, 每次运行qq,都会将代码和数据加载到内存中,除了数据,每次加载的代码都是一样的 ...
- xshell取消置顶
现象:xshell置顶,导致无法正常浏览其他应用,文件等 原因分析:打开xshell时,触发其置顶快捷方式:Alt+A 解决建议:针对此问题,首先,可以从"查看栏"手动取消置顶:其 ...