理论
- 它是从图像中的一个位置获取像素并将它们定位在新图像中的另一个位置的过程。
- 为了完成映射过程,可能需要对非整数像素位置进行一些插值,因为源图像和目标图像之间不总是存在一对一的像素对应关系。
- 我们可以将每个像素位置(x,y)的重映射表达为:
- 其中g()是重映射的图像,f()是源图像,h(x,y)是对(x,y)进行操作的映射函数。
- 让我们以一个简单的例子来思考。 想象一下,我们有一个图像我,比方说,我们想要做一个重映射,这样:
- 会发生什么? 很容易看出图像会在x方向上翻转。 例如,考虑输入图像:
- 观察红色圆圈如何相对于x改变位置(考虑x水平方向):
- 在OpenCV中,函数cv :: remap提供了一个简单的重映射实现。
代码
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
using namespace cv;
Mat src, dst;
Mat map_x, map_y;
const char* remap_window = "Remap demo";
int ind = 0;
void update_map( void );
int main( int, char** argv )
{
src = imread( argv[1], 1 );
dst.create( src.size(), src.type() );
map_x.create( src.size(), CV_32FC1 );
map_y.create( src.size(), CV_32FC1 );
namedWindow( remap_window, WINDOW_AUTOSIZE );
for(;;)
{
int c = waitKey( 1000 );
if( (char)c == 27 )
{ break; }
update_map();
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0) );
// Display results
imshow( remap_window, dst );
}
return 0;
}
void update_map( void )
{
ind = ind%4;
for( int j = 0; j < src.rows; j++ )
{ for( int i = 0; i < src.cols; i++ )
{
switch( ind )
{
case 0:
if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 )
{
map_x.at<float>(j,i) = 2*( i - src.cols*0.25f ) + 0.5f ;
map_y.at<float>(j,i) = 2*( j - src.rows*0.25f ) + 0.5f ;
}
else
{ map_x.at<float>(j,i) = 0 ;
map_y.at<float>(j,i) = 0 ;
}
break;
case 1:
map_x.at<float>(j,i) = (float)i ;
map_y.at<float>(j,i) = (float)(src.rows - j) ;
break;
case 2:
map_x.at<float>(j,i) = (float)(src.cols - i) ;
map_y.at<float>(j,i) = (float)j ;
break;
case 3:
map_x.at<float>(j,i) = (float)(src.cols - i) ;
map_y.at<float>(j,i) = (float)(src.rows - j) ;
break;
} // end of switch
}
}
ind++;
}
解释
- 创建一些我们将使用的变量:
- 加载图片:
- 创建目标图像和两个映射矩阵(对于x和y)
- 创建一个窗口以显示结果
- 建立一个循环。 每1000毫秒我们更新我们的映射矩阵(mat_x和mat_y)并将它们应用于我们的源图像:
应用重映射的函数是cv :: remap。 我们给出以下论点:
(1)src:源图像
(2)dst:与src大小相同的目标图像
(3)map_x:x方向的映射函数。 它相当于h(i,j)的第一个分量
(4)map_y:与上面相同,但是在y方向上。 请注意,map_y和map_x都与src的大小相同
(5)INTER_LINEAR:用于非整数像素的插值类型。
(6)BORDER_CONSTANT:默认
- 更新映射矩阵:我们将执行4种不同的映射:
- 将图片缩小到一半大小并将其显示在中间:
- 将图像上下颠倒:
- 从左到右反射图像:
- b和c的组合:
- 这在以下代码段中表示。 这里,map_x表示h(i,j)的第一个坐标,map_y表示第二个坐标。
for( int j = 0; j < src.rows; j++ )
{ for( int i = 0; i < src.cols; i++ )
{
switch( ind )
{
case 0:
if( i > src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 )
{
map_x.at<float>(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ;
map_y.at<float>(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ;
}
else
{ map_x.at<float>(j,i) = 0 ;
map_y.at<float>(j,i) = 0 ;
}
break;
case 1:
map_x.at<float>(j,i) = i ;
map_y.at<float>(j,i) = src.rows - j ;
break;
case 2:
map_x.at<float>(j,i) = src.cols - i ;
map_y.at<float>(j,i) = j ;
break;
case 3:
map_x.at<float>(j,i) = src.cols - i ;
map_y.at<float>(j,i) = src.rows - j ;
break;
} // end of switch
}
}
ind++;
}
效果
- 编译完上面的代码后,您可以执行它作为参数给出图像路径。 例如,通过使用以下图像:
- 这是将它缩小到一半大小并使其居中的结果:
- 将其颠倒过来:
- 在x方向上反射它:
- 在两个方向上反映它: