Programming 1
Manipulating Pixels
1.saltImage: Add salt-and-pepper noise into an image.
include
opencv_core為OpenCV的核心庫,包含許多基本數據結構和函式。
opencv_highgui包含圖片及影片讀寫以及用戶介面功能。
random包含許多隨機生成器函式以及用於隨機生成分布方法的函式。
iostream為C++標準輸入輸出函式庫。
salt函式(接收image及常數n)
參考書中使用'rand()%image.cols'和'rand()%image.rows'來產生隨機x,y座標,示範程式碼中使用'defult_random_engine'定義了名為'generator'的隨機數生成器,並使用'uniform_int_distribution<int>'定義均勻分布的對象'randomRow'和'randomCol'以及其分別的隨機生成範圍。
for迴圈次數為接收的常數n,即產生白點的數量,j , i 為隨機生成的像素點座標,接著判斷 image 類型,'CV_8UC1'為單通道8位無號整型像素,即灰階影像,'CV_8UC3'為三通道8位無號整型像素,即彩色影像。
使用'at<uchar>'讀取灰階影像的( j , i )並改寫為最大值255(白色)。
使用'at<cv::Vec3b>'讀取彩色影像的( j , i )並同時將三個通道Blue、Green、Red改寫為最大值255,即為白色。
補充說明CV_8UC1和CV_8UC3
image類型的表示方法命名規則為 CV_<位數><符號類型><數據類型><通道數>
<位數> 表示每個像素的位數,通常是8、16或32。
<符號類型> 表示像素值的符號類型,通常是 U(Unsigned) 表示無號或 S(signed) 表示有號。
<數據類型> 表示像素值的數據類型,通常是C表示字符型、F表示浮點型或者其他類型的簡寫,例如R表示浮點型像素的實部,I表示整型像素的虚部等。
<通道數> 表示圖像的通道數,通常是1、2、3或4,也可以是其他數值。
main函式
將檔名為car的 JPG 影像使用'imread'讀取至Mat型別的image中。
判斷是否讀取失敗,若條件成立,輸出字串"無法讀取圖片"並返回-1。
執行salt函式,參數設定為前面讀取的image及5000的n。
使用'namedWindow'將視窗命名為Image。
使用'imshow'將修改後的image顯示在Image視窗。
使用'imwrite'將修改後的image輸出為salted.bmp。
使用'waitKey'等待使用者按下任意鍵繼續。
回傳0程式結束。
Compile成功,無 error 及 warning。
原圖(尺寸480*270)。
經過salt函式處理之後的結果。salt(image, 5000)
salt(image, 2000)
salt(image, 10000)
專案內成功輸出BMP影像。
另外加入了salt_and_pepper函式,這個函式接收圖像及prob
將圖像轉為灰階影像。
訪問每個像素點時運算(float)rand()/(float)RAND_MAX,產生一個0-1的隨機數rdn,在按照設定的prob產生不同占比的salt_and_peper,當像素點判斷為產生salt_and_peper時,以0.5的機率產生0或255,否則維持原樣。
prob是訪問單個像素點時轉換成salt_and_peper的機率,所有像素點都會被訪問一次所以也等於所有像素點被轉換的機率。
此為prob=0.02的輸出,表示有2%的像素點被轉換。
此為prob=0.04的輸出,表示有4%的像素點被轉換。
2.colorReduce: Reduce the number of colors into 1/N.
原圖(尺寸480*270)。
colorReduce後
首先,我創建了專案加入範例程式碼並且換了自己的影像執行,輸出了15種不同的colorReduce方式名稱及其函式所花費的時間。
接著觀察了每個函式的內容,以及對main函式的些微更動。
include
第四項為我自己加入的iomanip,為了在輸出文字的格式做一些調整。
定義'NTESTS'為15,用於函式數量。
定義'NITERATIONS'為10,用於重複執行次數。
main函式
使用'imread'讀取影像car.jpg為image。
定義唯讀的64位元整數型別的'start',用來存放當前CPU時鐘週期數。
執行'colorReduce'函式。(函式內容在之後說明)
定義double型別的'duration',用來存放函式的執行時間。
輸出duration。
接著命名視窗、顯示影像以及等待按鍵繼續。
getTickCount減去start為執行函式後增加的週期數,除以時鐘頻率getTickFrequency,得到的結果為執行函式所花的時間。
右圖為此部分輸出結果,時間單位為秒(secs)。
定義64位整數型別 t 陣列,長度為NTESTS,以及64位整數 tinit 。
for迴圈重複15次將t陣列內15個整數設為0,用為將時間歸零。
定義圖像型別'images'的長度為NTESTS,用於存放多張影像。
定義圖像型別'result',用於存放結果影像。
定義指向函式的指標型別'FunctionPointer'。
使用這個型別將'functions'按順序指向所有函式。
將n設定為NITERATIONS,為10。
外層for迴圈每次進行一次輸出 k of n,k為當前次數,n為總次數,並在執行完內層for迴圈後換行。
內層for迴圈每次讀取同一張影像並存放在images的相對順序位置,當前時間週期數存放於tinit,接著執行對應的functions帶入影像及常數64,再將每次時間週期變化量累加在 t,最後輸出一個點。
右圖為此部分輸出結果。
接著定義了長度為NTESTS的字串陣列descriptions。
並將每個函式的敘述存放進去。
使用for迴圈分別對視窗命名及顯示。
下圖為此部分輸出結果,每張圖的輸出相同。
紅框為更動部分
我將最長字串長度存放在maxLen中。
接著印出分隔線。
將編號長度設定為3個寬度,編號為 i + 1 ,敘述寬度設定為maxLen並且靠左對齊,時間數字部分設定10個寬度並且向左靠齊,單位則向右靠齊。
等待按鍵繼續。
返回0程式結束。
我的電腦
實驗機
最後的輸出編號由1到15,數字部分對齊稍微比較好做觀察,這邊顯示的時間為執行10次的平均時間。
兩台硬體不同的電腦跑出的排名一樣。
overload operators為效率最高的函式,而花最多時間的則是Vec3b iterators。
original version(原始)
函式中取得影像rows為 nl,cols乘上通道數等於像素點總數 nc。
外層for迴圈執行 nl 次,每次以指標讀取像素點 data,表示第 j 行。
內層for迴圈執行 nc 次,每次對第 j 行內的第 i 個像素做/div*div+div/2,結果寫回data[i]內
with iterators(最慢)
這個函式使用了OpenCV提供的迭代器,能方便的訪問像素由開始 it 至結束 itend。
並且定義了常量向量offset,每個分量為div/2。
for迴圈內使用指標做運算。
overloaded operators(最快)
先將div取以2為底的對數,加0.5實現四捨五入,目的為將div轉換成二進制位數,即為除法次數n。
建立一個mask,用於將相素值量化,將0xFF左移n位,後方就會有n個0。
接著,程式會對圖像進行 AND 運算,將圖像中所有像素的值與mask值進行 AND 運算,將結果存回圖像。由於每個像素被截斷到了 n 位,因此像素值被量化到了 2^n 個數量級,為了實現圖像的亮度均衡,還需要加上一個固定的值 div/2,即 2^(n-1)。因此,圖像每個像素的值就被量化到了 [0,div-1] 範圍內的整數。
結果分析:
在overlload operators函式是以邏輯運算進行,相對會比算術運算更有效率。
3.contrast:Enhance an image by sharpening methods.
include
imgroc為OpencCV圖像處理的核心標頭檔,提供了許多用於圖像處理和計算的函數和工具 。
sharpen函式接收image和result的記憶體位址。
創造一個和image同size和type的result。
將image通道數放入nchannels中。
外層for迴圈內 j 避開最左及最右位置,previous、current和next分別代表右、本身及左像素。
output為輸出位置。
內層for迴圈 i 避開最上級最下位置,使用'saturate_case'進行飽和度轉換,確保值在0-255之間,5*cturrent[i]分別減掉其下上左右像素質,最後將output往下一個輸出位置移動。
去除邊緣效應,將result矩陣的首末行及首末列像素設為零。
sharpenIterator函式是針對灰階影像,並使用迭代器依序讀取像素的做法,原理和算法相同於sharpen。
sharpen2D函式建立一個 3x3 的矩陣 kernel 並將其所有元素初始化為 0。
接著將 kernel 的某些元素值設置為特定值,以使其成為一個銳利化的 kernel。
最後,使用 OpenCV 的 filter2D 函式將 kernel 應用到輸入影像 image 上,並將結果寫入到輸出影像 result 中。
main函式
定義影像image並讀取。
確認讀取成功程式繼續。
定義結果影像result。
接著分別對三個函式做計時及輸出。
左下為原圖,可以觀察到三個函式輸出的影像都有明顯銳利化的效果。
4.addImages:Add two imags to get a blended image.
include
vector是C++ 的標準標頭檔之一,可以讓程式設計師使用 STL (Standard Template Library) 中的 vector 容器,vector 容器是一個動態陣列,可以根據需要自動調整大小,並且支援隨機存取、插入、刪除等操作。
main函式
分別定義image1和image2並讀取、顯示。
image1和image2輸出結果。
addWeighted函式功能為將兩張影像進行線性混合 (linear blending)。
參數為兩張影像及分配權重,亮度調整,輸出。
下方則直接直接使用算術運算做結合。
左圖為使用 addWeighted 函式,權重分別為 image1, 0.5和 image2, 0.8。
右圖為使用 operators 結合,權重分別為 0.9 和 0.5。
創建一個 vector,名為 planes,用來存 image1 的三個通道,即 B、G 和 R。
使用 OpenCV 的 split 函數,將 image1 的三個通道分存到 planes 向量中。
將 planes 向量中的第一個元素(B)與 image2 進行像素級別的相加操作,將結果存儲回 planes[0] 中。
使用 OpenCV 的 merge 函數,將 planes 向量中的三個通道合併為一個多通道影像,並將結果存儲在 result 變數中。
輸出 result 影像。
planes[0]+=image2
planes[1]+=image2
planes[2]+=image2
結果分析:
做addWeighted的時候,權重分配很重要,會影響視覺效果。
用split做其中一個channel的合成也許是原圖的blue強度較強,加planes[0]時沒有太大的差異,但使用1或2時明顯整個色系都改變了。
5.remapping:Simulate a lens distortion to create distorted images.
wave函式
定義 srcX 和 srcY 兩個矩陣,大小與image相同,型態為CV_32F(單經度浮點型)。
使用兩層for迴圈訪問所有像素,當 srcX 對應 j ,srcY對應 i+3*sin( j/6.0),就能實現波浪變形的效果。
使用remap函式進行圖像重映射,將變形後的數據映射到 result 中。
main函式
以灰階影像讀取 image 並輸出。
再將影像傳入 wave 函式中進行變形,並輸出 result 。
原圖
srcY.at<float>(i,j)= i+3*sin(j/6.0)
srcY.at<float>(i,j)= i+6*sin(j/6.0)
srcY.at<float>(i,j)= i-3*sin(j/6.0)
結果分析:
將係數3的位置改為6能夠明顯增加變形幅度,改成-3的時候變形量和+3相同但變形方向方向相反。