1 摘要
上一章主要介紹了圖像卷積操作,此外我們還可以對圖像做很多有趣的操作,如使用一個(gè)較小的窗口在圖像上掃描以完成其他任務(wù)等。實(shí)際上即使卷積操作能夠改變整個(gè)圖像的效果,但是對于特定的像素,其改變僅由其周圍少量像素決定。而本章將要覆蓋那些不滿足此規(guī)律的通用圖像操作。
一些非常有用的圖像變換(Image Transforms)都比較簡單,并且你可能會(huì)經(jīng)常使用到它們,如調(diào)整圖像大小(Resize)。本章將要介紹的圖像操作的輸出圖像在尺寸上或者某種方式上與原圖并不一致,但是感覺上它們?nèi)允峭粡垐D片。而在下一章將會(huì)介紹一些處理結(jié)果完全是另外一幅圖像的變換方法。
2 重采樣
2.1 簡單重采樣
有時(shí)我們需要將某個(gè)邏輯分辨率的圖像(尺寸)轉(zhuǎn)換為另一個(gè)分辨率,即我們需要調(diào)整圖像的大小。這種操作可能并不像想象的那么簡單,因?yàn)閷τ诶於陨婕暗较袼氐牟逯颠\(yùn)算,對于收縮而言涉及到像素的合并運(yùn)算。負(fù)責(zé)處理該認(rèn)為的函數(shù)原型如下。
// src:待處理的圖像
// dst:處理好的圖像
// dsize:目標(biāo)分辨率,當(dāng)設(shè)置為cv::Size(0, 0)時(shí)表示由縮放系數(shù)fx和fy決定目標(biāo)圖像大小
// fx,fy:x和y軸上的縮放系數(shù),當(dāng)它們都為0時(shí)表示使用參數(shù)dsize決定目標(biāo)尺寸大小,
// 需要注意這兩種策略必須選擇一個(gè)
// interpolation:像素插值策略,具體下文介紹
void cv::resize(cv::InputArray src, cv::OutputArray dst,
cv::Size dsize, double fx = 0, double fy = 0,
int interpolation = CV::INTER_LINEAR}
參數(shù)interpolation的取值和含義如下表。
| 參數(shù)interpolation取值 | 含義 |
|---|---|
| cv::INTER_NEAREST | 最近相鄰像素 |
| cv::INTER_LINEAR | 雙線性插值 |
| cv::INTER_AREA | 像素區(qū)域重采樣 |
| cv::INTER_CUBIC | 雙三次插值 |
| cv::INTER_LANCZOS4 | 在8??8區(qū)域上插值 |
這個(gè)操作的核心問題是插值問題,圖像可以看作是離散的網(wǎng)格數(shù)據(jù),即在元素圖像中的每個(gè)像素都存在一個(gè)整型坐標(biāo),如對于像素P(20, 17)表示第20行第17個(gè)像素。當(dāng)其目標(biāo)大小與元素目標(biāo)不同,如更小時(shí)該像素可能被映射至一個(gè)小數(shù)坐標(biāo),則需要特殊處理?;蛘呤悄繕?biāo)尺寸更大時(shí),目標(biāo)圖像的有些相似無法從原圖中直接映射。這些問題都稱為前向投影(Forward Projection)問題,通常使用逆向思維解決,即目標(biāo)像素應(yīng)從源像素的那個(gè)地方取值。而通過目標(biāo)圖像像素坐標(biāo)計(jì)算出的源圖參考像素坐標(biāo)通常不是整數(shù),此時(shí)就需要對其附近像素進(jìn)行插值處理。
最簡單的方式就是使用離參考坐標(biāo)最近的像素,即使用cv::INTER_NEAREST選項(xiàng)?;蛘咭部梢苑謩e使用參考坐標(biāo)周圍的2??2區(qū)域內(nèi)的像素和它們在對應(yīng)軸上的距離作為權(quán)重進(jìn)行線性插值計(jì)算最終像素值,即使用cv::INTER_LINEAR選項(xiàng)。甚至可以計(jì)算參考坐標(biāo)覆蓋像素區(qū)域的平均值,即選用cv::INTER_AREA,但需要注意的是這種方式只對縮小圖像有效,對于放大圖像,該選項(xiàng)和cv::INTER_NEAREST效果相同。為了得到更光滑的插值效果,也可以選用參考坐標(biāo)周圍的4??4擬合三次樣條插值,最后根據(jù)參考坐標(biāo)的距離計(jì)算出最終的像素值,即使用cv::INTER_CUBIC。最后還可以使用Lanczos插值算法,它與三次樣條插值類似,但是采樣范圍擴(kuò)展至參考坐標(biāo)周圍8??8區(qū)域。
需要注意函數(shù)cv::resize()和cv::Mat::resize()的區(qū)域,前者上文已經(jīng)講解,后者對于超出目標(biāo)尺寸的原圖直接裁剪,沒有插值處理。
2.2 圖像金字塔
圖像金字塔指對原圖像不斷下采樣,直至到想要的分辨率,這個(gè)期望可以是只包含一個(gè)像素的圖像,這種技術(shù)被廣泛應(yīng)用于各種視覺程序中。圖像金字塔分為高斯(Gaussian)和拉普拉斯(Laplacian)圖像金字塔。高斯金字塔指的是下采樣圖像,而拉普拉斯金字塔指的是從已經(jīng)下采樣的圖像中重構(gòu)原始圖像。
2.2.1 高斯金字塔
向下逐層采樣
高斯金字塔的每一層Gi+1都是由其下一層Gi構(gòu)建而出,其中G0為原始圖像。構(gòu)建的方式是首先對Gi層應(yīng)用高斯卷積核,然后再移除所有的偶數(shù)行和列,從而得到Gi+1層。這樣得到的每一層圖像分辨率都是上一層的1/4,通過不斷迭代最后就能得到高斯金字塔。高斯金字塔構(gòu)建層的采樣函數(shù)原型如下。
// src:輸入圖像
// dst:處理結(jié)果
// dstsize:輸出圖像的尺寸,該參數(shù)下文介紹
void cv::pyrDown(cv::InputArray src, cv::OutputArray dst,
const cv::Size& dstsize = cv::Size());
函數(shù)cv::pyrDown的參數(shù)dstsize默認(rèn)值為cv::Size(),此時(shí)該參數(shù)不影響函數(shù)的結(jié)果。而此時(shí)輸出圖像的尺寸為((src.cols+1)/2, (src.rows+1)/2),其中行列的加1只是對行列為奇數(shù)的輸入圖像做的特殊調(diào)整,對偶數(shù)行列的原始圖像而言,將不會(huì)有+1操作。對于一些特別復(fù)雜的場景而言,當(dāng)需要嚴(yán)格控制輸出圖像的尺寸時(shí),可以通過設(shè)置參數(shù)dstsize完成,但是它們需要嚴(yán)格滿足如下條件。

構(gòu)建金字塔
直接使用原圖像構(gòu)建整個(gè)金字塔圖像序列的函數(shù)原型如下。
// src:輸入圖像
// dst:處理結(jié)果,構(gòu)成金字塔的圖像序列,可以理解為成員為cv::OutputArray的STL向量實(shí)例,
// 通常為vector<cv::Mat>
// maxlevel:需要構(gòu)建金字塔最高層數(shù)
void cv::buildPyramid(cv::InputArray src, cv::OutputArrayOfArrays dst,
int maxlevel);
該函數(shù)運(yùn)行后,得到的結(jié)果是一個(gè)標(biāo)準(zhǔn)向量容器,其中第一個(gè)元素為輸入圖像,其后的每一幅圖像的分辨率都是上一副圖像的1/4,最終這些圖像可以構(gòu)建出下圖中左側(cè)圖像表示的金字塔結(jié)構(gòu)。

在實(shí)際應(yīng)用中,我們可能需要更細(xì)粒度的金字塔結(jié)構(gòu),如上圖右側(cè)圖像是使用兩個(gè)金字塔交錯(cuò)得到的細(xì)粒度結(jié)構(gòu)。一種實(shí)現(xiàn)方式是使用函數(shù)cv::resizec()處理上圖左側(cè)圖像中的每一張圖片,但是明顯這種方式效率較低。
另外一種方式是只使用函數(shù)cv::resizec()處理原始圖像,然后在使用函數(shù)void cv::buildPyramid處理縮放后的原始圖像,最后再將兩次構(gòu)建金字塔的結(jié)果交錯(cuò)排列,從而得到更細(xì)粒度的金字塔結(jié)構(gòu),即上圖右側(cè)分圖。在該圖中,原圖的縮放系數(shù)為根號2,從而保證了金字塔的每一層圖像的寬高比例都為根號2。
向上逐層恢復(fù)
OpenCV也支持通過類似(不是簡單的逆運(yùn)算)高斯金字塔下采樣的方式對圖像進(jìn)行上采樣構(gòu)建長寬都為原圖兩倍的新圖像,其函數(shù)原型如下。
// src:輸入圖像
// dst:處理結(jié)果
// dstsize:輸出圖像的尺寸,下文具體介紹
void cv::pyrUp(cv::InputArray src, cv::OutputArray dst,
const cv::Size& dstsize = cv::Size());
該函數(shù)的內(nèi)部邏輯是首先為原始圖像每列和每行后添加新的行列,并填充為0,然后對每個(gè)像素應(yīng)用高斯濾波器,從而計(jì)算出這些新增像素的值。需要注意此時(shí)高斯濾波的卷積核權(quán)重系數(shù)和為4,這是因?yàn)橥ㄟ^插入值為0的行列后將1個(gè)像素?cái)U(kuò)展為4個(gè)像素,為例恢復(fù)平均亮度,需要將權(quán)重和設(shè)置為4.
和函數(shù)cv::pyrDown類似,參數(shù)dstsize可以更精細(xì)的控制輸出圖像的分辨率,默認(rèn)情況下寬高都是原始圖像的兩倍。同樣的參數(shù)dstsize的設(shè)置必須滿足如下條件,即其值非常接近于原始圖像的兩倍。

2.2.2 拉普拉斯金字塔
前文說過函數(shù)cv::pyrUp不是函數(shù)cv::pyrDown的簡單逆運(yùn)算,一個(gè)最直接的證據(jù)就是它也是一個(gè)丟失信息的運(yùn)算。為了恢復(fù)原始高分辨率圖像,我們需要訪問在下采樣過程中被丟失掉的數(shù)據(jù),這些數(shù)據(jù)形成了拉普拉斯金字塔(Laplacian Pyramid)。拉普拉斯金字塔的第i層可以通過如下公式表示。

上述公式的UP運(yùn)算表示的是對高斯金字塔的Gi+1層執(zhí)行寬高都擴(kuò)展為2倍的上采樣操作,而g5??5表示使用大小為5??5的高斯濾波器,而這正是函數(shù)cv::pyrUp使用的濾波器,因此上述公式可以表示如下。

因此使用高斯金字塔塔的某一層圖像向下構(gòu)建底層原始圖像的過程包含高斯金字塔上采樣,以及與對應(yīng)層的拉普拉斯金字塔相加兩個(gè)步驟。
3 幾何變換
接下來將會(huì)介紹一些圖像的幾何變換(Geometric Transformation),這些變換都和立體幾何以及投影幾何(Projective Geometry)相關(guān)。其中包含圖像的縮放、平移、旋轉(zhuǎn)和投影等,你可能會(huì)在很多場景需要使用到這些變化,如將圖像投影到墻上(VR程序)和已經(jīng)存在的場景進(jìn)行混合,或者通過這些變換擴(kuò)大物體識別的訓(xùn)練集合(機(jī)器學(xué)習(xí))。
對于一個(gè)平面圖形,幾何變換有兩種形式,分別是基于2??3矩陣的仿射變換(Affine Transforms)和基于3??3的投影變換(Perspective Transorms or Homographies)。它們的區(qū)別是假定原圖形位于某個(gè)平面內(nèi),則仿射變換得到的結(jié)果都是正視這個(gè)平面所觀察到的效果,而投影變換可能是從某個(gè)特別的角度看到的效果。
某個(gè)圖形仿射變換的結(jié)果可以是其中的每個(gè)頂點(diǎn)的坐標(biāo)列向量左乘一個(gè)2??2矩陣變換A,再加一個(gè)平移矩陣B構(gòu)建出。即對于原始圖形某點(diǎn)Xold,其變換后的點(diǎn)Xnew可以表示為Xnew = A??Xold + B。也可以合并AB矩陣得到變換矩陣T,并擴(kuò)展原始坐標(biāo)Xold至三維,得到Xe,則變換后的點(diǎn)Xnew可以表示為Xnew = T??Xold。

平面內(nèi)的一個(gè)矩形的仿射變換結(jié)果是一個(gè)平行四邊行,而由于改變了觀察點(diǎn),因此矩形的透視變換的結(jié)果可能是一個(gè)任意形狀的四邊形。一些幾何變換的示例如下圖。

當(dāng)已知多個(gè)圖形是屬于同一類形狀時(shí),通常使用仿射變換來描述它們之間的關(guān)系,而不使用透視變換,因?yàn)榉律渥儞Q需要的參數(shù)更少,解決問題也更容易。缺點(diǎn)是仿射變換不能非常準(zhǔn)確的描述所有圖形之間的聯(lián)系,而真正的透視變換需要通過單應(yīng)性(Homography)才能建模。另外一方面對于由于觀察點(diǎn)發(fā)生變化而導(dǎo)致看到的圖形出現(xiàn)輕微的形變(不屬于同一類形狀)時(shí),如果在誤差允許范圍內(nèi),也可以直接使用仿射變換來描述它們之間的聯(lián)系。
單應(yīng)性是一個(gè)數(shù)學(xué)術(shù)語,表示將一個(gè)點(diǎn)從一個(gè)平面映射到另外一個(gè)平面。而在計(jì)算機(jī)視覺的語境下,其含義更狹隘,特指具體相同模型坐標(biāo)(以模型坐標(biāo)系為參考的坐標(biāo))的兩個(gè)不同圖像平面(通常是世界坐標(biāo)系中的圖像平面和投影坐標(biāo)系上的投影平面)上的點(diǎn)的映射。這種映射關(guān)系可以通過一個(gè)3??3的正交矩陣表示。
3.1 仿射變換
通常需要應(yīng)用到仿射變換的情況有兩種,第一種是需要對某一幅圖像,或者圖像內(nèi)部的興趣區(qū)域執(zhí)行仿射變換,第二種是需要計(jì)算一系列點(diǎn)點(diǎn)仿射變換結(jié)果。這兩種情況在概念上都是計(jì)算仿射變換,但是實(shí)際實(shí)現(xiàn)上有很大的區(qū)別,因此OpenGL提供了兩組函數(shù)來處理它們,分別是稠密仿射變換和稀疏仿射變換。
3.1.1 稠密仿射變換
稠密變換應(yīng)用于圖像處理,這意味著內(nèi)部包含像素的合并和插值策略,從而確保得到的圖像是光滑的,看上去自然的。其函數(shù)原型如下。
// src:輸入圖像
// dst:計(jì)算結(jié)果
// M:仿射變換矩陣,尺寸為2??3
// dsize:輸出圖像的大小
// flags:像素插值策略,取值參考函數(shù)cv::resize
// borderMode:邊框擴(kuò)展策略,參考上一張濾鏡和卷積中的相關(guān)定義
// borderValue:當(dāng)參數(shù)borderMode選擇使用常量時(shí),使用的常量值
void cv::warpAffine(cv::InputArray src, cv::OutputArray dst,
cv::InputArray M, cv::Size dsize, int flags = cv::INTER_LINEAR,
int borderMode = cv::BORDER_CONSTANT,
const cv::Scalar& borderValue = cv::Scalar());
計(jì)算結(jié)果dst中的每個(gè)像素都是由原始圖像中的像素中的某個(gè)像素計(jì)算而得,其公式如下。需要注意等式左側(cè)和右側(cè)的x和y值不相同。

通過該公式計(jì)算出的原始圖像的參考像素坐標(biāo)不一定是整數(shù),此時(shí)就需要通過參數(shù)flags定義的策略進(jìn)行插值計(jì)算。參數(shù)flags取值除了函數(shù)cv::resize的可取值外,還額外支持cv::WARP_INVERSE_MAP,可以使用邏輯與符號和其他選項(xiàng)疊加使用,表示從dst到src的反向變換,而不是從src到dst的包裝方式。
除了通過數(shù)學(xué)變換計(jì)算仿射矩陣外,還可以通過如下方式計(jì)算仿射矩陣。即通過三個(gè)頂點(diǎn)可以定義一個(gè)平行四邊形,通過變換前后的平行四邊形可以計(jì)算出仿射變換(以坐標(biāo)原點(diǎn)為參考系)的矩陣,OpenCV實(shí)現(xiàn)該功能的函數(shù)原型如下。
// 返回值:計(jì)算得到的仿射變換矩陣
// src:變換前的三個(gè)頂點(diǎn)坐標(biāo)
// dst:變換后的三個(gè)坐標(biāo)
cv::Mat cv::getAffineTransform(const cv::Point2f* src, const cv::Point2f* dst);
示例AffineTransform使用函數(shù)cv::getAffineTransform獲取了仿射變換矩陣,然后對輸入圖像應(yīng)用這個(gè)仿射變換后顯示結(jié)果,最后連續(xù)旋轉(zhuǎn)并展示圖像直至用戶輸入任意鍵,其核心代碼如下。
int main(int argc, const char * argv[]) {
// 讀取原圖
cv::Mat src = cv::imread(argv[1], cv::IMREAD_COLOR);
// 定義旋轉(zhuǎn)之前圖像輪廓頂點(diǎn)坐標(biāo)
cv::Point2f srcTri[] = {
cv::Point2f(0,0), // 左上
cv::Point2f(src.cols - 1, 0), // 右上
cv::Point2f(0, src.rows - 1) // 左下
};
// 定義旋轉(zhuǎn)之后圖像輪廓頂點(diǎn)坐標(biāo)
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.15f, src.rows * 0.7f) // 左下
};
// 計(jì)算仿射矩陣
cv::Mat warp_mat = cv::getAffineTransform(srcTri, dstTri);
cv::Mat dst, dst2;
// 應(yīng)用仿射變換
cv::warpAffine(src, dst, warp_mat, src.size(),
cv::INTER_LINEAR,
cv::BORDER_CONSTANT, cv::Scalar());
// 在仿射變換處理后的圖像上目標(biāo)頂點(diǎn)上繪制圓形
for (int i = 0; i < 3; ++i) {
cv::circle(dst, dstTri[i], 5, cv::Scalar(255, 0, 255), -1, cv::LINE_AA);
}
// 顯示第一次仿射變換的結(jié)果
cv::imshow("Affine Transform Test", dst);
// 刮起程序直至輸入任意鍵
cv::waitKey();
// 定義旋轉(zhuǎn)中心
cv::Point2f center(src.cols * 0.5f, src.rows * 0.5f);
// 不斷旋轉(zhuǎn)縮放圖像
for (int frame = 0; ; ++frame) {
// 定義旋轉(zhuǎn)角度和在xy軸上的縮放系數(shù)
double angle = frame * 3 % 360;
double scale = (cos((angle - 60) * CV_PI/180) + 1.05) * 0.8;
// 計(jì)算旋轉(zhuǎn)縮放矩陣
cv::Mat rot_mat = cv::getRotationMatrix2D(center, angle, scale);
// 應(yīng)用仿射變換
cv::warpAffine(src, dst, rot_mat, src.size(),
cv::INTER_LINEAR,
cv::BORDER_CONSTANT, cv::Scalar());
// 顯示旋轉(zhuǎn)后的圖像
cv::imshow("Rotated Image", dst);
// 等待用戶輸入任意鍵結(jié)束程序
if (cv::waitKey(30) >= 0) {
break;
}
}
return 0;
}
上面的代碼使用到了另外一種計(jì)算仿射矩陣的方式,即函數(shù)cv::getRotationMatrix2D,該函數(shù)可以計(jì)算繞任意點(diǎn)的旋轉(zhuǎn)縮放矩陣,其原型如下。
// 返回值:2??3的旋轉(zhuǎn)矩陣
// center:旋轉(zhuǎn)中心
// angle:旋轉(zhuǎn)角度
// scale:旋轉(zhuǎn)后在x軸和y軸上的縮放系數(shù)
cv::Mat cv::getRotationMatrix2D(cv::Point2f center, double angle, double scale);
該函數(shù)構(gòu)建出的仿射矩陣如下圖,其中α = scale??cos(angle),β = scale??sin(angle)。

該示例程序的運(yùn)行效果如下圖,左側(cè)是原圖,中間是應(yīng)用函數(shù)cv::getAffineTransform構(gòu)建仿射矩陣的效果,右側(cè)是通過函數(shù)cv::getRotationMatrix2D構(gòu)建仿射矩陣的效果。

3.1.2 稀疏仿射變換
稀疏仿射變換用于處理一組坐標(biāo),其函數(shù)原型如下。源矩陣src中的每個(gè)元素都會(huì)應(yīng)用仿射變換最后得到輸出矩陣dst。
// src:帶處理的坐標(biāo)數(shù)組,通道數(shù)為Ds的N??1矩陣,每個(gè)元素表示為列向量
// dst:目標(biāo)輸出矩陣,通道數(shù)為Dd的N??1矩陣,每個(gè)元素表示為列向量
// mtx:仿射變換矩陣,尺寸為Dd??Ds,右乘輸入向量
void cv::transform(cv::InputArray src, cv::OutputArray dst, cv::InputArray mtx);
平面內(nèi)的坐標(biāo)由兩個(gè)分量表示,即處理平面圖像時(shí)參數(shù)src是一個(gè)雙通道矩陣,對于不包含平移的仿射變換而言,仿射矩陣通常是2??2的,得到的輸出結(jié)果也是雙通道矩陣。但是對于更通用的包含平移的仿射矩陣而言,通常將平面坐標(biāo)擴(kuò)充至三維齊次坐標(biāo),并將第三個(gè)分量設(shè)置為1,然后使用2??3的仿射向量計(jì)算,這種方式的輸出向量仍是一個(gè)雙通道矩陣。
3.1.2 逆仿射變換
OpenCV還提供函數(shù)通過一個(gè)已知的仿射變換矩陣計(jì)算實(shí)現(xiàn)其逆變換所需要的矩陣,其函數(shù)原型如下。當(dāng)計(jì)算出該矩陣后可以使用前文介紹的稠密或者稀疏仿射變換函數(shù)執(zhí)行對轉(zhuǎn)換后的結(jié)果執(zhí)行逆向操作。
// M:某個(gè)仿射變換的矩陣
// iM:該變換的逆變換矩陣
void cv::invertAffineTransform(cv::InputArray M, cv::OutputArray iM);
3.2 透視變換
投影變換可以通過投影矩陣實(shí)現(xiàn),需要注意的是它不是一個(gè)線性變換,因?yàn)橛?jì)算的結(jié)果需要和最后一個(gè)坐標(biāo)分量相除,對于二維空間而言,即z分量,更多信息可以參考OpenGL系列文章中的空間變換章節(jié)。和仿射變換一樣,OpenCV提供稠密投影變換和稀疏投影變換分別用于處理圖像和離散的點(diǎn)。
3.2.1 稠密投影變換
稠密投影變換的函數(shù)原型如下。
// src:輸入圖像
// dst:計(jì)算結(jié)果
// M:投影變換矩陣,尺寸為3??3
// dsize:輸出圖像的大小
// flags:像素插值策略,取值參考函數(shù)cv::resize
// borderMode:邊框擴(kuò)展策略,參考上一張濾鏡和卷積中的相關(guān)定義
// borderValue:當(dāng)參數(shù)borderMode選擇使用常量時(shí),使用的常量值
void cv::warpPerspective(cv::InputArray src, cv::OutputArray dst,
cv::InputArray M, cv::Size dsize,
int flags = cv::INTER_LINEAR,
int borderMode = cv::BORDER_CONSTANT,
const cv::Scalar& borderValue = cv::Scalar());
同樣的,投影變換得到的圖像中每一個(gè)像素都是從原始圖像中推導(dǎo)出的,其映射關(guān)系滿足如下公式。

和仿射變換一樣,計(jì)算出的參考坐標(biāo)系,即上式右側(cè)的x和y值不一定為整數(shù),此時(shí)需要根據(jù)參數(shù)flags定義的插值策略計(jì)算圖像插值。
計(jì)算投影變換矩陣
和仿射變換類似,OpenCV提供了兩種計(jì)算投影變換矩陣的方法,其中一種是根據(jù)兩組,每組共4個(gè)頂點(diǎn)表示的投影變換前后的坐標(biāo)來計(jì)算仿射矩陣,其函數(shù)原型如下。
// 返回值:3??3的仿射矩陣
// src:變換前的4個(gè)頂點(diǎn)坐標(biāo)
// dst:變換后的4個(gè)頂點(diǎn)坐標(biāo)
cv::Mat cv::getPerspectiveTransform(const cv::Point2f* src,
const cv::Point2f* dst);
示例PerspectiveTransform對一幅圖像應(yīng)用了投影變換,其核心代碼如下。
int main(int argc, const char * argv[]) {
// 讀取圖像
cv::Mat src = cv::imread(argv[1], cv::IMREAD_COLOR);
// 定義旋轉(zhuǎn)之前圖像輪廓頂點(diǎn)坐標(biāo)
cv::Point2f srcQuad[] = {
cv::Point2f(0, 0), // src 左上
cv::Point2f(src.cols - 1, 0), // src 右上
cv::Point2f(src.cols - 1, src.rows - 1), // src 右下
cv::Point2f(0, src.rows - 1) // src 左下
};
// 定義旋轉(zhuǎn)之后圖像輪廓頂點(diǎn)坐標(biāo)
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)
};
// 計(jì)算投影矩陣
cv::Mat warp_mat = cv::getPerspectiveTransform(srcQuad, dstQuad);
cv::Mat dst;
// 應(yīng)用投影變換
cv::warpPerspective(src, dst, warp_mat, src.size(), cv::INTER_LINEAR,
cv::BORDER_CONSTANT, cv::Scalar());
// 在仿射變換處理后的圖像上目標(biāo)頂點(diǎn)上繪制圓形
for (int i = 0; i < 4; i++) {
cv::circle(dst, dstQuad[i], 5, cv::Scalar(255, 0, 255), -1, cv::LINE_AA);
}
// 顯示圖像
cv::imshow("Perspective Transform Test", dst);
// 掛起程序直至用戶輸入任意鍵
cv::waitKey();
return 0;
}
該示例程序的運(yùn)行結(jié)果如下圖,其中左側(cè)是原圖,右側(cè)是應(yīng)用投影變換后的效果。

3.2.2 稀疏投影變換
函數(shù)cv::transform只能處理線性的幾何變換,而投影變換是非線性的,對于二維點(diǎn)Pold(x, y),首先需要擴(kuò)展其坐標(biāo)為Pold(x, y, 1),然后將執(zhí)行線性變換得到的結(jié)果除以第3個(gè)坐標(biāo)分量,即變換后的點(diǎn)坐標(biāo)為Pnew(X/Z, Y/Z),其中XYZ分別是對原坐標(biāo)Pold列向量左乘投影變換矩陣的結(jié)果。通過這種透視除法可以將原始坐標(biāo)投影到定義的目標(biāo)平面上。因此OpenCV單獨(dú)提供了一個(gè)函數(shù)來完成該任務(wù),其函數(shù)原型如下。
// src:待處理的坐標(biāo)數(shù)組,通道數(shù)為2或者3的N??1矩陣,每個(gè)元素表示為列向量
// dst:目標(biāo)輸出矩陣,通道數(shù)為2或者3的N??1矩陣,每個(gè)元素表示為列向量
// mtx:投影變換矩陣,尺寸為3??3(二維)或者4??4(三維),右乘輸入向量
void cv::perspectiveTransform(cv::InputArray src, cv::OutputArray dst,
cv::InputArray mtx);
需要注意這里的投影變換矩陣構(gòu)建方式和OpenGL中的透視投影變換矩陣的構(gòu)建方式是有差異的,后者通過透視除法后坐標(biāo)會(huì)被映射至標(biāo)準(zhǔn)設(shè)備坐標(biāo)系中。另外這里我們講到了該函數(shù)可以處理2維投影變換,但是我們需要知道,這里的二維是指嵌在三維空間中的一個(gè)平面??紤]相機(jī)從不同角度拍攝物體時(shí)其本身實(shí)際上是位于三維空間內(nèi)的,則不難理解這個(gè)概念。
4 通用變換
仿射變換和投影變換都是通用變換的特例,這兩種變換本質(zhì)上都是將原圖中的某個(gè)像素映射到不同位置的目標(biāo)圖像中。使用更一般的概念概括,它們都是在執(zhí)行坐標(biāo)映射計(jì)算。在這小節(jié)中將會(huì)介紹一些類似的變換,以及一些利用OpenCV實(shí)現(xiàn)我們自己的映射變換的方法。
4.1 極坐標(biāo)映射
在前面的章節(jié)中介紹到兩個(gè)函數(shù)cv::cartToPolar()和cv::polarToCart(),它們用于將坐標(biāo)在極坐標(biāo)系和笛卡爾坐標(biāo)系中轉(zhuǎn)換。除了單純的坐標(biāo)系轉(zhuǎn)換,實(shí)際上在更復(fù)雜的映射變換中也需要用到這兩個(gè)函數(shù),如在下文即將講到的通過函數(shù)cv::logPolar()實(shí)現(xiàn)的對數(shù)映射。
笛卡爾坐標(biāo)映射至極坐標(biāo)
笛卡爾坐標(biāo)映射至極坐標(biāo)的函數(shù)原型如下。該函數(shù)在計(jì)算弧度時(shí)使用了反正切函數(shù)atan2(y, x)的近似值,0表示的時(shí)x軸正方向。
// x:輸入矩陣,單通道,存儲(chǔ)笛卡爾坐標(biāo)的x分量
// y:輸入矩陣,單通道,存儲(chǔ)笛卡爾坐標(biāo)的y分量
// magnitude:輸出矩陣,單通道,極坐標(biāo)的幅度分量
// angle:輸出矩陣,單通道,極坐標(biāo)的角度分量
// angleInDegrees:極坐標(biāo)角度分量單位,false為弧度[0, 2pi),true為角度[0, 360)
void cv::cartToPolar(cv::InputArray x, cv::InputArray y,
cv::OutputArray magnitude,
cv::OutputArray angle, bool angleInDegrees = false);
該函數(shù)的一個(gè)使用場景是如果已經(jīng)通過cv::Sobel()函數(shù)計(jì)算處理圖像的x和y方向上的導(dǎo)數(shù),或者通過函數(shù)cv::DFT()或cv::filter2D()執(zhí)行卷積運(yùn)算得到類似的結(jié)果。將x和y方向的導(dǎo)數(shù)分布存儲(chǔ)在矩陣dx_img和dy_img中,通過這兩個(gè)矩陣可以的到一系列笛卡爾坐標(biāo)系中的點(diǎn)。然后定義兩個(gè)輸出矩陣img_mag和img_angle分布保存映射變換后的幅度和角度值,執(zhí)行代碼cvCartToPolar(dx_img, dy_img, img_mag, img_angle, True)得到極坐標(biāo)。通過幅度閾值過濾掉無效數(shù)據(jù)后就可以建立角度統(tǒng)計(jì)的直方圖,然后用于更下一步處理,如識別圖形。
極坐標(biāo)映射至笛卡爾坐標(biāo)
極坐標(biāo)映射至笛卡爾坐標(biāo)的函數(shù)原型如下。
// magnitude:輸入矩陣,單通道,極坐標(biāo)的幅度分量
// angle:輸入矩陣,單通道,極坐標(biāo)的角度分量
// x:輸出矩陣,單通道,笛卡爾坐標(biāo)的x分量
// y:輸出矩陣,單通道,笛卡爾坐標(biāo)的y分量
// angleInDegrees:極坐標(biāo)角度分量單位,false為弧度[0, 2pi),true為角度[0, 360)
void cv::polarToCart(cv::InputArray magnitude, cv::InputArray angle,
cv::OutputArray x, cv::OutputArray y,
bool angleInDegrees = false);
4.2 對數(shù)極坐標(biāo)
對于二維圖像而言,對數(shù)極坐標(biāo)轉(zhuǎn)換是將高速坐標(biāo)P(x, y)轉(zhuǎn)換為以對數(shù)表示的極坐標(biāo)形式P(p, θ)形式,其中p是點(diǎn)P至某一參考點(diǎn)C(xc, yc)的距離的對數(shù),其計(jì)算公式如下。

而θ是參考點(diǎn)C到點(diǎn)P的向量和x軸正方向的夾角,其計(jì)算方式如下。

通常還對在p前面乘以系數(shù)m擴(kuò)大圖像的顯示范圍。下圖演示了將一個(gè)正方形周長上的所有點(diǎn)以正方形為中心編碼到對數(shù)極坐標(biāo)的效果。

你可能會(huì)好奇為什么會(huì)有人愿意做這種轉(zhuǎn)換,其實(shí)這是源于人類的視覺系統(tǒng)。在人眼中心有一個(gè)較小區(qū)域,該區(qū)域內(nèi)部感光細(xì)胞分布十分密集,并且其向邊緣以指數(shù)級的速速下降??梢宰鲞@樣一個(gè)實(shí)驗(yàn),嘗試一直盯著墻上的某點(diǎn),然后將你的手伸平,使得手指在眼睛的正前方,然后慢慢將手向邊緣移動(dòng),同事保持盯著墻上的點(diǎn),感受視網(wǎng)膜上手指圖像的消失,你會(huì)發(fā)生圖像的消失強(qiáng)烈程度隨著距離的變換是以指數(shù)方式劇烈降低的。這種結(jié)構(gòu)具有某種很好的數(shù)學(xué)屬性,當(dāng)然這部分內(nèi)容超出了本文的討論范圍,即這種結(jié)構(gòu)能夠保存直線相交的角度。
這種變換在CV中更重要的應(yīng)用是它能夠用于創(chuàng)建二維圖像的縮放旋轉(zhuǎn)恒定表示。為了更好的理解這個(gè)概念,首先考慮簡單的圖形,即對于同一個(gè)形狀的縮放和旋轉(zhuǎn)后得到的多個(gè)圖形,分別以圖形的中心繪制其邊緣的對數(shù)極坐標(biāo)曲線,這些曲線經(jīng)過平移后能夠完全重疊。對于圖像而言,則是將原圖的每個(gè)像素轉(zhuǎn)換到對數(shù)極坐標(biāo)系表示,然后通過插值填充空白像素,則同如果是同一副圖像的多個(gè)縮放和旋轉(zhuǎn)變換結(jié)果,經(jīng)過對數(shù)極坐標(biāo)處理后經(jīng)過平移,在對數(shù)極坐標(biāo)系中仍然能夠重疊,這就是二維圖像的縮放旋轉(zhuǎn)恒定表示。
如在下圖中,左側(cè)是三個(gè)正方形,每個(gè)正方形都可以看作是另一個(gè)正方形縮放和旋轉(zhuǎn)變換后的結(jié)果,右圖是這三個(gè)正方形邊緣的在對數(shù)極坐標(biāo)系中的曲線表示??梢钥闯鲩L虛線的正方形是實(shí)線正方形的縮放結(jié)果,在對應(yīng)的對數(shù)極坐標(biāo)系中的曲線是實(shí)線曲線沿著logr軸平移的結(jié)果。而短虛線正方形是實(shí)線正方形的旋轉(zhuǎn)結(jié)果,在相應(yīng)的對數(shù)極坐標(biāo)系中的曲線是實(shí)現(xiàn)曲線沿著θ軸的平移的結(jié)果。

在后面的章節(jié)中將會(huì)介紹到圖像識別,現(xiàn)在只需要知道對整個(gè)目標(biāo)圖像應(yīng)用對數(shù)極坐標(biāo)轉(zhuǎn)換是不明智的,因?yàn)檫@個(gè)轉(zhuǎn)換對目標(biāo)圖形的中心位置特別敏感。更好的實(shí)現(xiàn)方式是首先檢測出目標(biāo)圖形的特征點(diǎn),如頂點(diǎn)等,然后通過關(guān)鍵點(diǎn)裁剪目標(biāo)圖形。再使用這些關(guān)鍵點(diǎn)的中心作為對數(shù)極坐標(biāo)轉(zhuǎn)換的中心,從而得到目標(biāo)圖形的對數(shù)極坐標(biāo)表示曲線,從而和對應(yīng)圖形關(guān)聯(lián)。
對數(shù)極坐標(biāo)轉(zhuǎn)換的函數(shù)原型如下。
// src:待處理的輸入圖像
// dst:處理完成的結(jié)果
// center:旋轉(zhuǎn)的中心,即坐標(biāo)轉(zhuǎn)換的中心參考點(diǎn)
// m:前文提到的距離系數(shù)p的縮放系數(shù)
// flags:插值和填充模式
// 插值模式和前文其他函數(shù)的取值相同
// 填充模式的可選值為cv::WARP_FILL_OUTLIERS表示執(zhí)行對數(shù)極坐標(biāo)變換
// cv::WARP_INVERSE_MAP表示執(zhí)行對數(shù)極坐標(biāo)逆變換
void cv::logPolar(cv::InputArray src, cv::OutputArray dst,
cv::Point2f center, double m,
int flags = cv::INTER_LINEAR | cv::WARP_FILL_OUTLIERS);
示例LogPolor使用對數(shù)極坐標(biāo)轉(zhuǎn)換處理了一副圖像,隨后再對其應(yīng)用逆變換,其核心代碼如下。
int main(int argc, const char * argv[]) {
// 加載圖像
cv::Mat src = cv::imread(argv[1], 1);
// 讀取縮放系數(shù),該系數(shù)越大,則得到的對數(shù)極坐標(biāo)表示水平拉伸更大
double M = atof(argv[2]);
cv::Mat dst(src.size(), src.type());
cv::Mat src2(src.size(), src.type());
// 計(jì)算對數(shù)極坐標(biāo)變換
cv::logPolar(src, dst, cv::Point2f(src.cols * 0.5f, src.rows * 0.5f),
M, cv::INTER_LINEAR | cv::WARP_FILL_OUTLIERS);
// 計(jì)算對數(shù)極坐標(biāo)逆變換
cv::logPolar(dst, src2, cv::Point2f(src.cols*0.5f, src.rows*0.5f),
M, cv::INTER_LINEAR | cv::WARP_INVERSE_MAP);
// 顯示圖像處理的結(jié)果
cv::imshow("log-polar", dst);
cv::imshow("inverse log-polar", src2);
}
該程序執(zhí)行的效果如下圖,其中左側(cè)是原始圖像,中間是對數(shù)極坐標(biāo)變換的結(jié)果,右側(cè)是其逆變換的結(jié)果。

4.3 自定義映射
OpenCV提供函數(shù)cv::remap()實(shí)現(xiàn)用戶自定義圖像的映射方式,其函數(shù)原型如下。該方法的一種常見應(yīng)用場景是優(yōu)化校準(zhǔn)的立體圖像,在后續(xù)章節(jié)介紹中將會(huì)學(xué)習(xí)如何處理相機(jī)成像扭曲和對齊,以及如何將它們轉(zhuǎn)換為參數(shù)map1和map2。
// src:輸入原始圖像
// dst:映射后的圖像
// map1和map2的尺寸必須和輸入輸出圖像的尺寸一致,元素?cái)?shù)據(jù)類型必須是CV::S16C2、
// CV::F32C1和CV::F32C2中的一個(gè)
// 函數(shù)內(nèi)部在計(jì)算時(shí)會(huì)對非整型的坐標(biāo)映射進(jìn)行插值運(yùn)算
// map1:目標(biāo)圖像每個(gè)像素映射后的參考坐標(biāo)x分量
// map2:目標(biāo)圖像每個(gè)像素映射后的參考坐標(biāo)y分量
// interpolation:插值方式,前文函數(shù)中的插值方式除了cv::INTER_AREA都可以使用
// borderMode:圖像邊界的填充方式
// borderValue:圖像邊界的填充常量
void cv::remap(cv::InputArray src, cv::OutputArray dst,
cv::InputArray map1, cv::InputArray map2,
int interpolation = cv::INTER_LINEAR,
int borderMode = cv::BORDER_CONSTANT,
const cv::Scalar& borderValue = cv::Scalar());
5 圖像修復(fù)
圖像通常會(huì)受到噪聲的污染,產(chǎn)生這些噪聲的原因可能是拍攝圖片時(shí)相機(jī)透鏡上覆蓋了水漬或者灰塵,也可能是老照片上出現(xiàn)的劃痕,或者是圖片的一部分直接被損壞。圖像修復(fù)(Inpainting)技術(shù)通過混合損壞部分的邊緣像素和損壞區(qū)域的像素修復(fù)圖像。下圖是一個(gè)使用該技術(shù)移除圖像表面文字的效果圖。

5.1 簡單修復(fù)
簡單的圖像修復(fù)對于損壞區(qū)域不是特別大,并且其邊緣的像素和紋理仍保留較好的場景才能取得較好的結(jié)果,下圖演示了對于損壞區(qū)域過大的圖像修復(fù)效果。

簡單圖像修復(fù)函數(shù)原型如下。
// src:輸入圖像,位深度為8,1通道灰度圖或者3通道彩色圖
// inpaintMask:修復(fù)蒙層,尺寸和輸入圖像相同,位深度為1,通道數(shù)為1,非0區(qū)域不會(huì)應(yīng)用圖像修復(fù)技術(shù)
// dst:修復(fù)完成的圖像
// inpaintRadius:每個(gè)損壞像素計(jì)算最終顏色時(shí)的參考像素區(qū)域半徑
// 較大損壞區(qū)域內(nèi)部的像素在修復(fù)時(shí)可能直接使用其他更接近邊緣的修復(fù)后像素的顏色
// 通常情況下該值設(shè)置為3,其值過大會(huì)出現(xiàn)模糊現(xiàn)象
// flags:圖像修復(fù)方法,cv::INPAINT_NS使用Navier Stokes方法,
// cv::INPAINT_TELEA使用Telea方法
void cv::inpaint(cv::InputArray src, cv::InputArray inpaintMask,
cv::OutputArray dst, double inpaintRadius, int flags);
5.2 噪聲去除
噪聲是圖像處理中的一個(gè)重要問題,在很多應(yīng)用中,引起噪聲的主要原因是較低的光照環(huán)境。在較低的光照環(huán)境下,相機(jī)傳感器獲得的數(shù)字信號必須經(jīng)過放大,因此噪聲信號也被同時(shí)放大了。這類信號的特點(diǎn)是出現(xiàn)大量隨機(jī)的極明亮或者暗淡的孤立像素,在彩色圖像中也會(huì)出現(xiàn)顏色異常。
OpenCV使用的降噪算法稱為快速非局部均值降噪技術(shù)(Fast Non-Local Means Denoising, FNLMD),相較于簡單的降噪算法僅僅通過計(jì)算目標(biāo)像素周圍像素的平均值而言,F(xiàn)NLMD技術(shù)的中心思想是在整個(gè)圖像中尋找相似像素,并計(jì)算這些像素的平均值。這里的相似并不是指的像素顏色,而是指的環(huán)境相似。對于一副圖像局部細(xì)節(jié)被損壞,但是通常其他區(qū)域有類似結(jié)構(gòu)的區(qū)域并未被損壞。
相似像素的識別基于一個(gè)窗口B(p, s),該窗口的中心為p,尺寸為s。對于損壞像素p定義的窗口B(p, s),以及參考點(diǎn)q定義的窗口B(q, s),它們的距離平方定義為如下等式。

上述等式中Ic(p)表示像素點(diǎn)p的c通道的強(qiáng)度,j是窗口B內(nèi)的像素索引。即上式計(jì)算的是目標(biāo)窗口和當(dāng)前窗口對應(yīng)像素對應(yīng)通道的強(qiáng)度差平方和,最后再乘一個(gè)常數(shù)系數(shù)。在計(jì)算出平方距離后,像素q的權(quán)重定義為如下公式。

上述等式中的σ是噪聲信號的標(biāo)準(zhǔn)偏差,單位為強(qiáng)度,h是一個(gè)通用的過濾參數(shù),它定義了目標(biāo)窗口和待更新窗口之間隨著平方距離增加,窗口無關(guān)性的變換快慢。即對于相同的平方距離增量,h值越大,著這兩個(gè)窗口的相關(guān)性越大。在應(yīng)用方面即h值越大,噪聲越容易移除,但是會(huì)損失圖像細(xì)節(jié),反之如果h值越小噪聲移除越不徹底,但是圖像細(xì)節(jié)保留得更好。
通常定義一個(gè)區(qū)域,稱為搜索窗口(Search Window),通過計(jì)算該窗口內(nèi)所有像素的加權(quán)平均值得到待修補(bǔ)像素的顏色,需要注意對于像素p而言,其權(quán)重值計(jì)算結(jié)果w(p, p)等于1,這個(gè)權(quán)重值會(huì)顯得異常大,因此通常使用窗口B(p, s)中最大的權(quán)重值作為該像素點(diǎn)點(diǎn)權(quán)重。由于最終計(jì)算結(jié)果有貢獻(xiàn)的參考像素的權(quán)重僅與定義的窗口和待更新窗口相似度相關(guān),因此這個(gè)方法名中包含關(guān)鍵字“非本地”。
5.2.1 處理灰度圖像
OpenCV提供了多個(gè)FNLMD算法函數(shù),它們用于處理不同的場景,其中基礎(chǔ)FNLMD算法函數(shù)的原型如下。
// src:輸入圖像,1、2或者3通道,基本數(shù)據(jù)類型為cv::U8
// 盡管支持彩色圖像,也盡量不要使用該函數(shù)處理彩圖,應(yīng)使用函數(shù)
// cv::fastNlMeansDenoisingColored()
// dst:降噪后的圖像
// h:權(quán)重衰減系數(shù)
// templateWindowSize:權(quán)重計(jì)算窗口的尺寸
// searchWindowSize:搜索窗口的尺寸
void cv::fastNlMeansDenoising(cv::InputArray src, cv::OutputArray dst,
float h = 3, int templateWindowSize = 7,
int searchWindowSize = 21);
當(dāng)使用該函數(shù)處理灰度圖時(shí),建議的權(quán)重衰減系數(shù)值如下。
| 噪聲標(biāo)準(zhǔn)偏差 | 權(quán)重計(jì)算窗口 | 搜索窗口 | 權(quán)重衰減系數(shù)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*σ |
5.2.2 處理彩色圖像
處理彩色圖像的降噪函數(shù)原型如下。由于人眼對亮度和顏色對感知敏感度是不相同對,因此該函數(shù)先將圖片轉(zhuǎn)換為LAB顏色空間,然后使用亮度權(quán)重衰減系數(shù)h和顏色權(quán)重衰減系數(shù)hColor處理原始圖像,最后再將得到的結(jié)果轉(zhuǎn)換為RGB顏色空間輸出。通常亮度權(quán)重衰減系數(shù)h比顏色權(quán)重衰減系數(shù)hColor大很多,在大多數(shù)場景中設(shè)置為hClor設(shè)置為10即可。
// src:輸入圖像,3通道,元素?cái)?shù)據(jù)類型為cv::U8C3
// dst:降噪后的圖像
// h:亮度權(quán)重衰減系數(shù)
// hColor:顏色權(quán)重衰減系數(shù)
// templateWindowSize:權(quán)重計(jì)算窗口的尺寸
// searchWindowSize:搜索窗口的尺寸
void cv::fastNlMeansDenoisingColored(cv::InputArray src, cv::OutputArray dst,
float h = 3, float hColor = 3,
int templateWindowSize = 7,
int searchWindowSize = 21);
推薦的亮度權(quán)重衰減系數(shù)如下。
| 噪聲標(biāo)準(zhǔn)偏差 | 權(quán)重計(jì)算窗口 | 搜索窗口 | 亮度權(quán)重衰減系數(shù)h |
|---|---|---|---|
| 0 < σ <= 25 | 3??3 | 21??21 | 0.50*σ |
| 25 < σ <= 55 | 5??5 | 35??35 | 0.40*σ |
| 55 < σ <= 100 | 7??7 | 35??35 | 0.35*σ |
5.2.3 處理視頻數(shù)據(jù)
對于視頻文件中連續(xù)的幾張圖片而言,圖像數(shù)據(jù)是相似甚至相同的,而噪聲通常是不相同的。因此很明顯可以利用前后的視頻幀來對指定幀應(yīng)用降噪技術(shù),其函數(shù)原型如下。
// srcImgs:需要處理的圖片數(shù)組
// dst:處理完的圖片
// imgToDenoiseIndex:需要降噪的圖片索引
// h:權(quán)重衰減系數(shù)
// templateWindowSize:權(quán)重技術(shù)窗口尺寸
// searchWindowSize:搜索窗口大小,即以需要處理的圖片索引為中心,前后共能搜索多少張圖片
void cv::fastNlMeansDenoisingMulti(cv::InputArrayOfArrays srcImgs,
cv::OutputArray dst,
int imgToDenoiseIndex, int temporalWindowSize,
float h = 3,
int templateWindowSize = 7,
int searchWindowSize = 21);
// hColor:顏色權(quán)重衰減系數(shù)
void cv::fastNlMeansDenoisingColoredMulti(cv::InputArrayOfArrays srcImgs,
cv::OutputArray dst,
int imgToDenoiseIndex,
int temporalWindowSize,
float h = 3, float hColor = 3,
int templateWindowSize = 7,
int searchWindowSize = 21);
6 直方圖均衡
相機(jī)和圖像傳感器不僅必須適應(yīng)場景中的自然對比度,還需要管理在可用光照等級下的曝光度。在標(biāo)準(zhǔn)的相機(jī)設(shè)備中,快門和光圈用于確保傳感器接收到的光線不會(huì)太多,也不會(huì)太少。然而,一張?zhí)囟▓D片的對比度范圍通常比傳感器的可用動(dòng)態(tài)范圍寬太多。因此需要在使用更長的曝光時(shí)間拍攝暗色區(qū)域,以及使用更短的曝光時(shí)間拍攝明亮區(qū)域,從而避免過飽和白化這兩種選擇之間進(jìn)行權(quán)衡。很多時(shí)候,在一張圖像中不能同時(shí)處理好這兩個(gè)問題。
當(dāng)圖片被傳感器記錄后,盡管我們不能夠改變相機(jī)拍攝時(shí)的曝光細(xì)節(jié),但是可以獲取其中的數(shù)據(jù),然后擴(kuò)展圖像的動(dòng)態(tài)范圍以增加它的對比度。最長用的技術(shù)就是直方圖均衡(Histogram Equalization)。下圖中左側(cè)的圖片看上去很糟糕,因?yàn)轭伾儞Q的范圍并不大,這點(diǎn)可以很明顯的從右側(cè)的亮度直方圖中看出。因?yàn)槭褂玫膱D像數(shù)據(jù)位深度為8,因此值的取值范圍為0到255,但是直方圖顯示值的取值范圍集中在中間的一小部分,直方圖均衡化可以擴(kuò)大值的取值范圍,從而使得圖像看上去更自然。

該技術(shù)的底層數(shù)學(xué)原理是將一個(gè)分布(給定的亮度直方圖)映射到另外一個(gè)分布(更寬的,理想下亮度的均勻分布)。事實(shí)證明使用累積分布函數(shù)(Cumulative Distribution Function)能夠得到更好的效果。對于理想情況的原始純高斯分布概率密度函數(shù)而言,其累積分布函數(shù)表示為下圖。

實(shí)際上累積分布函數(shù)可以應(yīng)用于任意的分布,只需要技術(shù)該分布在對應(yīng)點(diǎn)點(diǎn)累積概率密度即可。直方圖均衡的計(jì)算公式如下。其中X為處理前的灰度值,Y為處理后的灰度值,L為灰度級別,通常情況下位深度為8,此時(shí)L等于256,pX(x)是像素原始分布的概率密度函數(shù)。 該公式用于處理連續(xù)的數(shù)據(jù),因此使用了積分的方式,而數(shù)字圖像為離散數(shù)據(jù),因此直接累加概率即可。

對上圖應(yīng)用直方圖均衡處理后的到的結(jié)果如下圖,可以明顯的感受到圖片的對比度提高了很多。

OpenCV提供的直方圖均衡處理函數(shù)原型如下。該函數(shù)對輸入輸出圖像都必須是單通道8位圖像,另外如果處理彩色圖像應(yīng)該逐通道處理,并圖通常為了獲得更好的效果,會(huì)將原始圖像轉(zhuǎn)換至如LAB顏色空間,然后對亮度通道應(yīng)用直方圖均衡處理。
// src:輸入的待處理圖像
// dst:處理完成的圖像
void cv::equalizeHist(const cv::InputArray src, cv::OutputArray dst);
7 小結(jié)
本章首先介紹了一些圖像仿射變換的方法,這些變換包括縮放、旋轉(zhuǎn)和投影變換。另外也介紹了笛卡爾坐標(biāo)系和對數(shù)極坐標(biāo)系的轉(zhuǎn)換函數(shù)。這些函數(shù)的相同點(diǎn)是它們都是通過對整個(gè)圖像的全局操作將一個(gè)圖像轉(zhuǎn)換為另外一個(gè)圖像。此外還介紹了一個(gè)通用的圖像映射函數(shù),前面章節(jié)的這些變換都可以看作是該函數(shù)的一個(gè)特例。
隨后介紹了一些計(jì)算攝影學(xué)中的實(shí)用方法,如圖像修復(fù)和降噪,以及直方圖均衡。這些算法在處理相機(jī)或者視頻流時(shí)都很有幫助,當(dāng)你在實(shí)現(xiàn)其他的計(jì)算機(jī)視覺技術(shù)前先使用這些技術(shù)處理原始圖像以提升其質(zhì)量是一個(gè)不錯(cuò)的選擇。