上一个教程 : 使用广义巴拉德和霍夫变换检测物体
下一个教程 : 仿射变换
原作者 | 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.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;
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×i−src.cols/2+0.5,2×j−src.rows/2+0.5)
-
对于所有 (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<43⋅src.cols和4src.rows<j<43⋅src.rows
-
将图像上下颠倒: 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.rows−j)
-
从左往右反射图像: 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.cols−i,j)
-
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.cols−i,src.rows−j)
这可以用下面的代码段来表示。其中,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])]
结果
- 编译完上面的代码后,可以将图像路径作为参数来执行。例如,使用以下图像:
- 这是将其缩小到一半大小并居中的结果:
- 将其翻转过来:
- 向 x 方向反射:
- 向两个方向反射: