重映射
重映射就是把一幅圖像中某個位置的像素放置到另一個圖片中指定位置的過程。
用一個數學公式來表示就是:
其中的 f 就是映射方式,也就說,像素點在另一個圖像中的位置是由 f 來計算的。
在OpenCV中,用的是remap函數實現重映射。
基本重映射
#include <iostream>
#include <opencv2\opencv.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
using namespace cv;
using namespace std;
//基本重映射實驗
int main()
{
Mat srcImage = imread("2.jpg");
if (!srcImage.data)
{
cout << "找不到這張圖片!" << endl;
return -1;
}
imshow("Src Pic", srcImage);
Mat dstImage, map_x, map_y;
dstImage.create(srcImage.size(), srcImage.type());//創建和原圖一樣的效果圖
map_x.create(srcImage.size(), CV_32FC1);
map_y.create(srcImage.size(), CV_32FC1);
//遍歷每一個像素點,改變map_x & map_y的值,實現翻轉180度
for (int j = 0; j < srcImage.rows; j++)
{
for (int i = 0; i < srcImage.cols; i++)
{
map_x.at<float>(j, i) = static_cast<float>(i);
map_y.at<float>(j, i) = static_cast<float>(srcImage.rows - j);
}
}
//進行重映射操作
remap(srcImage, dstImage, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
imshow("重映射效果圖", dstImage);
waitKey();
return 0;
}
map_x與map_y分別代表目標圖中的(x,y)點在原圖中的x坐標(由map_x提供)與y坐標(由map_y提供)。
運行效果,圖像翻轉了。
仿射變換
仿射變換指的是一個向量空間進行一次線性變換並接上一個平移,變換為另一個向量空間的過程。
圖像進行仿射變換后,有以下幾個特點:
二維圖形之間的相對位置關系保持不變,平行線依舊是平行線,且直線上的點的位置順序保持不變。
一個任意的仿射變換都可以表示為乘以一個矩陣(線性變換)接着再加上一個向量(平移)的形式。
三種常見形式:
- 旋轉,rotation(線性變換)
- 平移,translation(向量加)
- 縮放,scale(線性變換)
仿射變換本質是一個2* 3的矩陣M乘上原圖的每個坐標,得到目標圖的對應點坐標。2*3矩陣M中的2表示目標點坐標的x與y,3中的第三維是平移分量。因此需要做的就是找到矩陣M,OpenCV提供 getAffineTransform 求出仿射變換, getRotationMatrix2D 來獲得旋轉矩陣。
這里簡單說說仿射變換是怎么做到的。
現在有兩幅圖像(如下圖),圖像二是圖像一經過放射變化得來的。那問題來了,我們怎么從這兩個圖像信息里挖掘出兩圖之間的映射關系?
很簡單,只要在圖像一種拿出三個點(1,2,3),圖像二也拿出對應的三個點(1,2,3),就可以求出兩圖間的映射關系!
OpenCV通過兩個函數的組合使用來實現仿射變換:
- 使用warpAffine來實現簡單重映射
- 使用getRotationMatrix2D來獲得旋轉矩陣
#include<iostream>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
//仿射變換實驗
int main()
{
Mat src = imread("lol9.jpg");
Mat dst_warp, dst_warpRotateScale;
Point2f srcPoints[3];//原圖中的三點
Point2f dstPoints[3];//目標圖中的三點
//第一種仿射變換的調用方式:三點法
//三個點對的值,上面也說了,只要知道你想要變換后圖的三個點的坐標,就可以實現仿射變換
srcPoints[0] = Point2f(0, 0);
srcPoints[1] = Point2f(0, src.rows - 1);
srcPoints[2] = Point2f(src.cols - 1, 0);
//映射后的三個坐標值
dstPoints[0] = Point2f(0, src.rows*0.3);
dstPoints[1] = Point2f(src.cols*0.25, src.rows*0.75);
dstPoints[2] = Point2f(src.cols*0.75, src.rows*0.25);
Mat M1 = getAffineTransform(srcPoints, dstPoints);//由三個點對計算變換矩陣
warpAffine(src, dst_warp, M1, src.size());//仿射變換
//第二種仿射變換的調用方式:直接指定角度和比例
//旋轉加縮放
Point2f center(src.cols / 2, src.rows / 2);//旋轉中心
double angle = 45;//逆時針旋轉45度
double scale = 0.5;//縮放比例
Mat M2 = getRotationMatrix2D(center, angle, scale);//計算旋轉加縮放的變換矩陣
warpAffine(dst_warp, dst_warpRotateScale, M2, src.size());//仿射變換
imshow("原始圖", src);
imshow("仿射變換1", dst_warp);
imshow("仿射變換2", dst_warpRotateScale);
waitKey(0);
return 0;
}
兩種仿射變換的效果如下。
有沒有發現圖片進行仿射變換后的背景被填充為黑色了?其實這個背景色是可以調的,像這樣:
warpAffine(dst_warp, dst_warpRotateScale, M2, src.size(), 1, 0, Scalar(11,111, 211));//利用Scalar來填充不同顏色背景
然后背景色就變成這樣子了:
最后寫一個對圖片旋轉任何角度的代碼。
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
#define PIC_BEGIN_NUM 100 //這里定義你的起始圖片編號
#define ANGLE_START -45 //旋轉角度的開始
#define ANGLE_END 0 //旋轉角度的結束
#define ANGLE_STEP 2 //旋轉角度步長
int main(int argc, char **argv)
{
//Read a single-channel image
const char* filename = "lol9.jpg";
Mat srcImg = imread(filename, 1);
imshow("source", srcImg);
Point center(srcImg.cols / 2, srcImg.rows / 2); //圖片中心為旋轉點
char file[20];
int count = PIC_BEGIN_NUM;
Mat tmpimg;
for (int tmp = ANGLE_START; tmp < ANGLE_END; tmp += ANGLE_STEP)
{
Mat rotMatS = getRotationMatrix2D(center, tmp, 0.5); //圖片縮小到原來的0.5倍
warpAffine(srcImg, tmpimg, rotMatS, srcImg.size(), 1, 0, Scalar(0, 0, 0));//填充黑色背景
sprintf(file, "%d.jpg", count++); //旋轉圖片以1.jpg 2.jpg 的名字格式保存
imwrite(file, tmpimg);
}
waitKey(0);
return 0;
}
這里的代碼實現對圖片旋轉2度、4度...45度的功能,並將這些旋轉后的圖像保存先來。
然后文件夾下就出現旋轉好的圖片了!