文章目錄
- 前言
- 一、 圖像顏色處理
- 1. 顏色加法
- 1.1 OpenCV加法
- 1.2 numpy加法
- 1.3 顏色加權加法
- 2.顏色空間
- 2.1 RGB顏色空間
- 2.2 HSV顏色空間
- 3. 顏色轉換
- 3.1 讀取的圖片同時轉換
- 3.2 對已有圖片轉換
- 4. 圖像灰度化
- 4.1 灰度圖概念
- 4.2 最大值灰度化
- 4.3 平均值灰度化
- 4.4 加權均值灰度化
- 5. 圖像二值化處理
- 5.1 二值圖像
- 5.2 閾值法(THRESH_BINARY)
- 5.3 反閾值法(THRESH_BINARY_INV)
- 5.4 截斷閾值法(THRESH_TRUNC)
- 5.5 低閾值零處理(THRESH_TOZERO)
- 5.6 超閾值零處理(THRESH_TOZERO_INV)
- 5.7 OTSU閾值法
- 5.8 自適應二值化
- 5.8.1 取均值
- 5.8.2 加權求和(高斯法)
- 二、圖像仿射變換
- 1. 圖像翻轉(圖像鏡像旋轉)
- 2.仿射變換
- 2.1 仿射變換的基本原理 -- 單點旋轉
- 2.2 仿射變換函數
- 總結
- --- opencv系列相關文章 ---
前言
昨天我們初步了解了opencv-python,學習了利用cv2的相關API對已知路徑下的圖片進行文件基本操作,最后還完成了對視頻的操作。今天我們要學習圖像顏色處理及圖像仿射變換。
一、 圖像顏色處理
1. 顏色加法
1.1 OpenCV加法
- opencv加法是
飽和操作
- 飽和的意思是若計算的結果超出當前上限,就以當前上限值作為最終結果。
- 在opencv中,顏色值默認是np.unit8類型,上限為255
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像cao = cv.imread('../images/cao.png')pig = cv.imread('../images/pig.png')# 飽和操作 -- 范圍是np.uint8, 超出上限的取255dst1 = cv.add(cao, pig)print(cao[500,500,:])print(pig[500,500,:])print('cv飽和運算\n',dst1[500,500,:])cv.imshow('dst1', dst1)# 設定顯示等待cv.waitKey(0)# 釋放內存cv.destroyAllWindows()
1.2 numpy加法
- 利用numpy加法的前提是進行加法運算的兩張圖片的形狀大小要一樣
- numpy加法是模運算
- 取模意思是用計算結果對255取模
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像cao = cv.imread('../images/cao.png')pig = cv.imread('../images/pig.png')# 飽和操作 -- 范圍是np.uint8, 超出上限的取255dst2 = cao + pigprint(cao[500,500,:])print(pig[500,500,:])print('numpy取模運算\n',dst2[500,500,:])cv.imshow('dst1', dst2)# 設定顯示等待cv.waitKey(0)# 釋放內存cv.destroyAllWindows()
1.3 顏色加權加法
同樣滿足飽和運算
基本格式:
cv2.addWeighted(src1,alpha,src2,deta,gamma)
參數說明:
src1
、src2
:輸入圖像。alpha
、beta
:兩張圖象權重。gamma
:亮度調整值。gamma > 0
,圖像會變亮。gamma < 0
,圖像會變暗。gamma = 0
,則沒有額外的亮度調整。
示例
import cv2 as cv
# import numpy as np
if __name__ == '__main__':# 讀圖cao = cv.imread('../images/cao.png')pig = cv.imread('../images/pig.png')# cv2.addWeighted(src1,alpha,src2,deta,gamma) dst3 = cv.addWeighted(cao,0.3,pig,0.7,100)print(cao[500,500,:])print(pig[500,500,:])print('cv加權運算\n',dst3[500,500,:])# 顯示圖片cv.imshow('dst3',dst3)cv.waitKey(0)# 釋放內存cv.destroyAllWindows()
2.顏色空間
2.1 RGB顏色空間
在圖像處理中,最常見的就是RGB顏色空間。RGB顏色空間是我們接觸最多的顏色空間,是一種用于表示和顯示彩色圖像的一種顏色模型。RGB
代表紅色(Red)
、綠色(Green)
和藍色(Blue)
,這三種顏色通過不同強度的光的組合來創建其他顏色,廣泛應用于我們的生活中,比如電視、電腦顯示屏以及上面實驗中所介紹的RGB彩色圖。
RGB顏色模型基于笛卡爾坐標系,如下圖所示,RGB原色值位于3個角上,二次色青色、紅色和黃色位于另外三個角上,黑色位于原點處,白色位于離原點最遠的角上。因為黑色在RGB三通道中表現為(0,0,0),所以映射到這里就是原點;而白色是(255,255,255),所以映射到這里就是三個坐標為最大值的點。
RGB顏色空間可以產生大約1600萬(2553255^32553)種顏色,幾乎包括了世界上的所有顏色,也就是說可以使用RGB顏色空間來生成任意一種顏色。
注意:
在OpenCV中,顏色是以BGR
的方式進行存儲的,而不是RGB,這也是上面紅色的像素值是(0,0,255)而不是(255,0,0)的原因。
2.2 HSV顏色空間
HSV顏色空間指的是HSV顏色模型,這是一種與RGB顏色模型并列的顏色空間表示法。RGB顏色模型使用紅、綠、藍三原色的強度來表示顏色,是一種加色法模型,即顏色的混合是添加三原色的強度。而HSV顏色空間
使用色調(Hue)
、飽和度(Saturation)
和亮度(Value)
三個參數來表示顏色,色調H表示顏色的種類,如紅色、綠色、藍色等;飽和度表示顏色的純度或強度,如紅色越純,飽和度就越高;亮度表示顏色的明暗程度,如黑色比白色亮度低。
HSV顏色模型是一種六角錐體模型,如下圖所示:
色調H:
使用角度度量,取值范圍為0°~360°,從紅色開始按逆時針方向計算,紅色為0°,綠色為120°,藍色為240°。它們的補色是:黃色為60°,青色為180°,紫色為300°。通過改變H的值,可以選擇不同的顏色
飽和度S:
飽和度S表示顏色接近光譜色的程度。一種顏色可以看成是某種光譜色與白色混合的結果。其中光譜色所占的比例越大,顏色接近光譜色的程度就越高,顏色的飽和度就越高。飽和度越高,顏色就越深而艷,光譜色的白光成分為0,飽和度達到最高。通常取值范圍為0%~100%,其中0%表示灰色或無色,100%表示純色,通過調整飽和度的值,可以使顏色變得更加鮮艷或者更加灰暗。
明度V:
明度表示顏色明亮的程度,對于光源色,明度值與發光體的光亮度有關;對于物體色,此值和物體的透射比或反射比有關。通常取值范圍為0%(黑)到100%(白),通過調整明度的值,可以使顏色變得更亮或者更暗。
一般對顏色空間的圖像進行有效處理都是在HSV空間進行的,然后對于基本色中對應的HSV分量需要給定一個嚴格的范圍,下面是通過實驗計算的模糊范圍(準確的范圍在網上都沒有給出)。
H: 0— 180
S: 0— 255
V: 0— 255
此處把部分紅色歸為紫色范圍:
為什么有了RGB顏色空間我們還是需要轉換成HSV顏色空間來進行圖像處理呢?
- 符合人類對顏色的感知方式:人類對顏色的感知是基于色調、飽和度和亮度三個維度的,而HSV顏色空間恰好就是通過這三個維度來描述顏色的。因此,使用HSV空間處理圖像可以更直觀地調整顏色和進行色彩平衡等操作,更符合人類的感知習慣。
- 顏色調整更加直觀:在HSV顏色空間中,色調、飽和度和亮度的調整都是直觀的,而在RGB顏色空間中調整顏色不那么直觀。例如,在RGB空間中要調整紅色系的顏色,需要同時調整R、G、B三個通道的數值,而在HSV空間中只需要調整色調和飽和度即可。
- 降維處理有利于計算:在圖像處理中,降維處理可以減少計算的復雜性和計算量。HSV顏色空間相對于RGB顏色空間,減少了兩個維度(紅、綠、藍),這有利于進行一些計算和處理任務,比如色彩分割、匹配等。
因此,在進行圖片顏色識別時,我們會將RGB圖像轉換到HSV顏色空間,然后根據顏色區間來識別目標顏色。
3. 顏色轉換
3.1 讀取的圖片同時轉換
基本格式:
cv2.imread(path [,讀取方式])
參數說明:
昨天我們講了path,而讀取方式有很多種,默認是BGR形式
常見 mode 參數對照表:
模式值(flag) | 常量名 | 功能說明 |
---|---|---|
1 | cv2.IMREAD_COLOR (默認) | 以彩色方式讀取圖像,忽略透明通道 |
0 | cv2.IMREAD_GRAYSCALE | 以灰度模式讀取圖像(單通道) |
-1 | cv2.IMREAD_UNCHANGED | 以原始方式讀取圖像,保留 alpha 通道 |
2 | cv2.IMREAD_ANYDEPTH | 若圖像是 16/32 位深度,將按原深度讀取 |
4 | cv2.IMREAD_ANYCOLOR | 以任意顏色格式讀取(不強制轉換 BGR) |
8 | cv2.IMREAD_IGNORE_ORIENTATION | 忽略圖像的 EXIF 方向元數據 |
示例
import cv2 as cv
# import numpy as np
if __name__ == '__main__':# 讀取圖像為灰度圖cat = cv.imread('../images/1.jpg',cv.IMREAD_GRAYSCALE)# 顯示圖像cv.imshow('cat', cat)cv.waitKey(0)cv.destroyAllWindows()
3.2 對已有圖片轉換
基本格式:
cv2.cvtColor(img, code)
參數說明:
img
:輸入圖像,可以是一個Numpy數組繪著一個OpenCV的Mat對象Mat
是一個核心的數據結構,主要用于存儲圖像和矩陣數據。在 Python 中使用 OpenCV 時,通常直接處理的是 NumPy 數組,cv2
模塊自動將Mat
對象轉換為 NumPy 數組。二者之間的轉換是透明且自動完成的。例如,當你使用cv2.imread()
函數讀取圖像時,返回的是一個 NumPy 數組,但在C++中則是Mat
對象。code
:指定轉換的類型,可以使用預定義的轉換代碼。- 例如
cv2.COLOR_RGB2GRAY
表示從rgb到灰度圖像的轉換。
- 例如
示例
import cv2 as cv
# import numpy as np
if __name__ == '__main__':# 讀取圖像cat = cv.imread('../images/1.jpg')# 顏色轉換 cv2.cvtColor(img, code)# 轉灰度gray_ = cv.cvtColor(cat, cv.COLOR_BGR2GRAY)# 轉HSVhsv = cv.cvtColor(cat, cv.COLOR_BGR2HSV)# 顯示圖片cv.imshow('cat', cat)cv.imshow('gray',gray_)cv.imshow('hsv',hsv)cv.waitKey(0)cv.destroyAllWindows()
4. 圖像灰度化
4.1 灰度圖概念
每個像素只有一個采樣顏色的圖像,這類圖像通常顯示為從最暗的黑色到最亮的白色
的灰度
,盡管理論上這個采樣可以任何顏色的不同深淺,甚至可以是不同亮度上的不同顏色。灰度圖像與黑白圖像不同,在計算機圖像領域中黑白圖像只有黑色與白色兩種顏色;但是,灰度圖像在黑色與白色之間還有許多級的顏色深度。灰度圖像經常是在單個電磁波頻譜如可見光內測量每個像素的亮度得到的,用于顯示的灰度圖像通常用每個采樣像素8位的非線性尺度來保存,這樣可以有256級灰度。
原圖 | 灰度圖 |
---|---|
![]() | ![]() |
4.2 最大值灰度化
對于彩色圖像的每個像素,它會從R、G、B三個通道的值中選出最大的一個,并將其作為灰度圖像中對應位置的像素值。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像img_0 = cv.imread('../images/pig.png')shape = img_0.shape # (h, w, c)# 創建一個新圖用于存放灰度像素img_1 = []# 最大值法(選取三通道中的最大值)for i in img_0:for j in i:img_1.append(max(j))img_2 = np.array(img_1).reshape(shape[0:2])cv.imshow('old',img_0)cv.imshow('gray', img_2)cv.waitKey(0)cv.destroyAllWindows()
4.3 平均值灰度化
對于彩色圖像的每個像素,它會將R、G、B三個通道的像素值全部加起來,然后再除以三,得到的平均值就是灰度圖像中對應位置的像素值。
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像img_0 = cv.imread('../images/1.jpg')shape = img_0.shape # (h, w, c)# 創建一個新圖用于存放灰度像素img_1 = []# 平均值法for i in img_0:for j in i:tmp = np.mean(j).astype(np.uint8)img_1.append(tmp) img_2 = np.array(img_1).reshape(shape[0:2])cv.imshow('old',img_0)cv.imshow('gray', img_2)cv.waitKey(0)cv.destroyAllWindows()
4.4 加權均值灰度化
對于彩色圖像的每個像素,它會按照一定的權重去乘以每個通道的像素值,并將其相加,得到最后的值就是灰度圖像中對應位置的像素值。本實驗中,權重的比例為: R乘以0.299
,G乘以0.587
,B乘以0.114
,這是經過大量實驗得到的一個權重比例,也是一個比較常用的權重比例
。
所使用的權重之和應該等于1。這是為了確保生成的灰度圖像素值保持在合理的亮度范圍內,并且不會因為權重的比例不當導致整體過亮或過暗。
opencv中的灰度化默認使用的加權均值
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像img_0 = cv.imread('../images/1.jpg')shape = img_0.shape # (h, w, c)# 創建一個新圖用于存放灰度像素img_1 = []# 權重weights = np.array([1/4,1/2,1/4])# 加權均值法for i in img_0:for j in i:tmp = np.average(j,weights=weights).astype(np.uint8)img_1.append(tmp)img_2 = np.array(img_1).reshape(shape[0:2])cv.imshow('old',img_0)cv.imshow('gray', img_2)cv.waitKey(0)cv.destroyAllWindows()
5. 圖像二值化處理
將某張圖像的所有像素改成只有兩種值之一。
5.1 二值圖像
一幅二值圖像的二維矩陣僅由0、1兩個值構成,“0”代表黑色,“1”代白色。由于每一像素(矩陣中每一元素)取值僅有0、1兩種可能,所以計算機中二值圖像的數據類型通常為1個二進制位。二值圖像通常用于文字、線條圖的掃描識別(OCR)和掩膜圖像的存儲。
其操作的圖像也必須是灰度圖。也就是說,二值化的過程,就是將一張灰度圖上的像素根據某種規則修改為0和maxval(maxval表示最大值,一般為255,顯示白色)兩種像素值,使圖像呈現黑白的效果,能夠幫助我們更好地分析圖像中的形狀、邊緣和輪廓等特征。
-
特點和功能:
- 簡便:降低計算量和計算需求,加快處理速度。
- 節約資源:二值圖像占用空間遠小于彩色圖。
- 邊緣檢測:二值化常作為邊緣檢測的預處理步驟,因為簡化后的圖易于識別出輪廓和邊界。
全局閾值法基本格式:
_,binary = cv2.threshold(img,thresh,maxval,type)
參數說明:
img
:輸入圖像,要進行二值化處理的灰度圖。thresh
:設定的閾值。當像素值大于(或小于,取決于閾值類型)thresh
時,該像素被賦予的值。type
:閾值處理的類型。- 返回值:
- 第一個值(通常用下劃線表示):計算出的閾值,若使用自適應閾值法,會根據算法自動計算出這個值。
- 第二個值(binary):二值化后的圖像矩陣。與輸入圖像尺寸相同。
5.2 閾值法(THRESH_BINARY)
閾值法就是通過設置一個閾值,將灰度圖中的每一個像素值與該閾值進行比較,小于等于閾值
的像素就被設置為0(通常代表背景)
,大于閾值
的像素就被設置為maxval(通常代表前景
)。對于我們的8位圖像(0~255)來說,通常是設置為255。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 二值化 閾值法thresh, binary = cv.threshold(gray_, 127, 255, cv.THRESH_BINARY)print(thresh)# 顯示效果cv.imshow('binary', binary)cv.waitKey(0)cv.destroyAllWindows()
5.3 反閾值法(THRESH_BINARY_INV)
顧名思義,就是與閾值法相反
。反閾值法是當灰度圖的像素值大于閾值時,該像素值將會變成0(黑),當灰度圖的像素值小于等于閾值時,該像素值將會變成maxval。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 二值化 反閾值法thresh, binary_inv = cv.threshold(gray_, 127, 255, cv.THRESH_BINARY_INV)print(thresh)# 顯示效果cv.imshow('binary_inv', binary_inv)cv.waitKey(0)cv.destroyAllWindows()
5.4 截斷閾值法(THRESH_TRUNC)
截斷閾值法,指將灰度圖中的所有像素與閾值進行比較,像素值大于閾值的部分將會被修改為閾值,小于等于閾值的部分不變。
經過截斷閾值法
處理過的二值化圖中的最大像素值就是閾值
,此時參數maxval不起作用
。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 二值化 截斷閾值法_, trunc = cv.threshold(gray_,127,255, cv.THRESH_TRUNC)# 顯示效果cv.imshow('trunc', trunc)cv.waitKey(0)cv.destroyAllWindows()
5.5 低閾值零處理(THRESH_TOZERO)
低閾值零處理,字面意思,就是像素值小于等于閾值的部分被置為0(也就是黑色),大于閾值的部分不變。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 低閾值零處理_, tozero = cv.threshold(gray_,127,255, cv.THRESH_TOZERO)# 顯示效果cv.imshow('tozero', tozero)cv.waitKey(0)cv.destroyAllWindows()
5.6 超閾值零處理(THRESH_TOZERO_INV)
超閾值零處理就是將灰度圖中的每個像素與閾值進行比較,像素值大于閾值的部分置為0(也就是黑色),像素值小于等于閾值的部分不變。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 超閾值零處理_, tozero_inv = cv.threshold(gray_,127,255, cv.THRESH_TOZERO_INV)# 顯示效果cv.imshow('tozero_inv', tozero_inv)cv.waitKey(0)cv.destroyAllWindows()
5.7 OTSU閾值法
cv2.THRESH_OTS 并不是一個有效的閾值類型或標。THRESH_OTSU
本身并不是一個獨立的閾值化方法,而是與 OpenCV 中的二值化方法結合使用的一個標志。具體來說,THRESH_OTSU
通常與 THRESH_BINARY
或 THRESH_BINARY_INV
結合使用。在實際應用中,如果你使用 THRESH_OTSU
標志但沒有指定其他二值化類型,默認情況下它會與 THRESH_BINARY
結合使用。也就是說,當你僅指定了 cv2.THRESH_OTSU
,實際上等同于同時指定了 cv2.THRESH_BINARY + cv2.THRESH_OTSU
。
在介紹OTSU閾值法之前,我們首先要了解一下雙峰圖片的概念。
雙峰圖片就是指灰度圖的直方圖上有兩個峰值,直方圖就是對灰度圖中每個像素值的點的個數的統計圖,如下圖所示。
- 灰度圖直方圖的基礎概念
- 灰度級:
- 在灰度圖像中,每個像素的值代表其亮度,通常范圍是 0 到 255(對于 8 位灰度圖像)。
- 0 表示黑色,255 表示白色,中間的值表示不同程度的灰色。
- 直方圖定義:
- 直方圖是一個柱狀圖,其中 x 軸表示灰度級(從 0 到 255),y 軸表示對應灰度級在圖像中出現的次數(頻率)。
- 每個柱子的高度代表該灰度級在圖像中出現的像素數量。
OTSU算法是通過一個值將這張圖分前景色和背景色(也就是灰度圖中小于這個值的是一類,大于這個值的是一類。例如,如果你設置閾值為128,則所有大于128的像素點可以被視作前景,而小于等于128的像素點則被視為背景。),通過統計學方法(最大類間方差)來驗證該值的合理性,當根據該值進行分割時,使用最大類間方差計算得到的值最大時,該值就是二值化算法中所需要的閾值。通常該值是從灰度圖中的最小值加1開始進行迭代計算,直到灰度圖中的最大像素值減1,然后把得到的最大類間方差值進行比較,來得到二值化的閾值。以下是一些符號規定:
T:閾值
N0N_{0}N0?:前景像素點數
N1N_{1}N1?:背景像素點數
ω0\omega_{0}ω0?:前景的像素點數占整幅圖像的比例
ω1\omega_{1}ω1?:背景的像素點數占整幅圖像的比例
U0\mathcal{U_{0}}U0?:前景的平均像素值
U1\mathcal{U_{1}}U1?:背景的平均像素值
U\mathcal{U}U:整幅圖的平均像素值
rows×cols:圖像的行數和列數
下面舉個例子,有一張大小為4×4的圖片,假設閾值T為1,則:
也就是這張圖片根據閾值1分為了前景(像素為2的部分)和背景(像素為0)的部分,并且計算出了OTSU算法所需要的各個數據,根據上面的數據,我們給出計算類間方差的公式:
g=ω0(μ0?μ)2+ω1(μ1?μ)2g=\omega_{0}(\mu_{0}-\mu)^{2}+\omega_{1}(\mu_{1}-\mu)^{2} g=ω0?(μ0??μ)2+ω1?(μ1??μ)2
g就是前景與背景兩類之間的方差,這個值越大,說明前景和背景的差別就越大,效果就越好。OTSU算法就是在灰度圖的像素值范圍內遍歷閾值T,使得g最大,基本上雙峰圖片的閾值T在兩峰之間的谷底。
通過OTSU算法得到閾值之后,就可以結合
上面的方法根據該閾值進行二值化,在本實驗中有THRESH_OTSU
和THRESH_INV_OTSU
兩種方法,就是在計算出閾值后結合了閾值法和反閾值法。
THRESH_OTSU | THRESH_INV_OTSU |
---|---|
![]() | ![]() |
注意:
使用OTSU算法計算閾值時,組件中的thresh參數將不再有任何作用。
THRESH_OTSU
默認是cv2.THRESH_BINARY
+ cv2.THRESH_OTSU
示例
otsu反閾值法
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# OTSU結合反閾值_, otsu_inv = cv.threshold(gray_,127,255, cv.THRESH_OTSU + cv.THRESH_BINARY_INV)# 顯示效果cv.imshow('otsu_inv', otsu_inv)cv.waitKey(0)cv.destroyAllWindows()
5.8 自適應二值化
與二值化算法相比,自適應二值化更加適合用在明暗分布不均的圖片,因為圖片的明暗不均,導致圖片上的每一小部分都要使用不同的閾值進行二值化處理,這時候傳統的二值化算法就無法滿足我們的需求了,于是就出現了自適應二值化。
自適應二值化方法會對圖像中的所有像素點計算其各自的閾值,這樣能夠更好的保留圖片里的一些信息。自適應二值化組件內容如下圖所示:
基本格式:
cv2.adaptiveThreshold(image_np_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 7, 10)
參數說明:
maxval
:最大閾值,一般為255
adaptiveMethod
:小區域閾值的計算方式:
ADAPTIVE_THRESH_MEAN_C
:小區域內取均值
ADAPTIVE_THRESH_GAUSSIAN_C
:小區域內加權求和,權重是個高斯核
thresholdType
:二值化方法,只能使用THRESH_BINARY
、THRESH_BINARY_INV
,也就是閾值法和反閾值法
blockSize
:選取的小區域的面積,如7就是7*7的小塊。
c
:最終閾值等于小區域計算出的閾值再減去此值
5.8.1 取均值
假如我們使用的小區域是3*3的,那么就會從圖片的左上角開始(也就是像素值為162的地方)計算其鄰域內的平均值,如果處于邊緣地區就會對邊界進行填充,填充值就是邊界的像素點,如下圖所示:
那么對于左上角像素值為162的這個點,161(也就是上圖中括號內的計算結果,結果會進行取整)就是根據平均值計算出來的閾值,接著減去一個固定值C,得到的結果就是左上角這個點的二值化閾值了,接著根據選取的是閾值法還是反閾值法進行二值化操作。緊接著,向右滑動計算每個點的鄰域內的平均值,直到計算出右下角的點的閾值為止。我們所用到的不斷滑動的小區域被稱之為核,比如3*3的小區域叫做3*3的核,并且核的大小都是奇數
個,也就是3*3、5*5、7*7等。(取奇數個是為了能有中心)
自適應二值化(Adaptive Thresholding)的核心思想就是為圖像中的每個像素點計算一個局部閾值。這種方法與全局閾值化不同,后者對整個圖像使用同一個固定的閾值。而在自適應二值化中,每個像素的閾值是基于其周圍鄰域內的像素值動態確定的。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 自適應二值化adaption_mean = cv.adaptiveThreshold(gray_, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY,3,2)# 顯示效果cv.imshow('adaption_mean', adaption_mean)cv.waitKey(0)cv.destroyAllWindows()
5.8.2 加權求和(高斯法)
對小區域內的像素進行加權求和得到新的閾值,其權重值來自于高斯分布。
高斯分布,通過概率密度函數來定義高斯分布,一維高斯概率分布函數為:
p(y)=1σ2πe?(y?μ)22σ2p(y)={\frac{1}{\sigma{\sqrt{2\pi}}}}e^{{\frac{-(y-\mu)^{2}}{2\sigma^{2}}}} p(y)=σ2π?1?e2σ2?(y?μ)2?
通過改變函數中和的值,我們可以得到如下圖像,其中均值為μ\muμ,標準差為σ2\sigma^{2}σ2。
此時我們拓展到二維圖像,一般情況下我們使x軸和y軸的相等并且,此時我們可以得到二維高斯函數的表達式為:
g(x,y)=12πσ2e?(x2+y2)2σ2g(x,y)=\frac{1}{2\pi\sigma ^{2}}e^{-\frac{(x^{2}+y^{2})}{2\sigma^{2}}} g(x,y)=2πσ21?e?2σ2(x2+y2)?
高斯概率函數是相對于二維坐標產生的,其中(x,y)為點坐標,要得到一個高斯濾波器模板,應先對高斯函數進行離散化,將得到的值作為模板的系數。例如:要產生一個3*3的高斯權重核,以核的中心位置為坐標原點進行取樣,其周圍的坐標如下圖所示(x軸水平向右,y軸豎直向上)
將坐標帶入上面的公式中,即可得到一個高斯權重核。
而在opencv里,當kernel(小區域)的尺寸為1、3、5、7并且用戶沒有設置sigma的時候(sigma <= 0),核值就會取固定的系數,這是一種默認的值是高斯函數的近似。
kernel尺寸 | 核值 |
---|---|
1 | [1] |
3 | [0.25, 0.5, 0.25] |
5 | [0.0625, 0.25, 0.375, 0.25, 0.0625] |
7 | [0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125] |
比如kernel的尺寸為3*3時,使用
[0.250.50.25]×[0.250.50.25]\left[\begin{array}{c}{{0.25}}\\ {{0.5}}\\ {{0.25}}\end{array}\right]\times\left[0.25~~~~0.5~~~~0.25\right] ?0.250.50.25??×[0.25????0.5????0.25]
進行矩陣的乘法,就會得到如下的權重值,其他的類似。
kernel=[0.06250.1250.06250.1250.250.1250.06250.1250.0625]kernel=\left[\begin{array}{c}{{0.0625~~~0.125~~~0.0625}}\\{{0.125~~~~0.25~~~~0.125}}\\ {{0.0625~~~0.125~~~0.0625}} \end{array}\right] kernel=?0.0625???0.125???0.06250.125????0.25????0.1250.0625???0.125???0.0625??
通過這個高斯核,即可對圖片中的每個像素去計算其閾值,并將該閾值減去固定值得到最終閾值,然后根據二值化規則進行二值化。
而當kernels尺寸超過7的時候,如果sigma設置合法(用戶設置了sigma),則按照高斯公式計算.當sigma不合法(用戶沒有設置sigma),則按照如下公式計算sigma的值:
σ=0.3?((ksize?1)?0.5?1)+0.8\sigma=0.3*\big((k s i z e-1)*0.5-1\big)+0.8 σ=0.3?((ksize?1)?0.5?1)+0.8
某像素點的閾值計算過程如下圖所示:
首先還是對邊界進行填充,然后計算原圖中的左上角(也就是162像素值的位置)的二值化閾值,其計算過程如上圖所示,再然后根據選擇的二值化方法對左上角的像素點進行二值化,之后核向右繼續計算第二個像素點的閾值,第三個像素點的閾值…直到右下角(也就是155像素值的位置)為止。
當核的大小不同時,僅僅是核的參數會發生變化,計算過程與此是一樣的。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 讀取圖像flower = cv.imread('../images/flower.png')# 調整圖片大小flower = cv.resize(flower,(360, 360))# 灰度化處理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 自適應二值化--高斯核gauss = cv.adaptiveThreshold(gray_, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY,3,2)# 顯示效果cv.imshow('gauss', gauss)cv.waitKey(0)cv.destroyAllWindows()
二、圖像仿射變換
1. 圖像翻轉(圖像鏡像旋轉)
在OpenCV中,圖片的鏡像旋轉是以圖像的中心為原點
進行鏡像翻轉的。
基本格式:
cv2.flip(img,flipcode
參數說明:
- im.\ 要翻轉的圖像
- flipcode: 指定翻轉類型的標志
- flipcode=0: 垂直翻轉,圖片像素點沿x軸翻轉
- flipcode>0: 水平翻轉,圖片像素點沿y軸翻轉
- flipcode<0: 水平垂直翻轉,水平翻轉和垂直翻轉的結合
示例
import cv2 as cvif __name__ == '__main__':# 讀取圖片face = cv.imread('../images/face.png')# flipcode = 0 垂直翻轉,圖片像素點沿x軸翻轉flip_0 = cv.flip(face, 0)# flipcode >0 水平翻轉,圖片像素點沿y軸翻轉flip_1 = cv.flip(face, 2)# flipcode <0 水平垂直翻轉,水平翻轉和垂直翻轉的結合flip__1 = cv.flip(face, -1)# 顯示效果cv.imshow('face', face)cv.imshow('flip_0', flip_0)cv.imshow('flip_1', flip_1)cv.imshow('flip_-1', flip__1)cv.waitKey(0)cv.destroyAllWindows()
2.仿射變換
? 仿射變換(Affine Transformation)是一種線性變換,保持了點之間的相對距離不變。
-
仿射變換的基本性質
- 保持直線
- 保持平行
- 比例不變性
- 不保持角度和長度
-
常見的仿射變換類型
- 旋轉:繞著某個點或軸旋轉一定角度。
- 平移:僅改變物體的位置,不改變其形狀和大小。
- 縮放:改變物體的大小。
- 剪切:使物體發生傾斜變形。
2.1 仿射變換的基本原理 – 單點旋轉
首先我們以最簡單的一個點的旋轉為例子,且以最簡單的情況舉例,令旋轉中心為坐標系中心O(0,0)O(0,0)O(0,0),假設有一點P0(x0,y0)P_{0}(x_{0},y_{0})P0?(x0?,y0?),P0P_{0}P0?離旋轉中心OOO的距離為rrr,OP0OP_{0}OP0?與坐標軸x軸的夾角為α\alphaα,P0P_{0}P0?繞O順時針旋轉θ\thetaθ角后對應的點為P(x,y)P(x,y)P(x,y)。
那么我們可以得到如下關系:
x0=r×cos?αx_{0}=r\times\cos\alpha x0?=r×cosα
y0=r×sin?αy_{0}=r\times\sin\alpha y0?=r×sinα
x=r×cos?(α?θ)=rcos?αcos?θ+rsin?αsin?θ=x0cos?θ+y0sin?θx=r\times\cos(\alpha-\theta)=r\cos\alpha\cos\theta+r\sin\alpha\sin\theta=x_{0}\cos\theta+y_{0}\sin\theta x=r×cos(α?θ)=rcosαcosθ+rsinαsinθ=x0?cosθ+y0?sinθ
y=r×sin?(α?θ)=rsin?αcos?θ?rcos?αsin?θ=?x0sin?θ+y0cos?θy=r\times\sin(\alpha-\theta)=r\sin\alpha\cos\theta-r\cos\alpha\sin\theta=-x_{0}\sin\theta+y_{0}\cos\theta y=r×sin(α?θ)=rsinαcosθ?rcosαsinθ=?x0?sinθ+y0?cosθ
用矩陣來表示就是
[xy]=[cos?θsin?θ?sin?θcos?θ]?[x0y0]\left[\begin{array}{l l}{{x}}\\{{y}}\end{array}\right]=\left[\begin{array}{l l}{{\cos\theta~~~~\sin\theta}}\\{{-\sin\theta~~~~\cos\theta}}\\ \end{array}\right]*\left[\begin{array}{c}{{x_{0}}}\\{{y_{0}}}\end{array}\right] [xy?]=[cosθ????sinθ?sinθ????cosθ?]?[x0?y0??]
然而,在OpenCV中,旋轉時是以圖像的左上角為旋轉中心,且以逆時針為正方向,因此上面的例子中其實是個負值,那么該矩陣可寫為:
[xy]=[cos?θ?sin?θsin?θcos?θ]?[x0y0]\left[\begin{array}{l l}{{x}}\\{{y}}\end{array}\right]=\left[\begin{array}{l l}{{\cos\theta~~~~-\sin\theta}}\\{{\sin\theta~~~~\cos\theta}}\\ \end{array}\right]*\left[\begin{array}{c}{{x_{0}}}\\{{y_{0}}}\end{array}\right] [xy?]=[cosθ?????sinθsinθ????cosθ?]?[x0?y0??]
其中,
[cos?θ?sin?θsin?θcos?θ]\left[\begin{array}{l l}{{\cos\theta~~~~-\sin\theta}}\\{{\sin\theta~~~~\cos\theta}}\\ \end{array}\right] [cosθ?????sinθsinθ????cosθ?]
也被稱作旋轉矩陣。然而我們所要的不僅僅是可以圍繞圖像左上角進行旋轉,而是可以圍繞任意點進行旋轉。那么我們可以將其轉化成繞原點的旋轉,其過程為:
- 首先將旋轉點移到原點
- 按照上面的旋轉矩陣進行旋轉得到新的坐標點
- 再將得到的旋轉點移回原來的位置
也就是說,在以任意點為旋轉中心時,除了要進行旋轉之外,還要進行平移操作。那么當點經過平移后得到P點時,如下圖所示:
那么我們就可以得到:
x=x0+txx=x_{0}+t_{x} x=x0?+tx?
y=y0+tyy=y_{0}+t_{y} y=y0?+ty?
寫成矩陣的形式為:
[xy1]=[10tx01ty001]?[x0y01]\left[\begin{array}{l l l}{{x}}\\{{y}}\\{1}\end{array}\right]=\left[\begin{array}{c}{{1~~~~0~~~~t_{x}}}\\{{0~~~~1~~~~t_{y}}}\\{{0~~~~0~~~~1}} \end{array}\right]*\left[\begin{array}{c}{{x_0}}\\{{y_0}}\\{1}\end{array}\right] ?xy1??=?1????0????tx?0????1????ty?0????0????1????x0?y0?1??
于是
[10tx01ty001]\left[\begin{array}{l l l}{{1~~~~0~~~~t_{x}}}\\{{0~~~~1~~~~t_{y}}}\\{{0~~~~0~~~~1}} \end{array}\right] ?1????0????tx?0????1????ty?0????0????1??
也被叫做平移矩陣,相反的,從P移到點時,其平移矩陣為:
[10?tx01?ty001]\left[\begin{array}{l l l}{1}&{0}&{-\,t_{x}}\\ {0}&{1}&{-\,t_{y}}\\ {0}&{0}&{1}\end{array}\right] ?100?010??tx??ty?1??
我們將原始的旋轉矩陣也擴展到3*3的形式:
[xy1]=[cos?θ?sin?θ0sin?θcos?θ0001]?[x0y01]\begin{array}{l l l}{{\left[\begin{array}{c}{{x}}\\{{y}}\\{1} \end{array}\right]=\left[\begin{array}{c c c}{{\cos\theta}}&{{-\sin\theta}}&{{0}}\\ {{\sin\theta}}&{{\cos\theta}}&{{0}}\\ {{0}}&{{0}}&{{1}}\end{array}\right]*\left[\begin{array}{c}{{x_{0}}}\\{{y_{0}}}\\{{1}}\end{array}\right]}}\end{array} ?xy1??=?cosθsinθ0??sinθcosθ0?001????x0?y0?1???
從平移和旋轉的矩陣可以看出,3x3矩陣的前2x2部分是和旋轉相關的,第三列與平移相關。有了上面的表達式之后,我們就可以得到二維空間中繞任意點旋轉的旋轉矩陣了,只需要將旋轉矩陣先左乘
[10tx01ty001]\left[\begin{array}{l l l}{{1~~~~0~~~~t_{x}}}\\{{0~~~~1~~~~t_{y}}}\\{{0~~~~0~~~~1}} \end{array}\right] ?1????0????tx?0????1????ty?0????0????1??
,再右乘
[10?tx01?ty001]\left[\begin{array}{l l l}{1}&{0}&{-\,t_{x}}\\ {0}&{1}&{-\,t_{y}}\\ {0}&{0}&{1}\end{array}\right] ?100?010??tx??ty?1??
即可得到最終的矩陣其結果為:( 知道就好!!!)
M=[cos?θ?sin?θ(1?cos?θ)tx+ty?sin?θsin?θcos?θ(1?cos?θ)ty+tx?sin?θ001]M=\left[\begin{array}{l l l}{{\cos\theta~~-\sin\theta~~(1-\cos\theta)t_{x}+t_{y}*\sin\theta}}\\{{\sin\theta~~~\cos\theta~~~~~(1-\cos\theta)t_{y}+t_{x}*\sin\theta}}\\{{0~~~~~~~~~~~~~0~~~~~~~~~~~~~1}} \end{array}\right] M=?cosθ???sinθ??(1?cosθ)tx?+ty??sinθsinθ???cosθ?????(1?cosθ)ty?+tx??sinθ0?????????????0?????????????1??
于是我們就可以根據這個矩陣計算出圖像中任意一點繞某點旋轉后的坐標了,這個矩陣學名叫做仿射變換矩陣,而仿射變換是一種二維坐標到二維坐標之間的線性變換,也就是只涉及一個平面內二維圖形的線性變換,圖像旋轉就是仿射變換的一種。
? 仿射變換(Affine Transformation)是一種線性變換,保持了點之間的相對距離不變。
2.2 仿射變換函數
cv2.warpAffine(img,M,dsize)
參數說明:
-
img:輸入圖像。
-
M:2x3的變換矩陣,類型為
np.float32
。 -
dsize:輸出圖像的尺寸,形式為
(width,height)
。
示例
- 旋轉
import cv2 as cv
import numpy as np# 利用仿射變換矩陣
if __name__ == '__main__':# 讀取圖片old = cv.imread('../images/1.jpg')shape = old.shape# 獲取旋轉的仿射矩陣M = cv.getRotationMatrix2D((shape[0]//2, shape[1]//2),-45, 1)# 旋轉new_rotation = cv.warpAffine(old, M, (shape[1],shape[0]))# 顯示對象cv.imshow('new_1', new_rotation)cv.waitKey(0)cv.destroyAllWindows()
- 平移
import cv2 as cv
import numpy as np# 利用仿射變換矩陣
if __name__ == '__main__':# 讀取圖片old = cv.imread('../images/1.jpg')shape = old.shape# 獲取旋轉的仿射矩陣tx = -100ty = -50M = np.float32([[1, 0, tx],[0, 1, ty]])# 平移new_pingyi = cv.warpAffine(old, M, (shape[1],shape[0]))# 顯示對象cv.imshow('new_2', new_pingyi)cv.waitKey(0)cv.destroyAllWindows()
- 縮放
import cv2 as cv
import numpy as np# 利用仿射變換矩陣
if __name__ == '__main__':# 讀取圖片old = cv.imread('../images/1.jpg')shape = old.shape# 獲取旋轉的仿射矩陣Sx = 0.5 Sy = 1.2M = np.float32([[ 1.5, 0, 0],[ 0, 0.5, 0]])# 縮放new_pingyi = cv.warpAffine(old, M, (shape[1],shape[0]))# 顯示對象cv.imshow('new_2', new_pingyi)cv.waitKey(0)cv.destroyAllWindows()
總結
今天我們學習了圖像顏色處理及圖像仿射變換,其中顏色處理重點掌握二值化的OTSU閾值法和自適應二值化,仿射變換用到了旋轉角和矩陣,總的來說知識與線性代數結合很緊密,需要有一定的數學基礎。
讓我們下期再見!
— opencv系列相關文章 —
opencv–day01–opencv基礎知識及基礎操作