|
详解OpenCV的坐标重映射函数remap()的两种使用方法并附使用它得到图像的水平镜像和垂直镜像的示例代码
OpenCV的函数remap()可以实现图像像素坐标的重映射。
什么叫图像像素坐标的重映射?
注意:“重映射”中的“重”字的含义是“重新”的意思。
下面这张图显示了什么叫像素坐标的重映射。
![](http://pic1.hhai.cc/pic1/2022/2022-12/002/11.png)
上图中:
坐标为(1,2)的点的坐标变成了(7,8);
坐标为(1,3)的点的坐标变成了(7,9);
坐标为(1,4)的点的坐标变成了(7,10);
这就是坐标重映射的一个简单例子。
接下来说说OpenCV的函数remap()。
函数remap()的原型如下:
- C++: void remap(InputArray src,
- OutputArray dst,
- InputArray map1,
- InputArray map2,
- int interpolation,
- int borderMode=BORDER_CONSTANT,
- const Scalar& borderValue=Scalar())
复制代码
下面介绍各个参数的意义:
src---输入图像。
dst---目标图像,它和映射表map1的尺寸相同,数据类型与src相同。
map1---映射表map1,映射表map1可以是一通道的矩阵,也可以是二通道的矩阵。
当它为一通道的矩阵时,映射表map1中坐标为(x,y)的点的值代表把原图像中坐标为(x,y)的点进行重映射后其新坐标在x轴方向上的坐标。举个简单的例子,假如map1中坐标为(2,3)的点的值为5,则表示进行重映射后,原图中坐标为(2,3)的点的新坐标的x轴坐标变为5。当映射表map1为一通道时,其type只能为CV_32FC1。
当它为二通道矩阵时,映射表map1中坐标为(x,y)的点的两个通道值代表把原图像中坐标为(x,y)的点进行重映射后的新坐标值。举个简单的例子,假如map1中坐标为(2,3)的点的0通道为5,1通道值为7,则表示进行重映射后,原图中坐标为(2,3)的点的新坐标为(5,7)。当映射表map1为二通道时,其type可以为CV_16SC2或CV_32FC2。
正是由于映射表map1可以为一通道或两通道,所以我在本文标题中说函数remap()有两种使用方法。
map2---映射表map2,映射表map2只可以是一通道的矩阵。其意义与映射表map1相同,只是它里面存储的是映射后新坐标在y方向上的坐标。其type只能为CV_32FC1(虽然官方文档上说它的type可以为CV_16UC1,但是经实测是不行的)。当映射表map1为二通道矩阵时,map2为空矩阵,即按下面这句话定义:
interpolation---这个参数用于选择插值方式,可选的插值方式如下:
![](http://pic1.hhai.cc/pic1/2022/2022-12/002/12.png)
注意:上图中用红线杠了的是不能选的插值方式。
问:函数remap()为什么会牵涉到插值?
答:因为输出图像的尺寸并不一定和原图像尺寸相同,而与映射表的尺寸相同。举个例子来说,原图像为100×50的图像,而映射表是120×60的矩阵,那输出图像的尺寸也就是120×60,可见比原图扩大了,也就是说多出来一些像素点,那多出来的像素点的值怎么取呢?这就需要作插值运算来得到了。
borderMode--边界扩展模式,默认值为BORDER_CONSTANT。为什么函数remap()会牵涉到边界的扩展处理,因为在插值的时候我们需要用到窗口矩阵,一旦涉汲到窗口矩阵参与运算,就要牵涉到边界的扩展处理了。详情见我的另一篇博文:https://www.hhai.cc/thread-178-1-1.html
borderValue---当边界扩展模式为BORDER_CONSTANT时,是需要指定一个常数的。borderValue的默认值为0。
下面是映射表map1为单通道时实现图像水平镜像和垂直镜像的源码:
源码中用到的图像下载链接:
链接:https://pan.baidu.com/s/1rmeL7Pz0AzM88iosUmjPFA 提取码:6pkm
[C++] 纯文本查看 复制代码
//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
cv::Mat srcImage = cv::imread("F:/material/images/P0038-Four_colors.bmp");
if (!srcImage.data)
return -1;
// 输出矩阵定义
cv::Mat resultImage(srcImage.size(), srcImage.type());
// X与Y方向矩阵
cv::Mat xMapImage(srcImage.size(), CV_32FC1);
cv::Mat yMapImage(srcImage.size(), CV_32FC1);
int rows = srcImage.rows;
int cols = srcImage.cols;
for (int j = 0; j < rows; j++)
{
for (int i = 0; i < cols; i++)
{
//下面这句代码实现原图像在水平方向上的镜像
xMapImage.at<float>(j, i) = cols - i;
//下面这句代码实现原图像在垂直方向上的镜像
yMapImage.at<float>(j, i) = rows - j;
}
}
// 重映射操作
remap(srcImage, resultImage, xMapImage, yMapImage,
CV_INTER_LINEAR, cv::BORDER_CONSTANT,
cv::Scalar(0, 0, 0));
// 输出结果
cv::imshow("srcImage", srcImage);
cv::imshow("resultImage", resultImage);
cv::waitKey(0);
return 0;
}
运行结果如下:
![](http://pic1.hhai.cc/pic1/2022/2022-12/002/13.png)
从上面可以看出,代码实际了原图像的水平镜像和垂直镜像。
下面是映射表map1为双通道时实现图像水平镜像的源码:
源码中用到的图像下载链接:
链接:https://pan.baidu.com/s/1rmeL7Pz0AzM88iosUmjPFA;提取码:6pkm
[C++] 纯文本查看 复制代码
//出处:昊虹AI笔记网(hhai.cc)
//用心记录计算机视觉和AI技术
//博主微信/QQ 2487872782
//QQ群 271891601
//欢迎技术交流与咨询
//OpenCV版本 OpenCV3.0
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
cv::Mat srcImage = cv::imread("F:/material/images/P0038-Four_colors.bmp");
if (!srcImage.data)
return -1;
// 定义输出矩阵
cv::Mat resultImage;
cv::Mat Map1(srcImage.size(), CV_32FC2);//map1为双通道矩阵
cv::Mat Map2;//当map1为双通道矩阵时,虽然不需要map2参与运算,但是也要初始化一个空的map2
int rows = srcImage.rows;
int cols = srcImage.cols;
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
//下面这两句代码实现原图像在水平方向上的镜像
Map1.at<cv::Vec2f>(i, j)[0] = cols - j;
Map1.at<cv::Vec2f>(i, j)[1] = i;
}
}
// 重映射操作
remap(srcImage, resultImage, Map1, Map2, CV_INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));
// 输出结果
cv::imshow("srcImage", srcImage);
cv::imshow("resultImage", resultImage);
cv::waitKey(0);
return 0;
}
![](http://pic1.hhai.cc/pic1/2022/2022-12/002/14.png)
从上面的运行结果中可以看出,代码实现了原图像的水平镜像。
|
|