.. _remap: Remapping 重映射 ******************* 目标 ==== 本教程向你展示如何使用OpenCV函数 :remap:`remap <>` 来实现简单重映射. 理论 ====== 重映射是什么意思? ------------------ * 把一个图像中一个位置的像素放置到另一个图片指定位置的过程. * 为了完成映射过程, 有必要获得一些插值为非整数像素坐标,因为源图像与目标图像的像素坐标不是一一对应的. * 我们通过重映射来表达每个像素的位置 :math:`(x,y)` : .. math:: g(x,y) = f ( h(x,y) ) 这里 :math:`g()` 是目标图像, :math:`f()` 是源图像, :math:`h(x,y)` 是作用于 :math:`(x,y)` 的映射方法函数. * 让我们来思考一个快速的例子. 想象一下我们有一个图像 :math:`I` , 我们想满足下面的条件作重映射: .. math:: h(x,y) = (I.cols - x, y ) 会发生什么? 图像会按照 :math:`x` 轴方向发生翻转. 例如, 源图像如下: .. image:: images/Remap_Tutorial_Theory_0.jpg :alt: Original test image :width: 120pt :align: center 看到红色圈关于 x 的位置改变( :math:`x` 轴水平翻转): .. image:: images/Remap_Tutorial_Theory_1.jpg :alt: Original test image :width: 120pt :align: center * 通过 OpenCV 的函数 :remap:`remap <>` 提供一个简单的重映射实现. 代码 ==== #. **本程序做什么?** * 装载一幅图像. * 程序按秒循环, 在一个窗口中顺序出现4种重映射过程对相同的图像. * 等待用户按 ‘ESC’ 键退出程序。 #. 下面是本教程代码. 你也可以从 `这里 `_ 下载。 .. code-block:: cpp #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include #include using namespace cv; /// Global variables Mat src, dst; Mat map_x, map_y; char* remap_window = "Remap demo"; int ind = 0; /// Function Headers void update_map( void ); /** * @function main */ int main( int argc, char** argv ) { /// Load the image src = imread( argv[1], 1 ); /// Create dst, map_x and map_y with the same size as src: dst.create( src.size(), src.type() ); map_x.create( src.size(), CV_32FC1 ); map_y.create( src.size(), CV_32FC1 ); /// Create window namedWindow( remap_window, CV_WINDOW_AUTOSIZE ); /// Loop while( true ) { /// Each 1 sec. Press ESC to exit the program int c = waitKey( 1000 ); if( (char)c == 27 ) { break; } /// Update map_x & map_y. Then apply remap update_map(); remap( src, dst, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) ); /// Display results imshow( remap_window, dst ); } return 0; } /** * @function update_map * @brief Fill the map_x and map_y matrices with 4 types of mappings */ 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(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ; map_y.at(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ; } else { map_x.at(j,i) = 0 ; map_y.at(j,i) = 0 ; } break; case 1: map_x.at(j,i) = i ; map_y.at(j,i) = src.rows - j ; break; case 2: map_x.at(j,i) = src.cols - i ; map_y.at(j,i) = j ; break; case 3: map_x.at(j,i) = src.cols - i ; map_y.at(j,i) = src.rows - j ; break; } // end of switch } } ind++; } 说明 =========== #. 首先准备程序用到的变量: .. code-block:: cpp Mat src, dst; Mat map_x, map_y; char* remap_window = "Remap demo"; int ind = 0; #. 加载一幅图像: .. code-block:: cpp src = imread( argv[1], 1 ); #. 创建目标图像和两个映射矩阵.( x 和 y ) .. code-block:: cpp dst.create( src.size(), src.type() ); map_x.create( src.size(), CV_32FC1 ); map_y.create( src.size(), CV_32FC1 ); #. 创建一个窗口用于展示结果. .. code-block:: cpp namedWindow( remap_window, CV_WINDOW_AUTOSIZE ); #. 建立一个间隔1000毫秒的循环,每次循环执行更新映射矩阵参数并对源图像进行重映射处理(使用 *mat_x* 和 *mat_y*),然后把更新后的目标图像显示出来: .. code-block:: cpp while( true ) { /// Each 1 sec. Press ESC to exit the program int c = waitKey( 1000 ); if( (char)c == 27 ) { break; } /// Update map_x & map_y. Then apply remap update_map(); remap( src, dst, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) ); /// Display results imshow( remap_window, dst ); } 上面用到的重映射函数 :remap:`remap <>`. 参数说明: * **src**: 源图像 * **dst**: 目标图像,与 *src* 相同大小 * **map_x**: x方向的映射参数. 它相当于方法 :math:`h(i,j)` 的第一个参数 * **map_y**: y方向的映射参数. 注意 *map_y* 和 *map_x* 与 *src* 的大小一致。 * **CV_INTER_LINEAR**: 非整数像素坐标插值标志. 这里给出的是默认值(双线性插值). * **BORDER_CONSTANT**: 默认 如何更新重映射矩阵 *mat_x* 和 *mat_y*? 请继续看: #. **更新重映射矩阵:** 我们将分别使用4种不同的映射: a. 图像宽高缩小一半,并显示在中间: .. math:: h(i,j) = ( 2*i - src.cols/2 + 0.5, 2*j - src.rows/2 + 0.5) 所有成对的参数 :math:`(i,j)` 处理后都符合: :math:`\dfrac{src.cols}{4} src.cols*0.25 && i < src.cols*0.75 && j > src.rows*0.25 && j < src.rows*0.75 ) { map_x.at(j,i) = 2*( i - src.cols*0.25 ) + 0.5 ; map_y.at(j,i) = 2*( j - src.rows*0.25 ) + 0.5 ; } else { map_x.at(j,i) = 0 ; map_y.at(j,i) = 0 ; } break; case 1: map_x.at(j,i) = i ; map_y.at(j,i) = src.rows - j ; break; case 2: map_x.at(j,i) = src.cols - i ; map_y.at(j,i) = j ; break; case 3: map_x.at(j,i) = src.cols - i ; map_y.at(j,i) = src.rows - j ; break; } // end of switch } } ind++; } 结果 ====== #. 上面的代码编译后, 运行时给一个图片路径参数. 例如,使用下面的图片: .. image:: images/Remap_Tutorial_Original_Image.jpg :alt: Original test image :width: 250pt :align: center #. 图像宽高缩小一半,并显示在中间: .. image:: images/Remap_Tutorial_Result_0.jpg :alt: Result 0 for remapping :width: 250pt :align: center #. 图像上下颠倒: .. image:: images/Remap_Tutorial_Result_1.jpg :alt: Result 0 for remapping :width: 250pt :align: center #. 图像左右颠倒: .. image:: images/Remap_Tutorial_Result_2.jpg :alt: Result 0 for remapping :width: 250pt :align: center #. 两个方向同时颠倒: .. image:: images/Remap_Tutorial_Result_3.jpg :alt: Result 0 for remapping :width: 250pt :align: center 翻译者 ================= opencvzy@ `OpenCV中文网站 `_