图像处理:变换——重映射 OpenCV v4.8.0

上一个教程使用广义巴拉德和霍夫变换检测物体

下一个教程仿射变换

原作者Ana Huamán
兼容性OpenCV >= 3.0

目标

在本教程中,您将学习如何

a. 使用 OpenCV 函数 cv::remap 实现简单的重映射例程。

理论

什么是重映射?

  • 它是从图像中的一个位置提取像素,并将其定位到新图像中的另一个位置的过程。
  • 为了完成映射过程,可能需要对非整数像素位置进行一些插值,因为源图像和目标图像之间并不总是一一对应的。
  • 我们可以将每个像素位置(x,y)的重映射表示为

g ( x , y ) = f ( h ( x , y ) ) g\left( x,y \right) =f\left( h\left( x,y \right) \right) g(x,y)=f(h(x,y))

其中,g() 是重映射后的图像,f() 是源图像,h(x,y) 是作用于 (x,y) 的映射函数。

  • 让我们举个简单的例子。想象一下,我们有一幅图像 I,比如说,我们想重新映射它,那么

h ( x , y ) = ( I . c o l s − x , y ) h\left( x,y \right) =\left( I.cols-x,y \right) h(x,y)=(I.colsx,y)

结果会怎样?很容易看出,图像会向 x 方向翻转。例如,请看输入图像

在这里插入图片描述

观察红色圆圈相对于 x 的位置是如何变化的(将 x 视为水平方向):

在这里插入图片描述

  • 在 OpenCV 中,函数 cv::remap 提供了一个简单的重映射实现。

代码

  • 这个程序要做什么?
    • 加载图像
    • 每秒对图像应用 4 种不同重映射过程中的一种,并在窗口中无限期显示。
    • 等待用户退出程序

C++
教程代码如下所示。您也可以从此处下载

#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;
 } // 切换结束
 }
 }
 ind = (ind+1) % 4;
}

Java
教程代码如下所示。您也可以从此处下载

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class Remap {
 private Mat mapX = new Mat();
 private Mat mapY = new Mat();
 private Mat dst = new Mat();
 private int ind = 0;
 private void updateMap() {
 float buffX[] = new float[(int) (mapX.total() * mapX.channels())];
 mapX.get(0, 0, buffX);
 float buffY[] = new float[(int) (mapY.total() * mapY.channels())];
 mapY.get(0, 0, buffY);
 for (int i = 0; i < mapX.rows(); i++) {
 for (int j = 0; j < mapX.cols(); j++) {
 switch (ind) {
 case 0:
 if( j > mapX.cols()*0.25 && j < mapX.cols()*0.75 && i > mapX.rows()*0.25 && i < mapX.rows()*0.75 ) {
 buffX[i*mapX.cols() + j] = 2*( j - mapX.cols()*0.25f ) + 0.5f;
 buffY[i*mapY.cols() + j] = 2*( i - mapX.rows()*0.25f ) + 0.5f;
 } else {
 buffX[i*mapX.cols() + j] = 0;
 buffY[i*mapY.cols() + j] = 0;
 }
 break;
 case 1:
 buffX[i*mapX.cols() + j] = j;
 buffY[i*mapY.cols() + j] = mapY.rows() - i;
 break;
 case 2:
 buffX[i*mapX.cols() + j] = mapY.cols() - j;
 buffY[i*mapY.cols() + j] = i;
 break;
 case 3:
 buffX[i*mapX.cols() + j] = mapY.cols() - j;
 buffY[i*mapY.cols() + j] = mapY.rows() - i;
 break;
 default:
 break;
 }
 }
 }
 mapX.put(0, 0, buffX);
 mapY.put(0, 0, buffY);
 ind = (ind+1) % 4;
 }
 public void run(String[] args) {
 String filename = args.length > 0 ? args[0] : "../data/chicky_512.png";
 Mat src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_COLOR);
 if (src.empty()) {
 System.err.println("Cannot read image: " + filename);
 System.exit(0);
 }
 mapX = new Mat(src.size(), CvType.CV_32F);
 mapY = new Mat(src.size(), CvType.CV_32F);
 final String winname = "Remap demo";
 HighGui.namedWindow(winname, HighGui.WINDOW_AUTOSIZE);
 for (;;) {
 updateMap();
 Imgproc.remap(src, dst, mapX, mapY, Imgproc.INTER_LINEAR);
 HighGui.imshow(winname, dst);
 if (HighGui.waitKey(1000) == 27) {
 break;
 }
 }
 System.exit(0);
 }
}
public class RemapDemo {
 public static void main(String[] args) {
 // 加载本地 OpenCV 库
 System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
 new Remap().run(args);
 }
}

