OpenCV中的像素重映射原理及实战分析

news/2024/7/21 5:59:34 标签: opencv, 计算机视觉, 像素重映射, 图像处理

引言

映射是个数学术语,指两个元素的集之间元素相互“对应”的关系,为名词。映射,或者射影,在数学及相关的领域经常等同于函数。 基于此,部分映射就相当于部分函数,而完全映射相当于完全函数。

说的简单点,每个人都有一个名字,都有身份证号,人对应人名字,对应自己的身份证号,这种对应关系就叫映射。

 

一、什么是像素重映射

把一个图像中一个位置的像素放置到另一个图片指定位置的过程就是像素重映射

为了完成映射过程, 有必要获得一些插值为非整数像素坐标,因为源图像与目标图像的像素坐标不是一一对应的.

简单的说就是改变图片的位置(左,右,上,下,颠倒)

for example (举个栗子):g(x, y)=f(h(x,y))。 这里g()是目标图像,f()是原图像,h(x,y)是作用于(x,y)的映射方法函数。假设有一幅图像I,满足后面条件作重映射: h(x,y)=(I.cols - x,y)这个公式是有点绕哈,有些对数学不感冒的童鞋可以看一下这个图

7ebf7ae6da174ab1b2603896f7a83982.jpeg

没错,就是轴对称,左右翻转。 这就是数学的魅力。我们一起来体验一下。

 

二、像素重映射API — remap()

下面是函数原型:

cv::remap  (   
          InputArray  src,
          OutputArray  dst,
          InputArray  map1,
          InputArray  map2,
          int    interpolation,
          int    borderMode = BORDER_CONSTANT,
          const Scalar    borderValue = Scalar()
)

函数各个参数的解释:

第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。

第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型

第三个参数,InputArray类型的map1,它有两种可能的表示对象。表示点(x,y)的第一个映射。表示CV_16SC2 , CV_32FC1 或CV_32FC2类型的X值。

第四个参数,InputArray类型的map2,同样,它也有两种可能的表示对象,而且他是根据map1来确定表示那种对象。若map1表示点(x,y)时。这个参数不代表任何值。表示CV_16UC1 , CV_32FC1类型的Y值(第二个值)。

第五个参数,int类型的interpolation,插值方式,之前的resize( )函数中有讲到,需要注意,resize( )函数中提到的INTER_AREA插值方式在这里是不支持的,所以可选的插值方式如下:INTER_NEAREST - 最近邻插值INTER_LINEAR – 双线性插值(默认值)INTER_CUBIC – 双三次样条插值(逾4×4像素邻域内的双三次插值)INTER_LANCZOS4 -Lanczos插值(逾8×8像素邻域的Lanczos插值)

第六个参数,int类型的borderMode,边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。

第七个参数,const Scalar&类型的borderValue,当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。
 

三、实战

参数函数

首先我们要先考虑,我们要做的情况有如下四种:缩小(行与列均为原来的1/2),左右翻转,上下翻转,中心旋转。下面的图表示的就是四种变换模式:

e52d377734ff489e911cf7251e4ba53a.jpeg

最简单的是翻转了,行不变,第一列跟最后一列转换,第二列跟倒数第二列转换……这样就实现了左右翻转。同理,列不变,行转换,实现上下翻转。如果上下左右都翻转,那就是旋转180°,也就是中心旋转啦。

还有一个就是图像缩小,图像缩小就是将长跟宽变换为原来的1/2。所以只在图像的1/4处到3/4处有新图像。将图像范围控制在0原图像的0.25-0.75之间,其他的全部为0;设置图像的x和y方向的映射。对于x方向,是列的映射。图像的每个像素点,减去原图像的1/4,再×2,就表示图像缩小1/2.不过,经过我自己的实践发现,图像宽,高缩小一半不能再后面+0.5,应该是列不加,行-0.25。
 

