| OpenCV 4.3.0 Open Source Computer Vision

Remapping

Prev Tutorial: Hough Circle Transform

Next Tutorial: Affine Transformations

## Goal

In this tutorial you will learn how to:

a. Use the OpenCV function cv::remap to implement simple remapping routines.

## Theory

### What is remapping?

- It is the process of taking pixels from one place in the image and locating them in another position in a new image.
- To accomplish the mapping process, it might be necessary to do some interpolation for non-integer pixel locations, since there will not always be a one-to-one-pixel correspondence between source and destination images.

- We can express the remap for every pixel location \((x,y)\) as:

  \[g(x,y) = f ( h(x,y) )\]

  where \(g()\) is the remapped image, \(f()\) the source image and \(h(x,y)\) is the mapping function that operates on \((x,y)\).

- Let's think in a quick example. Imagine that we have an image \(I\) and, say, we want to do a remap such that:

  \[h(x,y) = (I.cols - x, y )\]

  What would happen? It is easily seen that the image would flip in the \(x\) direction. For instance, consider the input image:

  observe how the red circle changes positions with respect to x (considering \(x\) the horizontal direction):

- In OpenCV, the function cv::remap offers a simple remapping implementation.

## Code

- What does this program do?
  - Loads an image
  - Each second, apply 1 of 4 different remapping processes to the image and display them indefinitely in a window.
  - Wait for the user to exit the program

C++

- The tutorial code's is shown lines below. You can also download it from here
  #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;
  } // end of switch
  }
  }
  ind = (ind+1) % 4;
  }

Java

- The tutorial code's is shown lines below. You can also download it from here
  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

- The tutorial code's is shown lines below. You can also download it from here
  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

## Explanation

- Load an image:

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)

- Create the destination image and the two mapping matrices (for x and 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)

- Create a window to display results

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)

- Establish a loop. Each 1000 ms we update our mapping matrices (mat_x and mat_y) and apply them to our source image:

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

- The function that applies the remapping is cv::remap . We give the following arguments:

  - src: Source image
  - dst: Destination image of same size as src
  - map_x: The mapping function in the x direction. It is equivalent to the first component of \(h(i,j)\)
  - map_y: Same as above, but in y direction. Note that map_y and map_x are both of the same size as src
  - INTER_LINEAR: The type of interpolation to use for non-integer pixels. This is by default.
  - BORDER_CONSTANT: Default

  How do we update our mapping matrices mat_x and mat_y? Go on reading:

- Updating the mapping matrices: We are going to perform 4 different mappings:
  1. Reduce the picture to half its size and will display it in the middle:

     \[h(i,j) = ( 2 \times i - src.cols/2 + 0.5, 2 \times j - src.rows/2 + 0.5)\]

     for all pairs \((i,j)\) such that: \(\dfrac{src.cols}{4}<i<\dfrac{3 \cdot src.cols}{4}\) and \(\dfrac{src.rows}{4}<j<\dfrac{3 \cdot src.rows}{4}\)
  2. Turn the image upside down: \(h( i, j ) = (i, src.rows - j)\)
  3. Reflect the image from left to right: \(h(i,j) = ( src.cols - i, j )\)
  4. Combination of b and c: \(h(i,j) = ( src.cols - i, src.rows - j )\)

This is expressed in the following snippet. Here, map_x represents the first coordinate of h(i,j) and map_y the second coordinate.

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

## Result

1. After compiling the code above, you can execute it giving as argument an image path. For instance, by using the following image:

2. This is the result of reducing it to half the size and centering it:

3. Turning it upside down:

4. Reflecting it in the x direction:

5. Reflecting it in both directions: