【opencv + python】數字圖像處理課程設計:彩色圖像復原

     本文爲武漢理工大學數字圖像處理課程設計:彩色圖像復原的實現過程,本人初學Python,對數字圖像處理的理解也僅限於課堂教學範圍,如有不足之處歡迎指出。

       先上效果圖,對於jpg圖片,在一分鐘內處理完成,以下分別是原始圖片,彩色圖像,自動裁剪等優化後的圖像。

                                                                           

 

       對於tif圖片,在三分鐘內處理完成,以下分別是原始圖片,彩色圖像,裁剪等優化後的圖像。(原圖爲60MB,此處上傳圖片大小約爲2MB,僅作效果參考)

 

 

 

  • 問題內容描述

1. 問題背景

        Sergei Mikhailovich Prokudin-Gorskii (1863-1944)是一位超越其所在時代的人,早在1907年,他就堅信彩色攝影將成爲未來的發展趨勢。在當時,由於沙皇的特別許可,他可以周遊遼闊的沙俄帝國並拍攝他所看到的事物,包括列夫·托爾斯泰唯一的彩色肖像。他用簡單原始的彩色攝影法拍下了很多東西:人、建築、風景、鐵路、橋樑……!

其所採用的彩色攝影法很簡單:分別用紅、綠、藍濾光片把每個場景的三次曝光記錄到一個玻璃底板上,並設想通過特殊的投影裝置將三種顏色的底片疊加顯示,從而讓觀衆能夠通過彩色照片瞭解這個幅員遼闊的國家。可惜,他的計劃從未實現:他在1918年的十月革命後離開了俄羅斯,再也沒有回來。幸運的是,他所拍攝的沙俄帝國最後幾年的RGB玻璃板底片保存了下來,並於1948年被美國國會圖書館(LoC)買下。LoC最近將底片數字化,並可以通過網絡供公衆下載。

 

2.設計目標

      本次課程設計的目的是利用圖像處理技術,基於數字化存儲的玻璃底板圖像自動生成儘量非虛化的彩色圖像。爲完成本次課程設計,你需要從原始圖像文件中分割提取三個彩色通道圖像,將它們對齊並彼此疊加在一起,最終形成一張RGB彩色圖像。美國國會圖書館在其網站上詳細說明了他們對這批照片進行復原並創建彩色圖像的過程,可以參考

      http://www.loc.gov/exhibits/empire/making.html

1.設計思路

         Prokudin Gorskii將一塊3英寸寬9英寸長的窄玻璃板垂直放置在相機中。然後,他用紅色濾鏡、綠色濾鏡和藍色濾鏡以相當快的順序拍攝了同一場景三次。通過Prokudin Gorskii的相機觀看時,被拍攝的場景會出現顛倒,並與實際方向相反。對於數字處理,原始的三部分玻璃負片在灰度模式下用高架數碼相機掃描。圖像編輯軟件將整個板的掃描從負片轉換成正片。掃描被反轉以表示原始物理方向。然後將整個板還原爲8位灰度模式。在放大倍數下,檢查板上每個圖像的對比度、分色程度、乳劑損壞程度以及可能影響最終顏色合成的任何其他細節,將整個板的掃描對齊,並裁剪外側邊緣可以得到修復的彩色圖片。

        類似的,我們要通過數字圖像處理進行彩色圖片復原,需要首先獲得圖片不同顏色的三個圖層,在圖層處於灰度模式時,將紅色(R)、藍色(B)和綠色(G)層對齊,形成“RGB”顏色組合。對齊之後將得到一副帶有雜亂邊框的圖片,可以通過裁剪將其剪去,裁剪後的顏色組合整體調整以創建適當的對比度、適當的高光和陰影細節以及最佳的顏色平衡,最終可以完成彩色圖片的恢復。

2.簡易流程圖

 

 

  • 實現方案

(1)讀入圖片:

思路描述:讀入文件,若爲tif文件將其轉換爲8位無符號整數格式,之後轉換成灰度圖,並保存原始圖片。

關鍵代碼:

pic_name = '../turkmen.tif'  #圖片名

im =  cv.imread(pic_name)  #將圖像轉換爲8位無符號整數格式

if '.tif' in pic_name:

    im=skimage.util.img_as_ubyte(im)

im = cv.cvtColor(im,cv.COLOR_BGR2GRAY)

#之後會利用圖片構造高斯金字塔,保留原始圖片

im_old = im.copy()

(2)構造高斯金字塔。

思路描述:對於jpg文件我們可以輕鬆地對圖像進行遍歷操作,但是tif文件太大非常耗時,所以通過構造高斯金字塔的方法解決。先對圖片進行高斯濾波處理以使圖片即時縮小也能保留較多細節,然後再每次刪除一半的行和列達到減小圖片大小的效果,如圖所示。

                                                        

 

關鍵代碼:

#由於初始圖像已存至變量im_old,只需保留滿足大小要求的最後一層圖像即可

#計算高斯核函數

    def gausskernel(size):  

    sigma=0.8

    gausskernel=np.zeros((size,size),np.float32)

    k = int(size/2)

    print(k)

    for i in range (size):

        for j in range (size):

            norm=math.pow(i-k,2)+pow(j-k,2)

            gausskernel[i,j]=math.exp(-norm/(2*math.pow(sigma,2)))    

    sum=np.sum(gausskernel)   # 求和

    kernel=gausskernel/sum   # 歸一化

return kernel



def pyramid_Gauss(image):   

    kernel=gausskernel(3)        #階數取3

    temp = image.copy() 

    (length,width) = temp.shape[:2]

    length = length / 3.0

    cnt = 0  

#當圖片過大對其構造高斯金字塔使其大小小於一定限度

    while length * width > 250000 :

        cnt = cnt + 1

        temp = pyramid_Down(temp,kernel)

        (length,width) = temp.shape[:2]     

    cv.imshow("pyramid_down_" , temp)       

    return temp,cnt



def pyramid_Down(image,kernel):

    (length,width) = image.shape[:2]

    img1 = np.zeros((length,width),np.uint8)

# 高斯濾波過濾

    for i in range (1,length-1):

        for j in range (1,width-1):

            print(i,j)

            s = 0

            for k in range(-1,2):

                for l in range(-1,2):

                    s = s + image[i+k,j+l]*kernel[k+1,l+1]  

            img1[i,j] = s

   #刪去一半的行和列,構造高斯金字塔1

    cnt = 0

    for i in range(length):

        if i % 2 == 1:

            img1 = np.delete(img1, i - cnt, axis=0)

            cnt = cnt + 1

    cnt = 0

    for j in range(width):    #刪列操作與上文類似,在此省略

        ……

    #主函數調用上述函數完成操作

im,count = pyramid_Gauss(im)

print('pyramid finished!')

(3)計算圖層相似性及平移距離

思路描述:由公式:

SSD(u,v) =  Sum{[Ima_1(u+x,v+y) – Ima_2(x,y)] ^2} 

可計算並比較圖層間的相似程度。

關鍵代碼:

def find_min_ssd(img_1,img_2,r_lim,d_lim):

      #尋找兩幅圖片的最小ssd所需平移的座標

      #img_1固定,img_2平移的空間爲[-r_lim,-d_lim] -> [r_lim,d_lim]

      length,width = img_1.shape[:2]



      #最小ssd初始化爲無窮大

      min_ssd = float("inf")

      min_r = 0

      min_d = 0

      sum_ssd = 0

      for r_dis in range(-r_lim,r_lim+1):

          for d_dis in range(-d_lim,d_lim+1):

              sum_ssd = 0

              for i in range(length):

                  for j in range(width):

                      x = i + d_dis

                      y = j + r_dis

                      if(x >= 0 and x < length and y >= 0 and y < width):

                          sum_ssd = sum_ssd + (int(img_1[x,y])-int(img_2[i,j])) * (int(img_1[x,y])-int(img_2[i,j]))

              sum_ssd = sum_ssd

              if sum_ssd < min_ssd:

                  min_r = r_dis

                  min_d = d_dis

                  min_ssd = sum_ssd

return min_r,min_d

#在主函數中調用上述函數

height = np.floor(im.shape[0]/3.0).astype(np.int)

b = im[:height]

g = im[height:2 * height]

r = im[2 * height:3 *height]

(a1,b1) = find_min_ssd(r,b,15,15)

(a2,b2) = find_min_ssd(r,g,15,15)

#每一層高斯金字塔圖片的長寬各縮小一倍,共建了count層,需要使平移參數乘上2的count次方

k = math.pow(2,count)

a1 = int(a1 * k)

b1 = int(b1 * k)

a2 = int(a2 * k)

b2 = int(b2 * k)

 

(4)平移圖層,將三個圖層重疊得到彩色圖片。

思路描述:通過調用translate函數對圖像進行仿射變化,達到平移的效果。

關鍵代碼:

def translate(img,tx,ty):

    #圖像平移

    length,width = img.shape[:2]

    m = np.float32([[1,0,tx],[0,1,ty]])

#仿射變換

    res = cv.warpAffine(img,m,(width,length))

    return res



#在主函數中調用上述函數

b = translate(b,a1,b1)

g = translate(g,a2,b2)

im_out = cv.merge((b,g,r))

print(a1,b1,a2,b2)

cv.imshow("aligned image",im_out)

cv.imwrite('../turkmen_2.tif',im_out)



 

 

五、優化方案

(1)圖像微調。

思路:在得到平移距離後,因爲對於.tif文件,該平移距離是通過比較壓縮後的圖像所得,而非原始圖像,假設構建了四層金字塔,則需要對原距離乘以16,那麼所得平移距離必然是16的整數倍,對於原始圖像,這些點往往不是平移的最優點,在對原始圖片平移前,應該對該平移距離進行微調。

關鍵代碼:

#im_old爲原始灰度圖像

height = np.floor(im_old.shape[0]/3.0).astype(np.int)

b = im_old[:height]

g = im_old[height:2 * height]

r = im_old[2 * height:3 *height]

print(b.shape)

#find_min_ssd_adj邏輯與find_min_ssd相同,只是搜索空間爲原始圖像,且平移距離僅爲之前所得平移距離的領域,l1,l2,l3,l4爲常數可根據實際情況修改

(a1,b1) = find_min_ssd_adj(r,b,a1-l1,a1+l1,b1-l2,b1+l2)

(a2,b2) = find_min_ssd_adj(r,g,a2-l3,a2+l3,b2-l4,b2+l4)

(2)改進相似度比較策略。

思路: 在使用上文的算法進行處理後,即使對jpg圖片,對齊效果依然不能算完美,如cathedral.jpg圖片放大後藍色圖層存在一定偏移,需要人工對距離進行一定微調,於是我輸出了人工調整後的SSD總和與之前計算的最優SSD總和進行比較,發現調整後的SSD和反而增加了,證明我的算法沒有錯誤,但調整後的對齊效果確實更好。

      經過仔細思考,我發現原始圖片四周存在黑邊,當對某一個圖層進行平移後,可能會使得某一圖層的非黑邊部分與另一圖層黑邊部分產生更多重疊,因爲黑邊的亮度接近0,在這部分計算相似度可能會產生較大的SSD和,使得全局比較並累加得到的平移距離往往比真實應該平移的距離偏小。

      於是對比較策略進行調整,如果當待比較的點距離上、下、左、右邊界較近(本課程設計中設爲距離小於1/6長或寬),則其值不計入SSD總和,否則計入總和,即人爲設定一條閾值,只計算圖片中間部分的點進行比較相似度,雖然一定程度上減少了比較的面積,但通過嘗試對不同圖像進行實驗,得到的效果非常好,且算法運行速度大大提高。

       具體代碼即爲在之前的for循環加一個條件判斷即可,在此不再贅述。

 

(3)提高對比度。

思路:在合成彩色圖片前,對圖像的亮度進行重新標定是常用的處理手法。本課程設計中,在平均亮度最暗的顏色通道上,其接近最黑的像素(取與最黑像素值之差小於10的點)被標定爲0,在平均亮度最亮的顏色通道上,其接近最亮的像素(同上)被標定爲255

關鍵代碼:

def contrast(r1,b1,g1):

    (length1,width1) = r1.shape[:2]

    sum_b = 0

    sum_g = 0

    sum_r = 0

    for i in range(length1):

        for j in range(width1):

            sum_b += r1[i,j]

            sum_g += b1[i,j]

            sum_r += g1[i,j]

    if(sum_b > sum_g and sum_g > sum_r):

        i_max = b1

        i_min = r1

    elif(sum_b > sum_g and sum_r > sum_g):

#判斷哪個圖層平均亮度最大和最小,與上述代碼類似,在此省略

    maxn = -1    

    minn = 256

    for i in range(length1):

        for j in range(width1):

            if(i_max[i,j] > maxn):

                maxn = i_max[i,j]

            if(i_min[i,j] < minn):

                minn = i_min[i,j]

    for i in range(length1):

        for j in range(width1):              

            if(i_max[i,j] >= maxn - 10):    

                i_max[i,j] = 255

    for i in range(length1):

        for j in range(width1):

            if(i_min[i,j] <= minn + 10):

                i_min[i,j] = 0

    return r1,b1,g1

 