Python
教程代码如下所示。您也可以从此处下载

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
def update_map(ind, map_x, map_y):
 if ind == 0:
 for i in range(map_x.shape[0]):
 for j in range(map_x.shape[1]):
 if j > map_x.shape[1]*0.25 and j < map_x.shape[1]*0.75 and i > map_x.shape[0]*0.25 and i < map_x.shape[0]*0.75:
 map_x[i,j] = 2 * (j-map_x.shape[1]*0.25) + 0.5
 map_y[i,j] = 2 * (i-map_y.shape[0]*0.25) + 0.5
 else:
 map_x[i,j] = 0
 map_y[i,j] = 0
 elif ind == 1:
 for i in range(map_x.shape[0]):
 map_x[i,:] = [x for x in range(map_x.shape[1])]
 for j in range(map_y.shape[1]):
 map_y[:,j] = [map_y.shape[0]-y for y in range(map_y.shape[0])]
 elif ind == 2:
 for i in range(map_x.shape[0]):
 map_x[i,:] = [map_x.shape[1]-x for x in range(map_x.shape[1])]
 for j in range(map_y.shape[1]):
 map_y[:,j] = [y for y in range(map_y.shape[0])]
 elif ind == 3:
 for i in range(map_x.shape[0]):
 map_x[i,:] = [map_x.shape[1]-x for x in range(map_x.shape[1])]
 for j in range(map_y.shape[1]):
 map_y[:,j] = [map_y.shape[0]-y for y in range(map_y.shape[0])]
parser = argparse.ArgumentParser(description='Code for Remapping tutorial.')
parser.add_argument('--input', help='Path to input image.', default='chicky_512.png')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input), cv.IMREAD_COLOR)
if src is None:
 print('Could not open or find the image: ', args.input)
 exit(0)
map_x = np.zeros((src.shape[0], src.shape[1]), dtype=np.float32)
map_y = np.zeros((src.shape[0], src.shape[1]), dtype=np.float32)
window_name = 'Remap demo'
cv.namedWindow(window_name)
ind = 0
while True:
 update_map(ind, map_x, map_y)
 ind = (ind + 1) % 4
 dst = cv.remap(src, map_x, map_y, cv.INTER_LINEAR)
 cv.imshow(window_name, dst)
 c = cv.waitKey(1000)
 if c == 27:
 break

说明

  • 加载图像
    C++
 Mat src = imread( samples::findFile( filename ), IMREAD_COLOR );
 if (src.empty())
 {
 std::cout << "Cannot read image: " << filename << std::endl;
 return -1;
 }

Java

 Mat src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_COLOR);
 if (src.empty()) {
 System.err.println("Cannot read image: " + filename);
 System.exit(0);
 }

Python

src = cv.imread(cv.samples.findFile(args.input), cv.IMREAD_COLOR)
if src is None:
 print('Could not open or find the image: ', args.input)
 exit(0)
  • 创建目标图像和两个映射矩阵(x 和 y)
    C++
 Mat dst(src.size(), src.type());
 Mat map_x(src.size(), CV_32FC1);
 Mat map_y(src.size(), CV_32FC1);

Java

 mapX = new Mat(src.size(), CvType.CV_32F);
 mapY = new Mat(src.size(), CvType.CV_32F);

Python

map_x = np.zeros((src.shape[0], src.shape[1]), dtype=np.float32)
map_y = np.zeros((src.shape[0], src.shape[1]), dtype=np.float32)
  • 创建显示结果的窗口
    C++
 const char* remap_window = "Remap demo";
 namedWindow( remap_window, WINDOW_AUTOSIZE );

Java

 final String winname = "Remap demo";
 HighGui.namedWindow(winname, HighGui.WINDOW_AUTOSIZE);

Python

window_name = 'Remap demo'
cv.namedWindow(window_name)
  • 建立一个循环。每 1000 毫秒更新一次映射矩阵(mat_x 和 mat_y),并将其应用于源图像:
    C++
 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;
 }
 }

Java

 for (;;) {
 updateMap();
 Imgproc.remap(src, dst, mapX, mapY, Imgproc.INTER_LINEAR);
 HighGui.imshow(winname, dst);
 if (HighGui.waitKey(1000) == 27) {
 break;
 }
 }

Python

