圖像仿射變換
要求:
–設計一個函數WarpAffine,可以對圖像進行任意的二維仿射變換(用2*3矩陣表示):
–採用雙線性插值進行重採樣;
–可以只考慮輸入圖像爲3通道,8位深度的情況;
函數接口可以參考OpenCV的warpAffine函數
調用WarpAffine,實現繞任意中心的旋轉函數Rotate
圖像變形
記[x’, y’]=f([x, y])爲像素座標的一個映射,實現f所表示的圖像形變。f的逆映射爲:
1.1雙線性插值
對於4個相鄰像素,分別進行水平和垂直方向進行線性插值
找到給定(i,j)點的周圍四個頂點,對齊進行插值。
float billinear(float a, float b, float c, float d, float dx, float dy)
{
float h1 = a + dx * (b - a);
float h2 = c + dx * (d - c);
return h1 + dy * (h2 - h1);
}
float aa = rimg1.at(i / scale, j / scale);
float bb = rimg1.at(i / scale, j / scale+1);
float cc = rimg1.at(i / scale+1, j / scale);
float dd = rimg1.at(i / scale + 1, j / scale+1);
double ee = (double(j) / scale) - (j / scale);
double ff = (double(i) / scale) - (i / scale);
float xx = billinear(aa, bb, cc, dd, ff, ff);
rimg2.at(i, j) = saturate_cast(xx);
原始圖像
以放大2倍爲例:
原像素點的映射(有1/4的點可以直接通過映射找到對應原圖像的點,這部分直接填充顏色即可)
對在正方形邊界處的點進行另外處理,就會出現“點連成邊”的效果
正方形內部的點通過雙線性插值來填充填充:
當然,放大肯定會失真
接下來,嘗試取更少的像素點,下面第一張是長寬按照1/10取點。
(1/100取點連邊)
(1/100取點插值)
嘗試對雙線性插值的billinear函數進行修改:
a=b,即正方形內部的點用3個點,而不是4個來填充,或者dx=dy,會產生棱角的美感。
float xx = billinear(aa, aa, cc, dd, ee, ff);
float xx = billinear(aa, bb, cc, dd, ee, ee);
(初始圖像)
(1/50插值)
(A=B)
(dx=dy)
1.2旋轉的實現
原理:構造旋轉矩陣,建立初始圖像與新圖像之間每一個像素點之間的映射關係。
逆矩陣:將變換矩陣函數中的自變量編程新圖像,這個方便遍歷新圖像的每一個像素點通過逆矩陣找到對應的初始像素點進行操作。
void getmatrix(int degree,int dx,int dy)
{
double t1 = -dx * cos(degree) + dy * sin(degree) + dx;
double t2 = -dx * sin(degree) - dy * cos(degree) + dy;
rot3.at(0, 0) = cos(degree);
rot3.at(0, 1) = sin(degree);
rot3.at(0, 2) = -sin(degree)*t2-cos(degree)*t1;
rot3.at(1, 1) = cos(degree);
rot3.at(1, 0) = -sin(degree);
rot3.at(1, 2) = -cos(degree) * t2 +sin(degree) * t1;
rot3.at(0, 0) = rot3.at(0, 0) ;
rot3.at(1, 1) = rot3.at(1, 1) ;
}
Void warpaffine()//部分核心代碼{
int xx = rot3.at(0, 0) * i + rot3.at(0, 1) * j + rot3.at(0, 2);
int yy = rot3.at(1, 0) * i + rot3.at(1, 1) * j + rot3.at(1, 2);
if (xx >= 0 && xx < rimg1.rows && yy >= 0 && yy < rimg1.cols)
rimg2.at(i, j)[z] = rimg1.at(xx, yy);
}
旋轉後
那麼如果我們對仿射矩陣稍作變化,就會得到一些不同的效果。
1.3不同重採樣方法之間的對比
(1)初始圖像:(100100)(200200),分辨率96.
(2)
最近鄰Nearest:
找到最近的像素,輸出顏色:
(3)雙線性差值Liner
對於4個相鄰像素,分別進行水平和
垂直方向進行線性插值
float bilinear(float a, float b, float c,
float d, float dx, float dy)
{
float h1=a+dx*(b-a);
// = (1-dx)a + dxb
float h2=c+dx*(d-c);
return h1+dy*(h2-h1);
}
(4)雙三次插值
比較發現:最近鄰的效果是明顯較差的,雙三次相比雙線性在算法效果上可能更有優勢,但是在圖像最後的放大呈現上的差距並不是很大,且雙三次會有相對更高的算大複雜度。
在對Photoshop等圖像處理軟件的最後放大效果同各種算法進行比對後發現,Photoshop的放大效果要比最近鄰要好,但大圖像的處理上比起雙線性插值略差,放大倍數不是很大的話二者差距很小。
其他方法:
Lanczos photoshop
但即便使用4x4像素鄰域的雙三次插值或者8x8像素鄰域的Lanczos插值,還是可以很明顯的看到圖像會有失真效果,這種失真現象在反打4倍以後更加明顯。
8x8像素鄰域的Lanczos插值
實驗二 圖像變形
1.記[x’, y’]=f([x, y])爲像素座標的一個映射,實現f所表示的圖像形變。f的逆映射爲:
這個逆映射是歸一化中心座標之後的映射,即原圖像和新圖像都要進行中心歸一化座標,纔有上面的對應關係
座標歸一化:
逆變換要求的意爲:在給定橢圓(r限制)外不進行圖像的變形,在橢圓內進行給定的正弦餘弦變換
double X = x / ((row - 1) / 2.0) - 1.0;//歸一化
double Y = y / ((col - 1) / 2.0) - 1.0;
double theta = 1.0 + X * X + Y * Y - 2.0 * sqrt(X * X + Y * Y);//(1-r)*(1-r)
double x_ = cos(theta) * X - sin(theta) * Y;
double y_ = sin(theta) * X + cos(theta) * Y;//找到原來對於的點
x_ = (x_ + 1.0) * ((row - 1) / 2.0);//正方形映射回長方形
y_ = (y_ + 1.0) * ((col - 1) / 2.0);
color(X,Y)=color(x_,y_);
按照要求得到的形變效果如下所示:
修改參數theta,可以修改形變角度,產生不同程度的形變。
Theta=1-r
Theta=(1-r)3
稍加修改逆矩陣,就可以看到形變的具體範圍
問題:
- 一組namedWindow,imshow函數中出現兩個窗口:
描述:我在debug模式下運行只有一個窗口,但是到release模式下就變成兩個窗口,一個圖片,一個灰色區域
解釋:這是因爲在配置cv時,爲了保證可用,添加了兩個版本lib,opencv_world341d.lib和opencv_world341.lib(鏈接器-輸入-附加依賴項),帶d爲debug版,不帶d爲release版。release有兩個debug沒有,是兩個的lib添加順序問題。
解決:刪除多餘的lib或者換一下模式即可。
2. 在旋轉操作中會出現旋轉後的圖像只能呈現一部分的問題
這是因爲原始圖像上的部分點對應過來新建的圖像會有部分越界,這部分越界的會被丟棄
解決方法:可以先對原圖像縮小一下再進行旋轉,或者對應之後進行一下等比例變化。
大多數分析和體會都在上面實驗內容寫完了,這裏補充一下吧。
(1) Opencv的warpAffine函數
void cv::warpAffine(InputArray src,OutputArray dst,InputArray M,Size dsize,int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, const Scalar & borderValue = Scalar() )
src: 輸入圖像
dst: 輸出圖像,尺寸由dsize指定,圖像類型與原圖像一致
M: 2X3的變換矩陣
dsize: 指定圖像輸出尺寸
flags: 插值算法標識符,默認值INTER_LINEAR,
(2)OpenCV圖像縮放使用的函數是:resize
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
InputArray src -原圖像
OutputArray dst -輸出圖像
Size dsize -目標圖像的大小
double fx=0 -在x軸上的縮放比例
double fy=0 -在y軸上的縮放比例
int interpolation -插值方式,有以下四種方式
INTER_NN -最近鄰插值
INTER_LINEAR -雙線性插值 (缺省使用)
INTER_AREA -使用象素關係重採樣,當圖像縮小時候,該方法可以避免波紋出現。當圖像放大時,類似於 INTER_NN 方法。
INTER_CUBIC -立方插值。
(3)getRotationMatrix2D(Point2f center, double angle, dooule scale)
給定中心,角度和比例,得到旋轉矩陣。
(4)逆矩陣
對於繞任意中心進行旋轉,需要求取正變換的逆矩陣,正變換矩陣如下:
`
//不同重採樣方法之間的比較
#include<opencv2/core/core.hpp>
#include<opencv2/imgcodecs.hpp>
#include<opencv2/core/saturate.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include <imgproc.hpp>
#include <shape.hpp>
#include <filesystem>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("D:\\picture\\flower.jpg");
if (img.empty())
cerr << "can not load image" << endl;
Mat largeimg1;
Mat largeimg2;
Mat largeimg3;
Mat largeimg4;
Size dsize;
resize(img,largeimg1,dsize,2, 2, INTER_NEAREST);
resize(img,largeimg2, dsize, 2, 2, INTER_LINEAR);
resize(img, largeimg3, dsize, 2, 2, INTER_CUBIC);
resize(img, largeimg4, dsize, 4, 4, INTER_LANCZOS4);
//imwrite("D:\\picture\\flowernerst.jpg", largeimg1);
//imwrite("D:\\picture\\flowerliner.jpg", largeimg2);
//imwrite("D:\\picture\\flowerlanczos4.jpg", largeimg4);
//imwrite("D:\\picture\\flowerthree.jpg", largeimg3);
namedWindow("nearest", 1);
imshow("nearest", largeimg1);
namedWindow("linear", 1);
imshow("linear",largeimg2);
namedWindow("three", 1);
imshow("linear", largeimg3);
namedWindow("lanczos4", 1);
imshow("lanczos4", largeimg4);
waitKey();
} ``
//圖像旋轉和雙線性插值
#include<opencv2/core/core.hpp>
#include<opencv2/imgcodecs.hpp>
#include<opencv2/core/saturate.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include <imgproc.hpp>
#include <shape.hpp>
#include <filesystem>
using namespace std;
using namespace cv;
Mat rot3(2, 3, CV_64FC1, cv::Scalar(0, 0, 0));
Mat rot4(2, 3, CV_64FC1, cv::Scalar(0, 0, 0));
void rotation(double scale)
{
rot4.at<double>(0, 0) = scale;
rot4.at<double>(1,1) = scale;
rot4.at<double>(0, 1) = 0;
rot4.at<double>(0, 2) = 0;
rot4.at<double>(1, 0) = 0;
rot4.at<double>(1, 2) = 0;
}
//雙線性插值
float billinear(float a, float b, float c, float d, float dx, float dy)
{
float h1 = a + dx * (b - a);
float h2 = c + dx * (d - c);
return h1 + dy * (h2 - h1);
}
void Liner(Mat& rimg2, Mat rimg1, int scale, int cols, int rows)
{
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
if(i%scale==0&&j%scale==0)//說明在定點處
for (int z = 0; z < 3; ++z)
{
rimg2.at<Vec3b>(i, j)[z] = rimg1.at<Vec3b>(i / scale, j / scale)[z];//像素值對應
}
else if(i % scale == 0)
{
for (int z = 0; z < 3; ++z)
{
float aa = rimg1.at<Vec3b>(i / scale, j / scale)[z];
float bb = rimg1.at<Vec3b>(i / scale, j / scale+1)[z];
double ee = (double(j) / scale)- (j / scale); double ff = 0;
float xx = billinear(aa, bb, aa, bb, ee, ff);
rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(xx);
}
}
else if(j % scale == 0)
{
for (int z = 0; z < 3; ++z)
{
float aa = rimg1.at<Vec3b>(i / scale, j / scale)[z];
float bb = rimg1.at<Vec3b>(i / scale+1 , j / scale)[z];
double ee = 0; double ff = (double(i) / scale) - (i / scale);
float xx = billinear(aa, aa, bb, bb, ee, ff);
rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(xx);
}
}
else
{
for (int z = 0; z < 3; ++z)
{
float aa = rimg1.at<Vec3b>(i / scale, j / scale)[z];
float bb = rimg1.at<Vec3b>(i / scale, j / scale+1)[z];
float cc = rimg1.at<Vec3b>(i / scale+1, j / scale)[z];
float dd = rimg1.at<Vec3b>(i / scale + 1, j / scale+1)[z];
double ee = (double(j) / scale) - (j / scale); double ff = (double(i) / scale) - (i / scale);
float xx = billinear(aa, bb, cc, dd, ee, ff);
rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(xx);
}
}
}
}
return;
}
//只描繪定點
void Liner2(Mat& rimg2, Mat rimg1, int scale, int cols, int rows)
{
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
if (i % scale == 0 && j % scale == 0)//說明在定點處
for (int z = 0; z < 3; ++z)
{
// if(rimg1.at<Vec3b>(xx, yy))
rimg2.at<Vec3b>(i, j)[z] = rimg1.at<Vec3b>(i / scale, j / scale)[z];//像素值對應
//else
//rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
}
else if (i % scale == 0)
{
for (int z = 0; z < 3; ++z)
{
rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
}
}
else if (j % scale == 0)
{
for (int z = 0; z < 3; ++z)
{
rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
}
}
else
{
for (int z = 0; z < 3; ++z)
{
rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
}
}
}
}
return;
}
//WarpAffine函數
void WarpAffine(Mat&rimg2, Mat rimg1, Mat rot3, int cols,int rows)
{
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
int xx = rot3.at<double>(0, 0) * i + rot3.at<double>(0, 1) * j + rot3.at<double>(0, 2);
int yy = rot3.at<double>(1, 0) * i + rot3.at<double>(1, 1) * j + rot3.at<double>(1, 2);
if (xx >= 0 && xx < rimg1.rows && yy >= 0 && yy < rimg1.cols)
{
for (int z = 0; z < 3; ++z)
{
rimg2.at<Vec3b>(i, j)[z] = rimg1.at<Vec3b>(xx, yy)[z];
}
}
else
{
for (int z = 0; z < 3; ++z)
{
rimg2.at<Vec3b>(i, j)[z] = saturate_cast<uchar>(0);
}
}
}
}
return;
}
//得到變換矩陣函數
void getmatrix(int degree, int dx, int dy)
{
double t1 = -dx * cos(degree) + dy * sin(degree) + dx;
double t2 = -dx * sin(degree) - dy * cos(degree) + dy;
rot3.at<double>(0, 0) = cos(degree) * 2;
rot3.at<double>(0, 1) = sin(degree);
rot3.at<double>(0, 2) = (-sin(degree) * t2 - cos(degree) * t1) * 2-200 ;
rot3.at<double>(1, 1) = cos(degree);
rot3.at<double>(1, 0) = -sin(degree);
rot3.at<double>(1, 2) = -cos(degree) * t2 + sin(degree) * t1 ;
}
int main()
{
Mat img1 = imread("D:\\picture\\girl.jpg");
if (img1.empty())
cerr << "can not load image" << endl;
Point2f center = Point2f(img1.cols / 2.0, img1.rows / 2.0);
double degree = 1;
double scale = 0.5;//縮小一下,使得整個屏幕都可以看到
cout << img1.cols / 2.0 << img1.rows / 2.0 << endl;
// 輸出圖像的中心座標
//Mat rot = getRotationMatrix2D(center, degree, scale);
//直接調用線程的庫函數得到變換矩陣
double aa = 0.5000000000000001;
getmatrix(degree, img1.cols / 3.0, img1.rows / 3.0);
//這裏是使用自己寫的函數來得到矩陣
rotation(scale);
//這是一個縮放比例尺,調用的rot4矩陣
//cout << rot <<endl;
//cout << rot3 << endl;
//cout << rot4 << endl;
Mat rimg1 = Mat::zeros(img1.size(), CV_8UC3);
warpAffine(img1, rimg1, rot4, img1.size());
//這一步是縮小
//Mat rimg2 = Mat::zeros(img.size(), img.type());//獲取圖像,建立新的像素矩陣
Mat rimg2(img1.rows*2 , img1.cols*2 , CV_8UC3);
warpAffine(rimg1, rimg2, rot3, rimg2.size());
//WarpAffine(rimg2, rimg1, rot3, rimg2.cols,rimg2.rows);
//這一步是旋轉
namedWindow("img", 0);
resizeWindow("img", 400, 400);
imshow("img", img1);
Size dsize;
//Liner(rimg2, rimg1, 100, img1.cols, img1.rows);
namedWindow("rimg", 1);
//resizeWindow("rimg", 400, 400);
imshow("rimg", rimg2); waitKey();
}
//中心歸一化和變形
#include<opencv2/core/core.hpp>
#include<opencv2/imgcodecs.hpp>
#include<opencv2/core/saturate.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
#include <imgproc.hpp>
#include <shape.hpp>
#include <filesystem>
using namespace std;
using namespace cv;
Mat changeShape(const Mat& src) {
Mat imgAffine = Mat::zeros(src.rows, src.cols, src.type());
int row = imgAffine.rows, col = imgAffine.cols;
for (int x = 0; x < row; x++) {
for (int y = 0; y < col; y++) {
double X = x / ((row - 1) / 2.0) - 1.0;//這部分是歸一化,將每一個像素點同正方形的位置進行對比
double Y = y / ((col - 1) / 2.0) - 1.0;
double r = sqrt(X * X + Y * Y);//意思 是咋這個圈圈外的不進行形變
if (r >= 1) {
imgAffine.at<Vec3b>(x, y)[0] = saturate_cast<uchar>(src.at<Vec3b>(x, y)[0]);
imgAffine.at<Vec3b>(x, y)[1] = saturate_cast<uchar>(src.at<Vec3b>(x, y)[1]);
imgAffine.at<Vec3b>(x, y)[2] = saturate_cast<uchar>(src.at<Vec3b>(x, y)[2]);
}//像素點顏色等於原來的額像素點顏色就像ok
else {
double theta = 1.0 + X * X + Y * Y - 2.0 * sqrt(X * X + Y * Y);//(1-r)*(1-r)
double x_ = cos(theta) * X - sin(theta) * Y;
double y_ = sin(theta) * X + cos(theta) * Y;//找到原來對於的點
x_ = (x_ + 1.0) * ((row - 1) / 2.0);//正方形映射回長方形
y_ = (y_ + 1.0) * ((col - 1) / 2.0);
if (x_ < 0 || y_ < 0 || x_ >= src.rows || y_ >= src.cols) {
for (int c = 0; c < 3; c++) {
imgAffine.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(0);
}
}//越界處理
else {
//左上角座標(X1,Y1)
//計算雙線性插值
int X1 = (int)x_;
int Y1 = (int)y_;
for (int c = 0; c < 3; c++) {
if (X1 == (src.rows - 1) || Y1 == (src.cols - 1)) {
imgAffine.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(src.at<Vec3b>(X1, Y1)[c]);
}//還是邊界處理
else {
//四個頂點像素值
//注意訪問越界
int aa = src.at<Vec3b>(X1, Y1)[c];
int bb = src.at<Vec3b>(X1, Y1 + 1)[c];
int cc = src.at<Vec3b>(X1 + 1, Y1)[c];
int dd = src.at<Vec3b>(X1 + 1, Y1 + 1)[c];//這是,,找到原圖像周圍的4個點
double dx = x_ - (double)X1;
double dy = y_ - (double)Y1;
double h1 = aa + dx * (bb - aa);
double h2 = cc + dx * (dd - cc);
imgAffine.at<Vec3b>(x, y)[c] = saturate_cast<uchar>(h1 + dy * (h2 - h1));
//其實不使用線性插值效果也可以
}
}
}
}
}
}
return imgAffine;
}
int main()
{
Mat img1 = imread("D:\\picture\\monalisa.jpg");
Mat img2 = changeShape(img1);
namedWindow("mona", 1);
imshow("mona", img2);
waitKey();
return 0;
}