【OpenCV影象處理】五、影象的幾何變換(下)
5.1 影象的縮放變換
影象的縮放指的是將影象的尺寸變小或變大的過程,也就是減少或增加原影象資料的畫素個數。簡單來說,就是通過增加或刪除畫素點來改變影象的尺寸。當影象縮小時,影象會變得更加清晰,當影象放大時,影象的質量會有所下降,因此需要進行插值處理。
在影象縮放中常常會用到兩個概念,也就是水平縮放係數和垂直縮放係數,水平縮放係數控制水平畫素的縮放比例,垂直縮放係數控制垂直方向上畫素的縮放比例。在實際運用縮放時,常常需要保持原始影象的寬度和高度的比例,也就是讓水平縮放係數和垂直縮放係數相同。因為這種縮放係數不會使得縮放後的影象發生變形。
設水平縮放係數為Sx,垂直縮放係數為Sy,則縮放的座標對映關係如下:
因為在進行影象縮放時會改變影象的大小,因此這裡更關係向後對映矩陣,此時向後對映矩陣為:
也就是有
利用向後對映進行影象縮放的過程為:
首先進行計算新影象的大小,在這裡設newWidth和newHeight分別表示新影象的寬度和高度,width和height表示原始影象的寬度和高度,則有newWidth=Sx*width,newHeight=Sy*height,然後再進行列舉新影象每個畫素的座標,通過向後對映計算出該畫素對映在原始影象的座標位置,再進行獲取該畫素的值。
需要注意的是,在進行後向對映的過程中可能會產生浮點數座標,但是數字影象是以離散型整數儲存資料的,所以無法得到浮點數座標對應的畫素值,這裡就需要進行插值演算法計算座標是浮點型的畫素值。
差值演算法簡要介紹:
插值演算法主要用於處理影象在幾何變換中出現的浮點座標畫素,演算法的基本思想就是通過一系列演算法獲得浮點座標畫素的近似值,正因為浮點座標是“插入”在整數座標之間的,所以這種處理演算法被稱為“差值演算法”。比較常見的插值演算法有最鄰近插值法,雙線性插值法和二次立方插值法等。一般來說,最鄰近插值法的效果最差,影象在進行放大後出現很明顯的馬賽克效應,使得影象細節變得十分模糊,而雙線性插值法便大大的改善了放大影象後圖像的質量,較好的避免了馬賽克效應的產生,但是細節體現得依舊不夠清晰,二次立方插值法的效果最好,放大後的影象細節較雙線性插值法也有了較好的改善,但是它的計算複雜度非常高,因此導致運算的時間最長。
基本原理:
這裡只簡單地介紹一下最臨近插值法和雙線性插值方法
最鄰近插值法也就是零階插值,這種插值方法簡單粗暴,就是常常說的“四捨五入”,也就是浮點數座標的畫素值等於離這點最近的輸入影象的畫素值,也就是看這點距離周圍的四個點的哪個距離更近,那麼就取哪個畫素值。
雙線性插值法就是二次線性插值法,它的主要思想就是計算出浮點座標畫素的近似值,將周圍的4個點的畫素值按照一定比例進行混合,最終得到這個浮點座標的畫素值。
在OpenCV中利用resize函式的進行影象的縮放操作,函式的原型為:
resize( InputArray src, OutputArray dst,Size dsize, double fx=0, double fy=0,int interpolation=INTER_LINEAR );
前兩個引數分別為輸入和輸出影象。dsize表示輸出影象的大小,如果為0,則
dsize=Size(round(fx∗src.cols),round(fy∗src.rows))dsize和fx、fy不能同時為0。fx、fy是沿x軸和y軸的縮放係數;預設取0時,計算如下
最優一個引數interpolation表示插值方式,有以下幾種:
INTER_NEAREST - 最近鄰插值
INTER_LINEAR - 線性插值(預設)
INTER_AREA - 區域插值
INTER_CUBIC - 三次樣條插值
INTER_LANCZOS4 - Lanczos插值
程式設計實現如下:
//使用resize函式對影象進行縮放操作
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImg, dstImg1,dstImg2,dstImg3;
srcImg = imread("2345.jpg");
imshow("原影象", srcImg);
resize(srcImg, dstImg1, Size(0, 0), 1.5, 1.5, INTER_NEAREST);
resize(srcImg, dstImg2, Size(0, 0), 1.5, 1.5, INTER_LINEAR);
resize(srcImg, dstImg3, Size(0, 0), 1.5, 1.5, INTER_CUBIC);
imshow("放大後的影象1", dstImg1);
imshow("放大後的影象2", dstImg2);
imshow("放大後的影象3", dstImg2);
waitKey();
return 0;
}
這個程式實現的是將影象進行放大1.5倍的操作,而且可以通過結果看出,立方插值的效果好於雙線性插值好於最臨近插值。
5.2 影象的旋轉變換
影象旋轉就是指影象繞著某一點以逆時針或是順時針方向旋轉一定的角度,實際中常常按照逆時針進行旋轉。為了得到旋轉後新影象的座標,通常要經過三個步驟,也就是①座標原點平移到影象中心處(每個畫素座標都做平移變換)②針對新的原點對平移後的座標做旋轉③再將座標原點移回到螢幕的左上角處。
同樣的,影象在進行了旋轉操作後,可能會出現一些空白的畫素,需要對這些空白的畫素做灰度級插值處理,否則將會影響旋轉後圖像的質量。在影象經過旋轉變換後,它的寬度和高度都要發生一些相應的改變,所以原始影象中心點和輸出影象的中心點的座標的不相同的。而這也是造成旋轉表換比較不容易實現的原因之一。
在OpenCV中沒有寫好的單一影象旋轉函式,大多數情況我們會使用仿射變換函式warpAffine來進行影象旋轉相關的操作,在這裡先簡單介紹一下影象的仿射變換,在仿射變換中主要有縮放,翻轉與旋轉三種變換形式,因為所有的仿射變換都是一種線性變換,對應變換可以表示成為矩陣相乘的形式,在空間直角座標系中可以表示成為如下的形式:
在OpenCV中很容易構造仿射變換矩陣,一般使用一個2x3的矩陣來表示放射變換矩陣,也就是將上圖中的矩陣等式的係數和常數項提出來構造矩陣即可。如下所示:
應用影象仿射變換矩陣,可以得到大部分的幾何變換結果,例如之前提到的平移變換等,根據平移變換矩陣可以很容易的得到實現平移功能的仿射變換矩陣,如下所示:
對於影象縮放來說,設水平方向的縮放因子為a,垂直方向縮放因子為b,則用仿射矩陣實現圖縮放功能的仿射矩陣為:
而對於影象旋轉來說,設旋轉角度為θ,利用仿射變換實現影象旋轉操作的仿射矩陣為:
而對於較為特殊的斜切變換,同樣的,設斜切的角度為θ,則仿射矩陣為:
需要注意的是,在OpenCV中使用仿射變換函式是,通常會先計算一個仿射變換矩陣,以此來獲得放射變換矩陣,為了實現這個功能,常常使用getRotationMatrix2D()函式用來計算二維旋轉矩陣,這個變換會將旋轉中心對映到它自身。這裡給出它的函式宣告:
Mat getRotationMatrix2D( Point2f center, double angle, double scale );
這個函式中有三個引數,第一個引數是Point2f型別的center,也就是原影象的旋轉中心;第二個引數是double 型別的angle,也就是我們說的旋轉角度,值得一提的是,當angle的值為正時,表示的是逆時針旋轉,當angle的值為負時,表示的是順時針旋轉。第三個引數scale表示的是縮放係數,在這個函式計算的是下面這個矩陣:
其中:
最後補充一下warpAffine的函式宣告和引數意義:
void warpAffine( InputArray src, OutputArray dst,InputArray M, Size dsize,int flags=INTER_LINEAR,nt borderMode=BORDER_CONSTANT,const Scalar& borderValue=Scalar());
第一個引數和第二個引數分別表示原影象和函式操作結束後輸出的影象,二者的尺寸和型別應該相等,只需要填Mat類的物件即可。第三個引數是一個Mat型別的矩陣M,是一個2x3大小的變換矩陣,第四個引數是Size型別的dsize,表示輸出影象的尺寸,第五個引數是int型別的flags,表示的是不同插值方法的識別符號,這個引數擁有預設值CV_INTER_LINEAR(線性插值),第六個引數是int型別的bodertype,表示的是邊界畫素模式,也擁有預設值CV_BODER_CONSTANT。第七個引數為const
Scalar&型別的boderValue,預設值為0.
下面使用warpAffine函式實現影象旋轉的這一簡單功能。
//使用opencv的warpAffine函式對影象進行旋轉
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage, dstImage;
srcImage = imread("2345.jpg");
if(!srcImage.data)
{
cout << "讀入圖片有誤!" << endl;
return -1;
}
imshow("原影象", srcImage);
dstImage.create(srcImage.size(), srcImage.type());
double degree;
cout << "請輸入旋轉角度:";
cin >> degree;
double a = sin(degree * CV_PI / 180);
double b = cos(degree * CV_PI / 180);
int width = srcImage.cols;
int height = srcImage.rows;
int rotate_width = int(height * fabs(a) + width * fabs(b));
int rotate_height = int(width * fabs(a) + height * fabs(b));
Point center = Point(srcImage.cols / 2, srcImage.rows / 2);
Mat map_matrix = getRotationMatrix2D(center, degree, 1.0);
map_matrix.at<double>(0, 2) += (rotate_width - width) / 2; // 修改座標偏移
map_matrix.at<double>(1, 2) += (rotate_height - height) / 2; // 修改座標偏移
warpAffine(srcImage, dstImage, map_matrix, { rotate_width, rotate_height }, CV_INTER_CUBIC);
imshow("旋轉後的影象", dstImage);
waitKey();
return 0;
執行後設置旋轉角度為+30°,程式的結果如下: