Goal
在本教程中,您将学习如何:
a. 使用 OpenCV 函数 cv::remap 来实现简单的重新映射例程。
Theory
什么是重映射?
- 这是从图像中的一个位置获取像素并将它们定位到新图像中的另一个位置的过程。
- 为了完成映射过程,可能需要对非整数像素位置进行一些插值,因为源图像和目标图像之间并不总是存在一对一的像素对应关系。
- 我们可以将每个像素位置 (x,y) 的重映射表示为:
其中 g() 是重新映射的图像,f() 是源图像,h(x,y) 是对 (x,y) 进行操作的映射函数。
- 让我们想一个简单的例子。 想象一下,我们有一个图像 I,比如说,我们想做一个remap ,这样:
会发生什么? 很容易看出图像会在 x 方向翻转。 例如,考虑输入图像:
观察红色圆圈如何改变相对于 x 的位置(考虑 x 水平方向):
在 OpenCV 中,函数 cv::remap 提供了一个简单的重映射实现。
Code
这个程序有什么作用?
加载图像
每秒对图像应用 4 种不同重映射过程中的 1 种,并在窗口中无限期地显示它们。
等待用户退出程序
教程代码如下所示。 你也可以从这里下载
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace cv;
void update_map( int &ind, Mat &map_x, Mat &map_y );
int main(int argc, const char** argv)
{
CommandLineParser parser(argc, argv, "{@image |chicky_512.png|input image name}");
std::string filename = parser.get<std::string>(0);
Mat src = imread( samples::findFile( filename ), IMREAD_COLOR );
if (src.empty())
{
std::cout << "Cannot read image: " << filename << std::endl;
return -1;
}
Mat dst(src.size(), src.type());
Mat map_x(src.size(), CV_32FC1);
Mat map_y(src.size(), CV_32FC1);
const char* remap_window = "Remap demo";
namedWindow( remap_window, WINDOW_AUTOSIZE );
int ind = 0;
for(;;)
{
update_map(ind, map_x, map_y);
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0) );
imshow( remap_window, dst );
char c = (char)waitKey( 1000 );
if( c == 27 )
{
break;
}
}
return 0;
}
void update_map( int &ind, Mat &map_x, Mat &map_y )
{
for( int i = 0; i < map_x.rows; i++ )
{
for( int j = 0; j < map_x.cols; j++ )
{
switch( ind )
{
case 0:
if( j > map_x.cols*0.25 && j < map_x.cols*0.75 && i > map_x.rows*0.25 && i < map_x.rows*0.75 )
{
map_x.at<float>(i, j) = 2*( j - map_x.cols*0.25f ) + 0.5f;
map_y.at<float>(i, j) = 2*( i - map_x.rows*0.25f ) + 0.5f;
}
else
{
map_x.at<float>(i, j) = 0;
map_y.at<float>(i, j) = 0;
}
break;
case 1:
map_x.at<float>(i, j) = (float)j;
map_y.at<float>(i, j) = (float)(map_x.rows - i);
break;
case 2:
map_x.at<float>(i, j) = (float)(map_x.cols - j);
map_y.at<float>(i, j) = (float)i;
break;
case 3:
map_x.at<float>(i, j) = (float)(map_x.cols - j);
map_y.at<float>(i, j) = (float)(map_x.rows - i);
break;
default:
break;
} // end of switch
}
}
ind = (ind+1) % 4;
}
Explanation
- Load an image:
Mat src = imread( samples::findFile( filename ), IMREAD_COLOR );
if (src.empty())
{
std::cout << "Cannot read image: " << filename << std::endl;
return -1;
}
- Create the destination image and the two mapping matrices (for x and y )
创建目标图像和两个映射矩阵(对于 x 和 y )
Mat dst(src.size(), src.type());
Mat map_x(src.size(), CV_32FC1);
Mat map_y(src.size(), CV_32FC1);
- 创建一个窗口来显示结果
const char* remap_window = "Remap demo";
namedWindow( remap_window, WINDOW_AUTOSIZE );
- Establish a loop. Each 1000 ms we update our mapping matrices (mat_x and mat_y) and apply them to our source image:
建立一个循环。 每 1000 毫秒我们更新我们的映射矩阵(mat_x 和 mat_y)并将它们应用到我们的源图像:
int ind = 0;
for(;;)
{
update_map(ind, map_x, map_y);
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0) );
imshow( remap_window, dst );
char c = (char)waitKey( 1000 );
if( c == 27 )
{
break;
}
}
应用重新映射的函数是 cv::remap 。 我们给出以下参数:
src:源图像
dst:与 src 大小相同的目标图像
map_x:x方向的映射函数。 它等价于 h(i,j) 的第一个分量
map_y:同上,但在 y 方向。 请注意,map_y 和 map_x 的大小都与 src 相同
INTER_LINEAR:用于非整数像素的插值类型。 这是默认设置。
BORDER_CONSTANT:默认
我们如何更新我们的映射矩阵 mat_x 和 mat_y? 继续阅读:
更新映射矩阵:我们将执行 4 种不同的映射
- 将图片缩小到一半大小,并将其显示在中间:
对于所有对 (i,j) 使得:
- 把图像倒过来:
- 从左到右反射图像:
- 2 和 3 的组合:
这在以下代码段中表示。 这里,map_x 表示 h(i,j) 的第一个坐标,map_y 表示第二个坐标。
void update_map( int &ind, Mat &map_x, Mat &map_y )
{
for( int i = 0; i < map_x.rows; i++ )
{
for( int j = 0; j < map_x.cols; j++ )
{
switch( ind )
{
case 0:
if( j > map_x.cols*0.25 && j < map_x.cols*0.75 && i > map_x.rows*0.25 && i < map_x.rows*0.75 )
{
map_x.at<float>(i, j) = 2*( j - map_x.cols*0.25f ) + 0.5f;
map_y.at<float>(i, j) = 2*( i - map_x.rows*0.25f ) + 0.5f;
}
else
{
map_x.at<float>(i, j) = 0;
map_y.at<float>(i, j) = 0;
}
break;
case 1:
map_x.at<float>(i, j) = (float)j;
map_y.at<float>(i, j) = (float)(map_x.rows - i);
break;
case 2:
map_x.at<float>(i, j) = (float)(map_x.cols - j);
map_y.at<float>(i, j) = (float)i;
break;
case 3:
map_x.at<float>(i, j) = (float)(map_x.cols - j);
map_y.at<float>(i, j) = (float)(map_x.rows - i);
break;
default:
break;
} // end of switch
}
}
ind = (ind+1) % 4;
}
Result
1. 编译上面的代码后,您可以执行它,将图像路径作为参数。 例如,通过使用以下图像:
2. 这是将其缩小到一半并居中的结果:
3. 把它颠倒过来:
4. 在 x 方向反射它:
- 在两个方向上反射它: