OpenCV常見的圖像變換

拉伸、收縮、扭曲和旋轉

最簡單的圖像變換是調整圖像大小,使其變大或變小。但實際操作時要比想象的複雜一些,因爲調整大小帶來了像素如何插值(放大)或合併(減少)的問題。

均勻調整

cv::resize()

void cv::resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=cv::INTER_LINEAR)

作用:根據設置調整圖像爲指定大小,其中有兩種指定方式:① 絕對尺寸:通過dsize直接設置;② 相對尺寸:dsize設置爲cv::Size(0,0),然後將fx和fy設置爲我們想要的比例因子即可。

插值方法
插值 含義
cv::INTER_LINEAR 雙線性插值法
cv::INTER_NEAREST 最近鄰插值
cv::INTER_AREA 像素區域重採樣
cv::INTER_CUBIC 雙三次插值
cv::INTER_LANCZ0S4 插值(超過8×8個鄰域)
#include "stdafx.h"
#include <opencv2/opencv.hpp>


int main()
{
	cv::namedWindow("image", cv::WINDOW_NORMAL);
	cv::namedWindow("nearest", cv::WINDOW_NORMAL);
	cv::namedWindow("linear", cv::WINDOW_NORMAL);
	cv::namedWindow("area", cv::WINDOW_NORMAL);
	cv::namedWindow("cubic", cv::WINDOW_NORMAL);
	cv::namedWindow("lancz0s4", cv::WINDOW_NORMAL);
	cv::Mat image = cv::imread("D:\\personal-data\\wallpapers\\cat.jpg");
	cv::Mat nearest, linear, area, cubic, lancz0s4;
	cv::resize(image, nearest, cv::Size(0, 0), 2, 2);
	cv::resize(image, linear, cv::Size(0, 0), 2, 2);
	cv::resize(image, area, cv::Size(0, 0), 2, 2);
	cv::resize(image, cubic, cv::Size(0, 0), 2, 2);
	cv::resize(image, lancz0s4, cv::Size(0, 0), 2, 2);
	cv::imshow("image", image);
	cv::imshow("nearest", nearest);
	cv::imshow("linear", linear);
	cv::imshow("area", area);
	cv::imshow("cubic", cubic);
	cv::imshow("lancz0s4", lancz0s4);
	cv::waitKey(0);
	cv::destroyAllWindows();
    return 0;
}

圖像金字塔

關於圖像金字塔,在之前的學習過程中已經詳細介紹過,詳細可以點擊鏈接:OpenCV圖像金字塔

其中詳細介紹了其原理,以及生成金字塔的過程。在這裏將補充一個自動生成高斯金字塔的方法:

void cv::buildPyramid(InputArray src, OutputArrayOfArrays dst, int maxlevel)

不均勻映射

可以拉伸、收縮、扭曲或轉換圖像的功能稱爲幾何變換。對於平面區域,有兩種幾何變換:使用2×3矩陣的變換稱爲“仿射變換”;使用3×3矩陣的變換稱爲“透視變換”或“同形”。

仿射變換

仿射變換是可以以矩陣乘法後跟向量加法的形式表示的任何變換。在OpenCV中,該變換的標準樣式是2×3矩陣。定義如下:

A\equiv \begin{bmatrix} a_{00} &a_{01} \\ a_{10}& a_{11} \end{bmatrix}B\equiv \begin{bmatrix} b_{0}\\ b_{1} \end{bmatrix}T\equiv \begin{bmatrix} A & B \end{bmatrix}X\equiv \begin{bmatrix} x\\ y \end{bmatrix}X'\equiv \begin{bmatrix} x\\ y\\ 1 \end{bmatrix}

仿射變換可以將矩形轉換爲平行四邊形,它們可以擠壓形狀,但是必須保持對邊平行,可以旋轉或縮放。

密集仿射變換函數cv::warpAffine()

void cv::warpAffine(InputArray src, OutputArray dst, InputArray M, cv::Size dsize, int flags=cv::INTER_LINEAR, int borderMode=cv::BORDER_CONSTANT, const cv::Scalar& borderValue=cv::Scalar())

作用:對輸入矩陣做仿射變換,爲了輸出圖像平滑而自然,需要處理內插。M爲前面提到的2×3的矩陣;dsize是目標圖像尺寸;flags是插值方法,和resize()方法裏的插值方法相同,這裏有個附加個cv::WARP_INVERSE_MAP(組合使用),用於指明是否可以反向變換;最後兩個參數則是邊界外推時所用的參數。

dst(x,y)=src(M_{00}x+M_{01}y+M_{02}, M_{10}x+M_{11}y+M_{12})

計算仿射映射矩陣的cv::getAffineTransform()和cv::getRotationMatrix2D()

cv::Mat cv::getAffineTransform(const cv::Point2f* src, const cv::Point2f* dst)

作用:生成映射矩陣M。src和dst是三個二維(x,y)點的數組。

cv::Mat cv::getRotationMatrix2D(cv::Point2f center, double angle, double scale)

作用:生成映射矩陣M。center是旋轉中心,angle是旋轉中心,scale是重新調整。

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;

int main()
{
	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	if (src.empty())
	{
		return -1;
	}

	cv::Point2f srcTri[] = { cv::Point2f(0, 0),cv::Point2f(src.cols - 1, 0),cv::Point2f(0, src.rows - 1) };
	cv::Point2f dstTri[] = { cv::Point2f(src.cols * 0.f, src.rows * 0.33f),cv::Point2f(src.cols * 0.85f, src.rows * 0.25f), cv::Point2f(src.cols * 0.15, src.rows * 0.7f) };
	cv::Mat warp_mat = cv::getAffineTransform(srcTri, dstTri);
	cv::Mat dst, dst2;
	cv::warpAffine(src, dst, warp_mat, src.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar());
	for (int i = 0; i < 3; ++i)
	{
		cv::circle(dst, dstTri[i], 5, cv::Scalar(255, 0, 255), -1, cv::LINE_AA);
	}
	cv::imshow("affineTransform", dst);
	cv::waitKey(0);

	for (int frame = 0;; ++frame) {
		cv::Point2f center(src.cols * 0.5f, src.rows * 0.5f);
		double angle = frame * 3 % 360;
		double scale = (cos((angle - 60) * CV_PI / 180) + 1.05) * 0.8;
		cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, scale);
		cv::warpAffine(src, dst, rot_mat, src.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar());
		cv::imshow("Rotated image", dst);
		if (cv::waitKey(30) >= 0) {
			break;
		}
	}
	cv::destroyAllWindows();
    return 0;
}

cv::transform()用於稀疏仿射變換

void cv::transform(InputArray src, OutputArray dst, InputArray mtx)

作用:生成稀疏矩陣的仿射變換。輸入輸出都是N×1,但是通道數可能不同,分別爲Ds和Dd,這mtx是Ds×Dd的矩陣。

cv::invertAffineTransform()用於逆仿射變換

void cv::invertAffineTransform(InputArray M, OutputArray iM)

透視變換

透視投影完全由單個矩陣指定,但實際上不是線性變換,因爲變換需要通過最終維度進行劃分,從而在過程中失去一個維度。

cv::warpPerspective()用於密集透視變換

void cv::warpPerspective(InputArray src, OutputArray dst, InputArray M, cv::Size dsize, int flags=cv::INTER_LINEAR, int borderMode=cv::BORDER_CONSTANT, const cv::Scalar& borderValue=cv::Scalar())

作用:對輸入矩陣做透視變換,爲了輸出圖像平滑而自然,需要處理內插。M爲前面提到的3×3的矩陣;dsize是目標圖像尺寸;flags是插值方法,和resize()方法裏的插值方法相同,這裏有個附加個cv::WARP_INVERSE_MAP(組合使用),用於指明是否可以反向變換;最後兩個參數則是邊界外推時所用的參數。

dst(x,y)=src(\frac{M_{00}x+M_{01}y+M_{02}}{M_{20}x+M_{21}y+M_{22}},\frac{M_{10}x+M_{11}y+M_{12}}{M_{20}x+M_{21}y+M_{22}})

cv::getPerspectiveTransform()用於計算透視映射矩陣

cv::Mat cv::getPerspectiveTransform(const cv::Point2f* src, const cv::Point2f* dst)

作用:生成映射矩陣M。src和dst是四個二維(x,y)點的數組。

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;

int main()
{
	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	if (src.empty()) {
		return -1;
	}

	cv::Point2f srcQuad[] = { cv::Point2f(0, 0), cv::Point2f(src.cols - 1, 0), cv::Point2f(src.cols - 1, src.rows - 1), cv::Point2f(0, src.rows - 1) };
	cv::Point2f dstQuad[] = { cv::Point2f(src.cols * 0.05f, src.rows * 0.33f), cv::Point2f(src.cols * 0.9f, src.rows * 0.25f), cv::Point2f(src.cols * 0.8f, src.rows * 0.9f), cv::Point2f(src.cols * 0.2f, src.rows * 0.7f) };

	cv::Mat warp_mat = cv::getPerspectiveTransform(srcQuad, dstQuad);
	cv::Mat dst;
	cv::warpPerspective(src, dst, warp_mat, src.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar());
	for (int i = 0; i < 4; i++)
	{
		cv::circle(dst, dstQuad[i], 5, cv::Scalar(255, 0, 255), -1, cv::LINE_AA);
	}
	cv::imshow("dst", dst);
	cv::waitKey(0);
	cv::destroyAllWindows();
    return 0;
}

cv::perspectiveTransform()用於稀疏透視變換

 cv::perspectiveTransform()是一個特殊的函數,可以對點列陣表執行透視變換。因爲cv::transform()被限定於線性運算,所有不能正確地處理透視變換。這是因爲這種變換需要由同質表示的第三個座標(x=f * x / Z, y=f * y /Z)來劃分,所以提供了cv::perspectiveTransform()函數:

void cv::perspectiveTransform(InputArray src, OutputArray dst, InputArray mtx)

作用:生成稀疏矩陣的透視變換。輸入輸出都是N×1,但是通道數可能是2或3,矩陣mtx可以是3×3(投影從二維到二維)或4×4(投影從三維到三維)的矩陣。

通用變換

極座標變換

cv::cartToPolar()用於將直角座標轉換爲極座標

void cv::cartToPolar(InputArray x, InputArray y, OutputArray magnitude, OutputArray angle, bool angleInDegrees=false)

原理:x,y(單通道)表示的並不僅僅是一系列的點,它還代表着向量場。該函數返回結果保存着數組的大小和角度,大小表示該點處x和y向量的長度,並且角度表示該向量的方向。如果angleInDegrees設置爲true,則角度來保存,否則默認以弧度保存。角度值使用atan2(y, x)進行計算。

cv::polarToPolar()用於將極座標轉換爲直角座標

void cv::polarToPolar(InputArray magnitude,InputArray angle, OutputArray x, OutputArray y, bool angleInDegrees=false)

和cv::cartToPolar()正好相反,求逆的過程。

LogPolar

對於二維圖像,對數-極座標變換是從直角座標變換爲對數極座標,即(x,y)\leftrightarrow re^{i\theta },其中,r=\sqrt{x^2+y^2},\theta =atan2(y,x),然後將極座標表示爲a(\rho ,\theta )形式。當我們需要把一些有趣的東西填充到目標圖像的空白區域,通常的做法是將比例因子m應用於ρ。

cv::logPolar()函數

void cv::logPolar(InputArray src, OutputArray dst, cv::Point2f center, double m, int flags=cv::INTER_LINEAR | cv::WARP_FILL_OUTLIERS)

作用:進行對數-極座標轉換。center是對數-極座標變換的中心點;m是比例因子,這個比例因子的設置原則應該便於最感興趣的特徵能夠存在於圖像的大部分區域。flags表示不同的插入方式,cv::WARP_FILL_OUTLIERS是附加選項(填充未定點),可組合使用。

#include "stdafx.h"
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;

int main()
{
	cv::Mat src = cv::imread("D:\\personal-data\\wallpapers\\test.png");
	if (src.empty()) {
		return -1;
	}
	
	double M = 100;
	cv::Mat dst(src.size(), src.type()); 
	cv::Mat src2(src.size(), src.type());

	cv::logPolar(src, dst, cv::Point2f(src.cols * 0.5f, src.rows * 0.5f), M, cv::INTER_LINEAR | cv::WARP_FILL_OUTLIERS);
	cv::logPolar(dst, src2, cv::Point2f(src.cols * 0.5f, src.rows * 0.5f), M, cv::INTER_LINEAR | cv::WARP_INVERSE_MAP);
	cv::imshow("image", src);
	cv::imshow("log-polar", dst);
	cv::imshow("inverse log-polar", src2);
	cv::waitKey(0);
	cv::destroyAllWindows();
        return 0;
}

運行結果:

原始圖像

對數-極座標變換

對數-極座標逆變換


任意映射

cv::remap()用於常規圖像重繪

void cv::remap(InputArray src, OutputArray dst, OutputArray map1, OutputArray map2, int interpolation=cv::INTER_:INEAR, int borderMode=cv::BORDER_CONSTANT, const cv::Scalar& borderValue=cv::Scalar())

作用:自定義映射,通常用來糾正校準立體圖像。map1和map2分別表示原圖像上需重新定位的任意一點的x和y的位置。要求:這些圖應與原圖像和目標圖像保持尺寸相同,並且必須使用以下數據類型:cv::S16C2、cv::F32C1、cv::F32C2,函數將根據interpolation設置自動插值,其中插值方法不能爲cv::INTER_AREA。

圖像修復

圖像修復

cv::inpaint(InputArray src, InputArray inpaintMask, OutputArray dst, double InpaintRadius, int flags)

這裏src是一個8位,一維的灰度圖像或者一個三維的彩色的待修復圖像;inpaintMask是一個8位,與src大小相同的一維圖像,且損壞區域被非0像素標記,inpaintMask的其他像素均被設置爲0;dst是輸出圖像,它與src有相同大小和數量的維度;InpaintRadius是每個已渲染像素周圍區域,這一區域將被分解成該像素的結果輸出顏色;flags有兩種不同的修復方法:cv::INPAINT_NS和cv::INPAINT_TELEA。

去噪

在許多應用中,噪聲主要來源於低光條件的影響。在低光下,數字成像器的增益必須增加,結果噪聲也被放大。這種噪聲的特徵隨着孤立的像素,看起來太亮或太暗,但在彩色圖像中也可能發生變色。

OpenCV中實現的去燥算法稱爲“快速非局部均值去燥”(FNLMD)。雖然簡單的去噪算法基本上依賴於對其周邊的各個像素進行平均,但是FNLMD的中心概念是在圖像中的其他地方尋找類似的像素,再對其取平均值。在這種情況下,像素被認爲是相似的像素,不是因爲它的顏色或者強度相似,而是因爲它在環境中是相似的。這裏的關鍵是,許多圖像包含重複的結構,因此即使像素被噪聲破壞,也會有許多其他類似像素。

基於以像素p和大小s爲中心的窗口B(p,s),進行類似像素的識別,在點的周圍給定一個期望更新的窗口,我們可以將這個窗口與其他像素q周圍的窗口進行比較。定義兩窗口之間的平方距離如下:

d^2(B(p,s), B(q,s))=\frac{1}{3(2s+1)}\sum _{c=1}^3\sum _{j\in B(0,s)}(I_c(p+j)-I_c(q+j))^2

其中,c是色彩索引,Ic(P)是圖像上點p在通道c的色彩強度,並且j的總和超過了patch元素。從這個平方距離,可以將權重分配給相對於當前正在更新的像素的每隔一個像素,該權重由下式計算:

w(p,q)=e^{-\frac{max(d^2-2\sigma ^2,0.0)}{h^2}}

其中,σ是噪聲中預測的標準偏差(強度單位),而h是一個通用的過濾參數,可以確定補丁的速度隨着平均距離從我們更新的補丁增長而變得無關緊要。一般來說,增加h的值將增加去除的噪聲,但是會犧牲一些圖像細節;降低h的值會保留細節,同時增加噪聲。

通常情況下,考慮到選擇與要更新的補丁相距較遠(以像素的爲單位)的點,返回結果會相應遞減,因爲在一定距離內,這樣的補丁數量呈平方增加。因此,通常會定義一個稱爲“搜索窗口”的整體區域,只有搜索窗口中的修補程序有助於更新。然後使用給定的指數衰減權重函數,通過搜索窗口中所有其他像素的簡單加權平均值來給出當前像素的更新。

FNLMD基礎算法cv::fastNIMeansDenoising()

void cv::fastNIMeansDenoising(InputArray src, OutputArray dst, float h = 3, int templateWindowSize = 7, int searchWindowSize = 21)

templateWindowSize(用於比較的補丁尺寸)的補丁區域和衰減參數h從輸入數組src計算出輸出數組dst,並考慮searchWindowSize(允許範圍內補丁的最大值)距離內的補丁。圖像可以是一維、二維或三維的,但必須是cv::U8類型。

cv::fastNIMeansDenoising()的灰度圖像的推薦值
噪聲:σ 塊大小:s 搜索窗口 衰減參數:h
0 < σ ≤ 15 3 × 3 21 × 21 0.40 · σ
15 < σ ≤ 30 5 × 5 21 × 21 0.40 · σ
30 < σ ≤ 45 7 × 7 35 × 35 0.35 · σ
45 < σ ≤ 75 9 × 9 35 × 35 0.35 · σ
75 < σ ≤ 100 11 × 11 35 × 35 0.30 · σ

FNLMD彩色圖像算法cv::fastNIMeansDenoisingColored()

void cv::fastNIMeansDenoisingColored(InputArray src,OutputArray dst,float h=3,float hColor=3,int templateWindowSize = 7, int searchWindowSize = 21)

該函數只接受cv::U8C3類型的圖像,雖然原則上可能直接將算法或多或少地應用於RGB圖像,但實際上,最好將圖像轉換爲不同的色彩空間進行計算。首先將圖像轉爲LAB顏色空間,然後應用FNLMD算法,然後將結果轉換回RGB。其主要優點是在顏色方面實際上有三個衰減參數。然而在RGB表示中,不太可能將其中任何一個設置爲不同的值。但是在LAB空間中,爲顏色分量指定不同的衰減參數對於亮度分量是很自然的。h用於亮度衰減參數,而新參數hColor用於顏色通道。一般來說,hColor的值會比h小很多。在大多數情況下,10是一個合適的值。

cv::fastNIMeansDenoisingColored()彩色圖像的推薦值
噪聲:σ 塊大小:s 搜索窗口 衰減參數:h
0 < σ ≤ 25 3 × 3 21 × 21 0.55 · σ
25 < σ ≤ 55 5 × 5 35 × 35 0.40 · σ
55 < σ ≤ 100 7 × 7 35 × 35 0.35 · σ

FNLMD視頻圖像算法cv::fastNIMeansDenoisingMulti()和cv::fastNIMeansDenoisingColoredMulti()

void cv::fastNIMeansDenoisingMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h=3, int templateWindowSize = 7, int searchWindowSize = 21)

void cv::fastNIMeansDenoisingColoredMulti(InputArrayOfArrays srcImgs, OutputArray dst, int imgToDenoiseIndex, int temporalWindowSize, float h=3, float hColor=3, int templateWindowSize = 7, int searchWindowSize = 21)

這兩個函數用於順序圖像,在這種情況下,很自然地想象出來當前幀以外的幀很可能包含去噪像素的有用信息。在大多數應用中,圖像之間的噪聲不會是恆定的,而信號可能會相似甚至相同。這兩個函數期望一個圖像數組,並通過imgToDenoiseIndex告知函數序列中哪個圖像是要被去噪的。最後必須提供一個時間窗口,指示在去噪中使用的序列中的圖像數量,該參數必須爲奇數,隱含窗口始終以imgToDenoiseIndex爲中心。

直方圖均衡化

void cv::equalizeHist(const InputArray src, OutputArray dst)

作用:用於對比均衡,src和dst都必須是單通道圖像,所以對於彩色圖像,必須分通道逐一處理。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章