(4)自動裁剪邊框。

思路:在進行自動裁邊前,我觀察到邊框的顏色很怪異,可能導致在邊框不同圖層像素值之間差很大,於是我將圖片每一行(列)的不同圖層像素值最大的差輸出,觀察到在邊框附近這個差值遠大於圖片內部,即可以通過設定一個閾值將差值大於閾值的行(列)刪除,達到自動裁邊的效果。

關鍵代碼:
 

#刪除列的代碼與刪除行的邏輯相同,以下代碼省略刪除列的部分代碼

up_record = 0     #上邊界,小於其的行將被刪除,以下類似

down_record = 999999999    #下邊界

for i in range(length):    #計算上下邊界

    sum_t = 0

    for j in range(width):

        sum_t += max(b[i,j],g[i,j],r[i,j]) - min(b[i,j],g[i,j],r[i,j])

    if(sum_t/width > 65 and i <length/4):

        up_record = i

    elif(sum_t/width > 65 and i > 3 * length / 4 and down_record > i ):

        down_record = i

    #刪除行,刪除列代碼已省略

for i in range(length - down_record):  

    b = np.delete(b,-1,axis = 0)

    g = np.delete(g,-1,axis = 0)

    r = np.delete(r,-1,axis = 0)

for i in range(up_record):

    b = np.delete(b,0,axis = 0)

    g = np.delete(g,0,axis = 0)

    r = np.delete(r,0,axis = 0) 

im_out = cv.merge((b,g,r))

六、其他改進

特徵點匹配

         基於對課程設計任務的理解,採用了模板匹配的方式進行特徵匹配,模板匹配是一種最原始、最基本的模式識別方法,研究某一特定對象物的圖案位於圖像的什麼地方,進而識別對象物。它是圖像處理中最基本、最常用的匹配方法。模板匹配具有自身的侷限性,因爲它只能進行平行移動,但是對於本次課程設計來說非常適合。

 

關鍵代碼:

b = im[:height]

g = im[height:2 * height]

r = im[2 * height:3 *height]

raw = g



#選取某圖層部分區域充當模板

template = b

(l1,l2,w1,w2)=(50,150,200,300)

template = template[l1:l2,:]

template = template[:,w1:w2]

cv.imshow("test",template)

height, width = template.shape[:2]



#使用歸一化平方差匹配法進行模板匹配

result = cv.matchTemplate(raw,template,cv.TM_SQDIFF_NORMED)



#進行歸一化

cv.normalize( result, result, 0, 1, cv.NORM_MINMAX, -1 )

min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result)



a1 = min_loc[0]-w1

b1 = min_loc[1]-l1

#得到該圖層平移距離,再對另外一個圖層做相同操作後調用前文中的代碼進行平移,並將平移後的圖層重疊即可恢復彩色圖像。

 

  • 問題與影響因素討論

 

1.結果描述

        程序經過調試與修改,對所給的每張圖片都能恢復成效果較好的彩色圖片,對jpg圖片能夠在2分鐘左右完成,對於tif圖片能夠在5分鐘左右完成,且能對不同顏色的邊框進行自動裁剪。

2.綜合分析

         要對彩色圖像進行恢復,首先需要獲得圖片不同顏色的三個圖層,在圖層處於灰度模式時,將紅色(R)、藍色(B)和綠色(G)層對齊,形成“RGB”顏色組合。對齊之後將得到一副帶有雜亂邊框的圖片,可以通過裁剪將其剪去,裁剪後的顏色組合整體調整以創建適當的對比度、適當的高光和陰影細節以及最佳的顏色平衡,最終可以完成彩色圖片的恢復。

         本次課程設計所實現的程序,對jpg圖片處理較快,大約在一分鐘之內可以完成,對tif圖片,因爲對tif圖片構造高斯金字塔的圖片進行對齊後,爲了對原始圖片進行對齊,還對原始的三個圖層進行了小範圍的微調,所以處理時間大約在三分鐘,考慮到圖片的大小,這個時間我認爲還在可以接受的範圍內,後續可以考慮對循環體進行結構的優化,以減少運行的時間。    

  • 參考文獻

 

[1]Brooks, L.E.. The empire that was Russia: the Prokudin-Gorskii photographic record re-created[P]. Applied Imagery Pattern Recognition Workshop, 2002. Proceedings. 31st,2002.

[2]Rafael C.Gonzalez Richard E.woods.數字圖像處理(第三版)[M].電子工業出版社:北京,2013:1.

 

 

 

 

 

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