Skip to content

重映射

本教程中你将学到如何:

  • 使用 OpenCV 函数 cv::remap 实现简单的重映射(remapping)例程。
  • 重映射是把图像中某处的像素取出、并把它们放置到新图像中另一个位置的过程。
  • 为了完成映射过程,可能需要对非整数像素位置做一些插值,因为源图像和目标图像之间并不总存在一一对应的像素关系。
  • 我们可以把每个像素位置 (x,y)(x,y) 的重映射表达为:

g(x,y)=f(h(x,y))g(x,y) = f(h(x,y))

其中 $g()$ 是重映射后的图像,$f()$ 是源图像,$h(x,y)$ 是作用于 $(x,y)$ 的映射函数。
  • 让我们想一个快速的例子。假设我们有一幅图像 II,想做这样一个重映射:

h(x,y)=(I.cols−x,y)h(x,y) = (I.cols - x, 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;
/// Function Headers
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);
/// Load the image
Mat src = imread( samples::findFile( filename ), IMREAD_COLOR );
if (src.empty())
{
std::cout << "Cannot read image: " << filename << std::endl;
return -1;
}
/// Create dst, map_x and map_y with the same size as src:
Mat dst(src.size(), src.type());
Mat map_x(src.size(), CV_32FC1);
Mat map_y(src.size(), CV_32FC1);
/// Create window
const char* remap_window = "Remap demo";
namedWindow( remap_window, WINDOW_AUTOSIZE );
/// Index to switch between the remap modes
int ind = 0;
for(;;)
{
/// Update map_x & map_y. Then apply remap
update_map(ind, map_x, map_y);
remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0) );
/// Display results
imshow( remap_window, dst );
/// Each 1 sec. Press ESC to exit the program
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;
} // end of switch
}
}
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) {
// Load the native OpenCV library
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

Remap_Tutorial_Theory_0.jpg Remap_Tutorial_Theory_1.jpg

  • 加载一幅图像:

    C++

    /// Load the image
    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++

    /// Create dst, map_x and map_y with the same size as src:
    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++

    /// Create window
    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++

    /// Index to switch between the remap modes
    int ind = 0;
    for(;;)
    {
    /// Update map_x & map_y. Then apply remap
    update_map(ind, map_x, map_y);
    remap( src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0) );
    /// Display results
    imshow( remap_window, dst );
    /// Each 1 sec. Press ESC to exit the program
    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)h(i,j) 的第一个分量

    • map_y:同上,但在 y 方向上。注意 map_y 和 map_x 都与 src 大小相同

    • INTER_LINEAR:对非整数像素使用的插值类型。这是默认值。

    • BORDER_CONSTANT:默认值

      我们如何更新映射矩阵 mat_x 和 mat_y?继续往下读:

  • 更新映射矩阵:我们将执行 4 种不同的映射:

    1. 把图像缩小到一半大小,并显示在中间:

h(i,j)=(2×i−src.cols/2+0.5,  2×j−src.rows/2+0.5)h(i,j) = (2 \times i - src.cols/2 + 0.5, \; 2 \times j - src.rows/2 + 0.5)

对于所有满足 $\dfrac{src.cols}{4} < i < \dfrac{3 \cdot src.cols}{4}$ 且 $\dfrac{src.rows}{4} < j < \dfrac{3 \cdot src.rows}{4}$ 的 $(i,j)$ 对。
2. 把图像上下翻转:$h(i, j) = (i, src.rows - j)$
3. 把图像从左到右镜像:$h(i,j) = (src.cols - i, j)$
4. 第 2 种与第 3 种的组合:$h(i,j) = (src.cols - i, src.rows - j)$

这在下面的代码片段中得以体现。其中 map_x 表示 h(i,j)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;
}

Remap_Tutorial_Original_Image.jpg

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])]

Remap_Tutorial_Result_0.jpg Remap_Tutorial_Result_1.jpg Remap_Tutorial_Result_2.jpg Remap_Tutorial_Result_3.jpg

  1. 编译上面的代码后,你可以以图像路径为参数运行它。

  2. 缩小到一半大小并居中显示的结果。

  3. 上下翻转的结果。

  4. 沿 x 方向镜像的结果。

  5. 沿两个方向同时镜像的结果。