void updata_map() {
  for (int row = 0; row < img.rows; row++)
  {
    for (int col = 0; col < img.cols; col++)
    {
      switch (index)
      {
        //index = 0 ,图像的行跟列为为原来的1/2。
        //index = 1,为左右翻转(列变换,行不变)
        //index = 2,为上下翻转(行变换,列不变)
        //index = 3,为中心旋转
        
      case 0:
        if (col > (img.cols*0.25) && col<(img.cols*0.75) && row>(img.rows*0.25) && row < (img.rows*0.75)) {
          map_x.at<float>(row, col) = 2 * (col - (img.cols*0.25));
          map_y.at<float>(row, col) = 2 * (row - (img.rows*0.25) - 0.25);
        }
        else
        {
          map_x.at<float>(row, col) = 0;
          map_y.at<float>(row, col) = 0;
        }
        break;
      case 1:
        map_x.at<float>(row, col) = (img.cols - col - 1);
        map_y.at<float>(row, col) = row;
        break;
      case 2:
        map_x.at<float>(row, col) = col;
        map_y.at<float>(row, col) = (img.rows - row - 1);
        break;
      case 3:
        map_x.at<float>(row, col) = (img.cols - col - 1);
        map_y.at<float>(row, col) = (img.rows - row - 1);
        break;

      default:
        break;
     }
   }
}

效果如下图所示:

ecb7d84752b242a18635278c4568cb24.jpeg

x和y都加上0.5

83f4b3f5f12947faaccc9ea5f27b8b79.jpeg 

x和y都不加0.5 

2c54098148c644aa9f0c0f4f2622c1ad.jpeg 

 x不加,y-0.25

 

完整代码

#define INPUT_TITLE "input image"
#define OUTPUT_TITLE "remap image"

#include<iostream>
#include<opencv2\opencv.hpp>

using namespace std;
using namespace cv;

Mat img, src;//img 输入图像 ; src 最终输出的图像
Mat map_x, map_y;
int index = 0;
void updata_map();

int main() {
  
  img = imread("D:/测试程序/image/circle1.bmp");
  if (!img.data)
  {
    cout << "ERROR : could not load image.";
    return -1;
  }
  namedWindow(INPUT_TITLE, CV_WINDOW_AUTOSIZE);
  namedWindow(OUTPUT_TITLE, CV_WINDOW_AUTOSIZE);

  imshow(INPUT_TITLE, img);

  //建立映射表
  map_x.create(img.size(), CV_32FC1);
  map_y.create(img.size(), CV_32FC1);

  int c = 0;
  while (true)
  {
    c = waitKey(500);
    index = c % 4;
    if ((char)c == 27)
    {
      break;
    }
    updata_map();
    remap(img, src, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 255));
    imshow(OUTPUT_TITLE, src);
  }

  
  return 0;
}

void updata_map() {
  for (int row = 0; row < img.rows; row++)
  {
    for (int col = 0; col < img.cols; col++)
    {
      switch (index)
      {
        //index = 0 ,图像的行跟列为为原来的1/2。
        //index = 1,为左右翻转(列变换,行不变)
        //index = 2,为上下翻转(行变换,列不变)
        //index = 3,为中心旋转
        
      case 0:
        if (col > (img.cols*0.25) && col<(img.cols*0.75) && row>(img.rows*0.25) && row < (img.rows*0.75)) {
          map_x.at<float>(row, col) = 2 * (col - (img.cols*0.25));
          map_y.at<float>(row, col) = 2 * (row - (img.rows*0.25)-0.25);
        }
        else
        {
          map_x.at<float>(row, col) = 0;
          map_y.at<float>(row, col) = 0;
        }
        break;
      case 1:
        map_x.at<float>(row, col) = (img.cols - col - 1);
        map_y.at<float>(row, col) = row;
        break;
      case 2:
        map_x.at<float>(row, col) = col;
        map_y.at<float>(row, col) = (img.rows - row - 1);
        break;
      case 3:
        map_x.at<float>(row, col) = (img.cols - col - 1);
        map_y.at<float>(row, col) = (img.rows - row - 1);
        break;

      default:
        break;
      }
    }
  }
}

 效果图

