用Python實作 Perspective Transformation 透視變換

  • 主要演算法
    1. 通常我們在自然背景拍攝一張照片時,或多或少都會有一些透視變形存在,而透視變形是由拍攝和觀看圖像的相對距離決定的,導致遠近特徵的相對比例有變化,產生了彎曲或變形,當然在本例子,我是刻意拍了一張比例歪斜的圖,想要驗證依照公式轉換會得到什麼效果。
    2. 依照解決方法,這樣的變形我們可以下列的聯立方程式來表示:

​x = ax’ + by’ + cx’y’ + d

y = ex’ + fy’  + gx’y’ + h

​其中x、y為你原始影像的座標系,x’、y’為校正後的座標系,

a、b、c、d、e、f、g、h為常數,表示變形關係。

  • 程式片段
  1. 定義高斯消去

2. 先定義4個點,找出矩陣

3. 照公式進行矩陣轉換,解出方程式,輸出結果就得到圖片了。

4. 另外我也參考網路範例,仿造OpenCV寫了一個轉置方法,後續比較一下兩種做法的差異。

  • 測試資料與結果
  1. 原圖是張刻意拍歪斜的照片,我標出四個白點,可以看到原始轉換區塊變形到接近菱形了。

2. 轉換後得到的結果如下,或許是因為變形幅度高,沒達到完整的轉換。

3. 參考網路實作Perspective Transformation做出來的效果,因為先做3D後轉2D,校正效果很好,只是程式寫得不好,有失真。

4. 使用Open CV做的效果

  • 討論
  1. 原本課堂講解的校正來源是一個平行四邊形,校正效果相當好,但在本次作用我使用一個不規則的多邊形來做轉換,雖然轉換結果不比原本的好,但還算可以得出資訊。
  2. 參考Perspective Transformation實做出來的程式做轉換,對於不規則多邊形轉換的效果很好,只是有失真,若用OpenCV轉出來的話就趨近完美了。
  3. 此類的校正可以應用在機器人自駕時的資訊辨認,或是在OCR應用上先把讀入的圖片做校正,在讀取資訊,可以減少判斷失誤。
  4. 在Deepfake影片偵測的研究中,許多論文的原理是把影像的Frame讀入後分析他是不是假造影像;如果在深度學習時,我先把人臉或是指定的擷取特徵做校正,可以把每個Frame的特徵變成一樣的構圖,就有機會分辨出他是不是造假的Frame:因為Frame造假是基於原本扭曲變形的畫面做變更,在校正後應該可以看出他和一般正常角度Frame的不同之處。

使用Python實作Image Enhancement

趁著學期告一段落,工作也在過年前的休息時間,趕快把之前的作業整理出來;這篇文章依然會是先前的影像處理課程所編寫的作業,在這裡不會用到深度學習或機器學習,而是使用傳統的影像處理概念來做,目的是為了瞭解原理。

本篇文章主要是探討如何通過二階微分,將影像邊緣強化。

  • 主要演算法
    1. 原圖雖然用的是彩色影像,但我在程式中有轉為灰階,所以實際增強是針對灰階影像增強。
    2. 把原影像先乘Laplacioan運算子做二階微分,可以得到影像的邊緣。
    3. 接著和原始影像相加,邊緣會被加強,但是因為原圖的雜訊也被放大,所以這裡的圖是有雜訊的加強,得到圖A。
    4. 接著原始影像用Sobel對X和Y做一階微分,可以得到影像邊緣圖B,接著對圖B做去雜訊。
    5. 再來把圖B的像素做標準化,每個像素會被做成0~1的數值,此時在平坦區(比較亮的地方)數值會較大,趨近1,邊緣(比較暗的地方)則趨近0。
    6. 我們把標準化的矩陣和圖A相乘,可以知道原圖依照比例被放大了,原先暗的像素方又變得更黑了,非邊緣的地方則會因為乘趨近1的值,故不太有大變化,結果我們會得到圖C,圖C就是加強後的結果。
  • 程式片段
    1. 先把圖片轉成灰階影像。

2. 定義Laplace運算子遮罩

3. 定義Sobel運算子遮罩,0是dY,1是dX

4. 定義Slide Window Filter降噪

5. 對原本影像乘Laplace運算子,二階微分得到邊緣,接著和原圖相加。

6. 把微分後結果和原圖相加,得到一張銳化的圖。

7. 接著對原圖做Sobel運算子,一階微分先做dX再做dY,然後用OR合成,也可以得到一張圖的邊緣。

8. 接著用Slide Window filter把雜訊降低。

9. 最後我們把這結果做標準化,取得一個0~1的矩陣,然後把矩陣和步驟3的圖相乘,圖的暗部被放暗,亮度則不太改變,因為暗部被變暗了,會感覺到邊緣變得銳利。

  • 測試資料與結果
    1. 原圖,將他模糊後轉成灰階。

2. 做Laplace運算後得到邊緣。

3. 和原圖相加,但感覺到雜訊也放大了

4. 對原圖X、Y做Sobel微分後,做合成,得到一個較為銳利的邊緣。

5. 使用Slide Window Filter把雜訊去掉。

6. 接著把模糊後結果乘上前面的圖,得到邊緣增強的結果。

  • 討論
    1. 把Sobel微分後的結果做標準化後,再乘上和二階微分後的圖,可以有效讓暗部變暗,因為標準化結果介於0~1之間,亮部*1~0.5之間,只會感到些許變暗。
    2. 但是這樣的方法會讓原本的圖變得比較暗,暗部變的更暗,邊緣會更明顯,但亮部受到乘數影響,也明顯感覺到變暗。
    3. 依據課堂上提到一個好處,我們把邊緣做模糊消去雜訊後再做標準化,因為雜訊已經被去掉了,最後再去和圖相乘,可以避免雜訊放大。
    4. 延伸一個思考問題,若是我們處理彩色影像時,對R、G、B個做一次上述步驟的處理,是否也能把彩色影像的影像做增強呢?

使用Pytorch實作卷積網路(CNN)做多分類圖片識別

這篇文章是我在修習影像處理時做的作業之一,主要是想訓練一個Model來識別輸入的圖片是在室外環境,還是室內環境,為了完成這個目標,我使用Pytorch實作了卷積神經網絡(Convolutional Neural Network)簡稱CNN,搭配網路上的資料集進行訓練,最後將我隨意亂拍的照片輸入做辨識,發現效果還不錯。

以下節錄我當時整理出來的作業內容,大略解釋了演算法的概念,訓練過程,和最後檢討,因為是作業,故有很多是比較學生角度來解釋整個事件。

  • 主要演算法
    1. 室內、室外乍看是二分類,可以用線性回歸(Linear regression)的方式來做,但是考量室內室外只是眾多場景一種,故我用多分類(Multi label classification)的方式開發程式,並用CrossEntropy計算Loss。
    2. 會採用多分類(是因為我單純認為用二分法來識別兩個場景不是很好的方法,最後預測結果會落在0~1之間,若用0.49中位數來分辨0和1,感覺有點奇怪。
    3. 本次使用Pytorch實做一個簡單的CNN網路架構,CNN最主要會由Convolutional和Maxpool兩種層所組成,激勵函數是Relu,然後最後要用線性層(Fully Connected)輸出預測結果。
    4. 初次訓練只用了2層CNN搭配1層的線性層,但訓練的Loss率沒有想像好(低),故再次修改用4層CNN搭配3層的線性層,一方面想看看線姓層的降維幅度是否也會改善Loss率。
    5. 在訓練、驗證和測試資料夾中,我都定義了Indoor和Outdoor資料夾,同時也是以此當作標籤功能。
    6. 最後輸出時,我們去觀察哪個Label的得分最高,就可以知道他是屬於哪個類型的,假定我規劃0是室內,1是室外,當我發現0的得分是0.9991,1的得分是0.1234,那則代表這張圖是室外。
    7. 訓練過程中,一開始Model會去預測一個分數,但我們會依照正確的分類給他真正的分數,接著算出Loss後,經過backward去更新權重,Model就會知道屬於這張圖真正的分數應該是多少,因此一開始Loss極高是正常的,但會隨著訓練逐漸降低,但是Loss如果隨著訓練持續亂跳亂跳的,那有可能代表圖片的Label太雜亂,Model會精神錯亂,這時候你就必須回頭去檢視你的資料集正不正確。
  • 程式片段
    1. 定義網路,4層CNN搭配3層的線性層

本次我是使用多分類方式來做,故採用CrossEntropy的Loss計算。

Crossentropy主要用在計算多分類的交叉熵。

本次訓練15次,主要記得開啟訓練模式、梯度歸零和更新等,Model會預測這張圖是屬於哪個類型,一開始Model處於亂猜階段,隨著我們去算出他的Loss率,然後再backward和用step做權重更新後,Model就會學習到正確的分類。

訓練時需把Loss率反向傳播,這樣才能讓整個網路知道辨識的錯誤率為何。

驗證也是15次,記得開啟評估模式就好,因為在這裡我們想做的是驗證模型準確率,不需要再把驗證出來的結果反向傳播回模型了。

注意開啟評估模式,藉此來了解你的Model能力。

附上網路的形狀

本次作業規劃的網路形狀。
  • 測試資料與結果
    1. 訓練和驗證資料集主要來自https://diode-dataset.org/的Dataset,先人工整理出Train和Valid的資料夾,並在其中切分出Indoor和Outdoor,作為輔助,此外我也定義了Indoor是標籤0,Outdoor是標籤1。
    2. 訓練15次,可參考下圖,藍色為訓練的Loss率,橘色是驗證的Loss率,訓練的Loss率原本逐漸下降,但到後期又急速攀升,初步判斷是訓練Label雜亂造成?但因為訓練次數不夠多,如果訓練100次仍這樣亂跳,那就有可能代表圖片的Label太亂,造成loss計算不清楚;而驗證的Loss率則在訓練完第1次後趨於平緩。
訓練和驗證時,Model的Loss率變化。
  1. 在網路輸出時主要會得到一個2元素的Tensor,在這裡我沒多做轉換,若是索引0的機率高,則代表這圖是Label 0 Indoor,1的話則是Outdoor。
    1. 訓練15次後進行測試,Loss率0.31左右,得到的成功率是90多%,感覺好像有點厲害,相較先前使用3層CNN搭配一層線性層,有不錯增長。
圖片實際預測結果,此10張都是我刻意挑選用來混淆Model的圖片。
  • 討論
    1. 本次的測試資料集我是用自己拍的10張圖,其中有些我刻意選擇比較怪異的構圖,譬如在樹叢中拍攝帳篷,或是在有雨棚的路邊攤拍攝,並當作是Indoor照,企圖影響網路的判斷。
    2. 承上,之所以會這樣做是因為我認為類神經網路在擷取特徵時,應該是以構圖和色調在抓室內和室外的差別,才故意找一些界定模糊的資料。
    3. 訓練的Loss率亂跳一方面或許和原始資料集也有關,譬如有放在桌上的帆船模型,照理說和室外的海上的船是蠻像的,不過若是加大訓練次數,有機會看到Loss率逐漸趨緩,因為Model將會逐漸分辨出室內和室外該有的色調,進而判斷出正確結果,而不再是被物體的形狀限制。

若有機會,會想嘗試把RNN加入到網路中,藉此了解當面對複雜特徵且數量眾多的圖片,Model的學習能力是否有加分效果,當然可預想的是RNN效果未必會比較好。

若是你對本篇文章的原始碼有興趣,可以參考我的Github連結

在Colab Notebooks上定義並訓練Yolov4 Model (以水果辨識為例)

Yolo (You only look once)可以說是最知名的即時辨識模型了,不僅效果好,速度也快,當初的論文可以參考這裡,現在一路也出到V4了,你可以在訓練資料上定義不同的圖片和標籤,以此訓練模型,就可以輕鬆達到辨識新目標的效果。

當然我這樣講是有點浮誇,你必須注意一下你的電腦能不能如願把訓練跑完。

在這次的訓練中,我嘗試在Colab Notebooks上安裝,並訓練Yolov4,主要是為了省去GPU這個環節,因為平時我的工作筆電是一台Mac,想要跑完訓練是真的需要一點時間的;另外為了完成訓練,首先我必須找到一份資料集,再來我必須製作自己的YOLO  Format標記檔案,提供給Yolov4進行訓練。

資料集的部分,我使用的是Kaggle上的Fruits fresh and rotten for classification資料集,這其中包含了新鮮蘋果、香蕉、橘子和腐敗的蘋果、香蕉和橘子等六種類型的圖片,如何在Colab Notebooks上下載Kaggle資料集的話可以參考我這篇文章。

至於如何把Kaggle資料集轉成Yolo format的部分,可以參考這篇文章,在這裡我捨棄了用LabelImg,也捨棄了Yolo Marker,為什麼呢?純粹是因為我懶,純粹是因為我想盡量把工作自動化,雖然可能割捨掉一些正確性。

在準備好資料集和Yolo Format的標記檔案後,我們可以開始接著作模型下載和訓練的工作,首先我們要先從AlexeyAB下載darknet,這是一個使用C語言實作Yolo的共用函式庫,但是可以提供Python調用;當初是從Yolo的原始作者那fork出來的分支,因為原作者已經不維護原始程式碼了,所以網路上大多用這個版本在訓練和實戰。

首先,我們下載darknet。

當darknet下載完成後會在Colab上建立一個目錄,這裡要注意,後面訓練、打包時,都會在這個目錄底下進行。

當Make時,或模型訓練時,會需要持續寫入權重(weights),所以我們需要先把目錄權限改一下,開放寫入。

在Make打包之前,我們需要改一下組態檔案,這將影響我們後續打包完的模型如何運作,在這裡我們用sed來修改組態,sed 是「stream editor 」的縮寫,顧名思義是進行串流(stream) 的編輯。無論是在編寫shell 的或處理STDIN 的時候,當有需要進行字串取代、複製、刪除,就可以直接下命令調整,當然你也可以直接透過編輯器修改;這裡我們有幾個東西要調整。
  1. OPENCV=1,啟用OPENCV,注意,你必須先安裝OpenCV。
  2. GPU=1,啟用GPU,注意,你的Coblab要先啟用GPU。
  3. CUDNN也需要啟用,他是基於CUDA的DNN函式庫。如果你用的是自己的電腦,也有自己的GPU,可以找一下顯卡可以支援到什麼程度,然後修改ARCH,ARCH主要是指定框架裡面用到的GPU資訊,在某些狀況下會導致你的YOLO無法編譯,詳細狀況可以參考這篇文章。

接著進行Make,基本上做這些事情速度都很快,麻煩的是後面的訓練。

打包完成後,就可以開始訓練了,在訓練時,我傾向使用網路上已經訓練過的模型(Pre-Trained Model),再來做一次訓練,這樣的效果和識別能力會更好,因為在模型裡面已經有其他人訓練好的權重,如此一來,我們也可以用比較少的訓練資料量,來達到一定的辨識效果。

另外我會用yolov4-tiny.cfg做為訓練用的設定組態,之所以不用yolov4.cfg來訓練,是因為yolov4.cfg的batch數比較多,在Colab Notebooks上訓練很容易記憶體不足,或是說根本就練不起來。

訓練時我會引入前面下載的預訓練模型,需要用到一點時間訓練,基本上幾小時是跑不掉的。

訓練完畢的話,也有語法可以測試,注意一下他會在測試圖片標記出辨識結果。

圖片的辨識訓練其實不會很複雜,訓練資料集方面,也不需要把圖片都轉成同樣大小(這也是為什麼你在產生Yolo format的label資料時,需要取的是比例的相對位置,而不是像素的絕對位置),另外需要注意的是cfg設定檔中的filter數量,這個數值會和你定義的class數量相關,計算公式為filters=3*(classes+5),譬如你在資料集中定義了3個類型(classification),那filters則會是(3+5)*3=24。

後面我會花一點時間整理Yolo的做法和基本原理,並且附上我的些許心得,如此一來才能算真正掌握了Yolo這套框架。(待續)

January 14, 2022 at 04:33PM

利用Open CV自動找出Image的bounding box並轉成Yolo Label

這陣子我試圖在Yolo V4上訓練一個識別模型,在我原始構想中的功能,是為了讓這個模型可以擁有判斷水果新鮮度、腐敗程度的能力,想當然,如果要達到一定精準度,那勢必是要給他一些訓練資料(Training Data),而訓練資料的多寡也會影響模型判斷的正確性,於是乎,我就在網路上找了些資料集(Dataset),幸運的是最後我在Kaggle上找到這個近乎理想的資料集,裏面包含了腐敗的蘋果、香蕉、柑橘,同樣也有新鮮的蘋果、香蕉、柑橘,其實這資料集的原始功能是用來做一個分類器,只是被我暫借來用在Yolo V4上罷了。

也就因為有這些前因後果,這時候我就遇到訓練模型最耗工的時候了:資料預處理;在這個資料集中,有腐敗的蘋果、香蕉、柑橘,同樣也有新鮮的蘋果、香蕉、柑橘,我將各個型態的水果歸為一類,因為雖然資料集只有6類(Classes),但是資料集中涵蓋多個變化時段、多個角度、多個面向的水果,故所有圖片總結起來不多不少也是上萬張有。

在Yolo訓練時所需要的Label裡,需要標記圖片中的重點,框出一個正方形,告訴Yolo該去關注哪個區域,並讓Model知道這個區域是屬於哪類型的資料,譬如說我們可以在一張框出人臉來,然後告訴Model這個區域的樣子就是人類。

也就是說,當我想要讓Model知道我這些資料集是腐爛、新鮮的香蕉,我應該先把這些水果的特徵標記起來,然後告訴Model這是腐爛、新鮮的水果,也就是一個多分類的議題,雖然我們知道人工標記是最準確的作法,但當我們知道圖便有上萬張時還採人工用軟體(LabelImg、Yolo marker)來標記,就顯得比較不切實際了。
於是我選擇用Open CV在圖片上框正方形,也就是找出bounding box,要完成這個任務,需完成幾個步驟。

  • 首先我們輸入一張圖片。

  • 把cv2. cvtColor把圖片由RGB轉成HSV,主要是為了能方便表現出特定顏色,HSV即色相、飽和度、明度(Hue, Saturation, Value),色相(H)是色彩的基本屬性,就是平常所說的顏色名稱,如紅色、黃色等,飽和度(S)是指色彩的純度,越高色彩越純,低則逐漸變灰,取0-100%的數值,明度(V),亮度(L),取0-100%、越高越亮。

  • 將圖片二值化,指的不是變成灰階圖片,因為灰階圖片的像素有0~255的變化,二值化指的是圖片中的像素只會有0、255兩種變化,透過cv2.inRange(img2, lower, upper)設定閥值,像素超過upper的設為255,像素值低於lower的設為0。

  • 將圖片模糊化,主要是用平均濾波去雜訊,減少圖片上的雜訊影響,資料集中有些圖片有刻意加上雜訊,效果類似撒上了胡椒粉,在這裡可以一定程度抹去,但是這也可能導致圖片邊緣變模糊。

  • 主要因為模糊化去雜訊後,此時圖片的像素會出現0、255以外的變化,所以再做一次二值化,會得到一張正確的二值化圖片。

  • 最後把圖片像素做NOT運算,如此可以凸現出物件,把物件部分變成白色

  • 最後用cv2. findContours找出物件邊緣外的方框,也就是畫出個正方形,最後綠色正方形的四個角,就是我們需要的資訊了。

  • 整體來說,用Open CV找出bounding box的效果算很不錯,大部分時候程式都能在圖片上找出一個方框,並完整地把特徵圈出來。

當然,世上並不存在所謂的銀子彈(silver bullet),某些時候程式也會劃錯,特別是當圖片中的物件邊緣特別不明顯時,錯誤就會被凸顯出來。

有些時候,可能只找出物件的部分區塊而已,但我就原始資料集的狀況看來,倒覺得這問題不大,一方面是因為標記不完整的圖片,他們通常不是標記到很離譜的地方,譬如本該標香蕉,卻標到香蕉的影子,反倒很忠實地找到物件所在位置,故即便只有找到一部份的香蕉,相信仍不影響訓練結果。

但是找出四個角還不夠,最後最後,我們要透過一個公式來算出這個矩陣在圖片中的相對位置,如此一來Yolo才能在不同圖片中找出你指定的區域;在cv2. findContours找到的資訊分別是x (像素起始X)、y(像素起始Y)、w(正方形寬)、h(正方形高),而我們應該先找出圖片的原始長寬,接著用xmin、xmax、ymin、ymax,才能算出比例,xmin當然就是x,ymin則是y,透過w、h我們可以推算出xmax、ymax,最後和原圖長寬算出比例。

//原先找出的4個腳,分別轉換出四角的最大值、最小值
xmin=x
xmax=x+w
xmin=y
ymax=y+h
xp = (xmin + (xmax-xmin)/2) * 1.0 / image_width
yp = (ymin + (ymax-ymin)/2) * 1.0 / image_height
wp = (xmax-xmin) * 1.0 / image_ width
hp = (ymax-ymin) * 1.0 / image_ height
另一方面,我們會用0~5先定義好6種水果的Label,再將xp、yp、wp、hp寫入文字檔後,即完成這一張圖片的標記,仔細想想,是不是比一張一張標記快多了?總結一下,在我使用的資料集共有13319張圖片,而我只用了616.294秒就完成了標記工作!

January 11, 2022 at 10:58PM