ind = 0
while True:
 update_map(ind, map_x, map_y)
 ind = (ind + 1) % 4
 dst = cv.remap(src, map_x, map_y, cv.INTER_LINEAR)
 cv.imshow(window_name, dst)
 c = cv.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 种不同的映射:
    将图片缩小到一半大小,并在中间显示:
    h ( i , j ) = ( 2 × i − s r c . c o l s / 2 + 0.5 , 2 × j − s r c . r o w s / 2 + 0.5 ) h\left( i,j \right) =\left( 2×i−src.cols/2+0.5,2×j−src.rows/2+0.5 \right) h(i,j)=(2×isrc.cols/2+0.5,2×jsrc.rows/2+0.5)

  1. 对于所有 (i,j) 对,即: s r c . c o l s 4 < i < 3 ⋅ s r c . c o l s 4 和 s r c . r o w s 4 < j < 3 ⋅ s r c . r o w s 4 \frac{src.cols}{4}<i<\frac{3⋅src.cols}{4}和\frac{src.rows}{4}<j<\frac{3⋅src.rows}{4} 4src.cols<i<43src.cols4src.rows<j<43src.rows

  2. 将图像上下颠倒: h ( i , j ) = ( i , s r c . r o w s − j ) h\left( i,j \right) =\left( i,src.rows-j \right) h(i,j)=(i,src.rowsj)

  3. 从左往右反射图像: h ( i , j ) = ( s r c . c o l s − i , j ) h\left( i,j \right) =\left( src.cols-i,j \right) h(i,j)=(src.colsi,j)

  4. b 和 c 的组合: h ( i , j ) = ( s r c . c o l s − i , s r c . r o w s − j ) h\left( i,j \right) =(src.cols-i,src.rows-j) h(i,j)=src.colsi,src.rowsj

这可以用下面的代码段来表示。其中,map_x 表示 h(i,j) 的第一个坐标,map_y 表示第二个坐标。
C++

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;
}

Java

 private void updateMap() {
 float buffX[] = new float[(int) (mapX.total() * mapX.channels())];
 mapX.get(0, 0, buffX);
 float buffY[] = new float[(int) (mapY.total() * mapY.channels())];
 mapY.get(0, 0, buffY);
 for (int i = 0; i < mapX.rows(); i++) {
 for (int j = 0; j < mapX.cols(); j++) {
 switch (ind) {
 case 0:
 if( j > mapX.cols()*0.25 && j < mapX.cols()*0.75 && i > mapX.rows()*0.25 && i < mapX.rows()*0.75 ) {
 buffX[i*mapX.cols() + j] = 2*( j - mapX.cols()*0.25f ) + 0.5f;
 buffY[i*mapY.cols() + j] = 2*( i - mapX.rows()*0.25f ) + 0.5f;
 } else {
 buffX[i*mapX.cols() + j] = 0;
 buffY[i*mapY.cols() + j] = 0;
 }
 break;
 case 1:
 buffX[i*mapX.cols() + j] = j;
 buffY[i*mapY.cols() + j] = mapY.rows() - i;
 break;
 case 2:
 buffX[i*mapX.cols() + j] = mapY.cols() - j;
 buffY[i*mapY.cols() + j] = i;
 break;
 case 3:
 buffX[i*mapX.cols() + j] = mapY.cols() - j;
 buffY[i*mapY.cols() + j] = mapY.rows() - i;
 break;
 default:
 break;
 }
 }
 }
 mapX.put(0, 0, buffX);
 mapY.put(0, 0, buffY);
 ind = (ind+1) % 4;
 }

Python

def update_map(ind, map_x, map_y):
 if ind == 0:
 for i in range(map_x.shape[0]):
 for j in range(map_x.shape[1]):
 if j > map_x.shape[1]*0.25 and j < map_x.shape[1]*0.75 and i > map_x.shape[0]*0.25 and i < map_x.shape[0]*0.75:
 map_x[i,j] = 2 * (j-map_x.shape[1]*0.25) + 0.5
 map_y[i,j] = 2 * (i-map_y.shape[0]*0.25) + 0.5
 else:
 map_x[i,j] = 0
 map_y[i,j] = 0
 elif ind == 1:
 for i in range(map_x.shape[0]):
 map_x[i,:] = [x for x in range(map_x.shape[1])]
 for j in range(map_y.shape[1]):
 map_y[:,j] = [map_y.shape[0]-y for y in range(map_y.shape[0])]
 elif ind == 2:
 for i in range(map_x.shape[0]):
 map_x[i,:] = [map_x.shape[1]-x for x in range(map_x.shape[1])]
 for j in range(map_y.shape[1]):
 map_y[:,j] = [y for y in range(map_y.shape[0])]
 elif ind == 3:
 for i in range(map_x.shape[0]):
 map_x[i,:] = [map_x.shape[1]-x for x in range(map_x.shape[1])]
 for j in range(map_y.shape[1]):
 map_y[:,j] = [map_y.shape[0]-y for y in range(map_y.shape[0])]

结果

  1. 编译完上面的代码后,可以将图像路径作为参数来执行。例如,使用以下图像:

在这里插入图片描述

  1. 这是将其缩小到一半大小并居中的结果:

在这里插入图片描述

  1. 将其翻转过来:

在这里插入图片描述

  1. 向 x 方向反射:

在这里插入图片描述

  1. 向两个方向反射:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值