b65783ef2f644e729f04ce58acfede70.jpeg

a474b1884bba48cab6b4693a5049a00d.jpeg

4ed94ad2f3b449f7952ac420185123d0.jpeg

ea5589bfb59b434f982544e01950cd6f.jpeg

 


http://www.niftyadmin.cn/n/5187657.html

相关文章

ClickHouse UDF 运行速度慢问题

一、环境版本 环境版本docker clickhouse22.3.10.22 二、UDF运行速度时快时慢 udf配置文件xxx_function.xml type- 可执行类型。如果type设置为executable则启动单个命令。如果设置为&#xff0c;executable_pool则创建命令池。 pool_size- 命令池的大小。可选参数&#xff…

C/C++---------------LeetCode第1189. “气球” 的最大数量

气球的最大数量 题目及要求统计法在main内使用 题目及要求 给你一个字符串 text&#xff0c;你需要使用 text 中的字母来拼凑尽可能多的单词 “balloon”&#xff08;气球&#xff09;。 字符串 text 中的每个字母最多只能被使用一次。请你返回最多可以拼凑出多少个单词 “ba…

【云原生-Kurbernetes篇】K8s的存储卷/数据卷+PV与PVC

这是一个目录标题 一、Kurbernetes中的存储卷1.1 为什么需要存储卷&#xff1f;1.2 存储卷概述1.2.1 简介1.2.2 volume字段 1.3 常用的存储卷类型1.3.1 emptyDir&#xff08;临时存储卷&#xff09;1.3.2 hostPath&#xff08;节点存储卷&#xff09;1.3.3 nfs1.3.4 cephfs 二、…

单因素方差分析(one-way analysis of variance)【R实现,用例题帮你更好理解】

1, data00403 thorax 是四种昆虫的胸长&#xff0c;采用合适的统计方法&#xff0c;检验不同种类的 胸长有无差异&#xff1f;如果有差异&#xff0c;哪种之间有差异&#xff1f;&#xff08;说明选用依据&#xff09; #看样本量&#xff0c;n1n2n3n428 #看方差齐性。各组之间符…

Golang抓包:实现网络数据包捕获与分析

介绍 在网络通信中&#xff0c;网络数据包是信息传递的基本单位。抓包是一种监控和分析网络流量的方法&#xff0c;用于获取网络数据包并对其进行分析。在Golang中&#xff0c;我们可以借助现有的库来实现抓包功能&#xff0c;进一步对网络数据进行分析和处理。 本文将介绍如…

01Urllib

1.什么是互联网爬虫&#xff1f; 如果我们把互联网比作一张大的蜘蛛网&#xff0c;那一台计算机上的数据便是蜘蛛网上的一个猎物&#xff0c;而爬虫程序就是一只小蜘蛛&#xff0c;沿着蜘蛛网抓取自己想要的数据 解释1&#xff1a;通过一个程序&#xff0c;根据Url(http://www.…

2. zk集群部署

简介 上一篇文章我们已经把环境准备好了&#xff0c;jdk也配置好了&#xff0c;下面我们开始把zk部署起来 hadoop环境准备 创建zk用户 useradd zk -d /home/zk echo "1q1w1e1r" | passwd --stdin zk上传zk包 拷贝zk包到/home/zk目录,这里的zk版本为 3.6.3 scp…

WPF实现右键菜单

在WPF中&#xff0c;创建上下文菜单&#xff08;通常称为“右键菜单”&#xff09;是通过使用ContextMenu控件来实现的。你可以在XAML中声明上下文菜单&#xff0c;并将其关联到任何FrameworkElement。以下是如何在WPF中实现上下文菜单的基本步骤&#xff1a; 1. 在XAML中定义…