Remapping#

Original author

Ana Huamán

Compatibility

OpenCV >= 3.0

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

  • The tutorial code 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;
    
    /// 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;
    }
    
  • The tutorial code 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);
        }
    }
    
  • The tutorial code 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:

/// Load the image
Mat src = imread( samples::findFile( filename ), IMREAD_COLOR );
if (src.empty())
{
    std::cout << "Cannot read image: " << filename << std::endl;
    return -1;
}
Mat src = Imgcodecs.imread(filename, Imgcodecs.IMREAD_COLOR);
if (src.empty()) {
    System.err.println("Cannot read image: " + filename);
    System.exit(0);
}
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 )

/// 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);
mapX = new Mat(src.size(), CvType.CV_32F);
mapY = new Mat(src.size(), CvType.CV_32F);
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

/// Create window
const char* remap_window = "Remap demo";
namedWindow( remap_window, WINDOW_AUTOSIZE );
final String winname = "Remap demo";
HighGui.namedWindow(winname, HighGui.WINDOW_AUTOSIZE);
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:

/// 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;
    }
}
for (;;) {
    updateMap();
    Imgproc.remap(src, dst, mapX, mapY, Imgproc.INTER_LINEAR);
    HighGui.imshow(winname, dst);
    if (HighGui.waitKey(1000) == 27) {
        break;
    }
}
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.

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