一、圖像色彩空間轉換
(一)顏色加法
? ? ? ? 1、直接相加
????????1、直接相加
????????2、調用cv.add()函數進行飽和操作????????
在OpenCV中進行顏色的加法,我們說圖像即數組,所以從數據類型來說我們可以直接用numpy的知識來進行直接相加,但是存在溢出錯誤。
因為如果直接numpy進行直接相加,會進行取模運算,對256取模,相加后超出的值會又從0開始向上,例如250+10最后的值是4,導致色彩失真
#如果直接numpy進行直接相加,會進行取模運算,對256取模,相加后超出的值會又從0開始向上,例如250+10最后的值是4 dst2=pig+cao #舉例子 x=np.uint8([[250]]) y=np.uint8([[10]]) print(x+y) print(cv.add(x,y))
?正常進行圖像顏色加法時,調用庫中的API:飽和操作cv.add(img1,img2)? ? ? ? 類型是np.uint8 0~255 例如250+10最后到255就飽和不加了
#飽和操作cv.add(img1,img2) np.uint8 0~255 例如250+10最后只能到255到飽和 dst1=cv.add(pig,cao)
? ? ? ? 2、顏色加權加法:cv.addWeighted(img1,α,img2,β,,γ)
? ? ? ?α、β分別是img1和img2的權重參數
?????????γ是亮度調整值,
????????????????γ>0時,整體亮度增加
????????????????γ<0時,整體亮度降低
????????????????γ=0時,亮度不變
#顏色加權加法 cv.addWeighted(img1,α,img2,β,,γ) 其中γ是亮度調整值,γ>0時,整體亮度增加,γ<0時,整體亮度降低,γ=0時,亮度不變 dst3 =cv.addWeighted(pig,0.7,cao,0.3,0)
(二)色彩空間轉換
圖像的顏色表示有很多方式,除了RGB外,還有HCV,Gray(灰度)等。
????????HSV顏色空間使用色調(Hue)、飽和度(Saturation)和亮度(Value)三個參數來表示顏色,色調H表示顏色的種類,如紅色、綠色、藍色等;飽和度表示顏色的純度或強度,如紅色越純,飽和度就越高;亮度表示顏色的明暗程度,如黑色比白色亮度低
H: 0— 180
S: 0— 255
V: 0— 255
? ? ? ? Gray灰度圖像在進行二值化處理時需要使用,也經常需要對彩色圖像進行轉換
cv2.cvtColor:是OpenCV中的一個函數,用于圖像顏色空間的轉換。可以將一個圖像從一個顏色空間轉換為另一個顏色空間,比如從RGB到灰度圖,或者從RGB到HSV的轉換等
語法:cv2.cvtColor(img,code)
? ? ? ? img:輸入圖像
? ? ? ? code:指定轉換的類型,比如
cv.COLOR_RGB2GRAY
表示從rgb到灰度圖像的轉換? ? ? ? cv.COLOR_RGB2HSV表示從GGB圖像轉換到HSV圖像
import cv2 as cv #讀取圖像 img = cv.imread("../images/1.jpg") cv.imshow("img",img)#顏色轉換cv.cvtcolor(img,code) (得到的是一個新的圖像數組,所以是有返回值的) #轉灰度 gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY) cv.imshow("gray",gray) #轉HSV hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV) cv.imshow("hsv",hsv) #轉RGB rgb = cv.cvtColor(img,cv.COLOR_BGR2RGB) cv.imshow("rgb",rgb)cv.waitKey(0) cv.destroyAllWindows()
二、灰度實驗
????????灰度圖與彩色圖最大的不同就是:彩色圖是由R、G、B三個通道組成,而灰度圖只有一個通道,也稱為單通道圖像,所以彩色圖轉成灰度圖的過程本質上就是將R、G、B三通道合并成一個通道的過程
(一)最大值法
對于彩色圖像的每個像素,它會從R、G、B三個通道的值中選出最大的一個,并將其作為灰度圖像中對應位置的像素值
創建的思路:
- 首先需要讀取一張圖片
- 然后創建一個和圖片一樣大小的全0的圖像用來存儲圖片中的像素值
- 通過依次遍歷行列,然后取到每個像素對應三通道的值,用max小函數取出來賦值給當前像素即可
代碼實現:
import cv2 as cv import numpy as npimg = cv.imread('../images/pig.png')#創一個一樣大小的全0圖像 img1 = np.zeros((img.shape[0],img.shape[1]),dtype=np.uint8)#循環遍歷每一行,img[0,0,0] for i in range(img.shape[0]): # 遍歷行for j in range(img.shape[1]): # 遍歷列img1[i,j]=max(img[i,j,0],img[i,j,1],img[i,j,2]) # 取最大值cv.imshow('img1',img1) cv.waitKey(0) cv.destroyAllWindows()
(二)平均值法
對于彩色圖像的每個像素,它會將R、G、B三個通道的像素值全部加起來,然后再除以三,得到的平均值就是灰度圖像中對應位置的像素值
方法:
- 讀取一張圖片
- 創建一樣大的全0的圖像存放新的像素點
- 依次遍歷行列,然后取到每個像素對應三通道的值,加起來整除3(注意,此處取到的三通道的值都是uint8(無符號8為整數,數值范圍在0~255)直接這樣相加可能存中值溢出,所以我們需要先將讀取到的圖片轉為int類型后再進行相加)
- 最后再把計算得到的int類型的通道值轉為uint8
代碼實現:
import cv2 as cv import numpy as npimg = cv.imread('../images/pig.png')#創一個一樣大小的全0圖像 img1 = np.zeros((img.shape[0],img.shape[1]),dtype=np.uint8)#循環遍歷每一行,img[0,0,0] for i in range(img.shape[0]): # 遍歷行for j in range(img.shape[1]): # 遍歷列#為什么要先轉換為int類型呢?因為圖像數組通常都是uint8(無符號 8 位整數)取值范圍是 0 - 255,# 三個數直接相加可能會溢出,所以需要轉為int有符號的整數避免溢出,再用np.uint8給轉回來#//3 整除3是因為我們的像素值的通道都是整數img1[i,j]=np.uint8((int(img[i,j,0])+int(img[i,j,1])+int(img[i,j,2]))//3)cv.imshow('img1',img1) cv.waitKey(0) cv.destroyAllWindows()
(三 )加權平均值法
對于彩色圖像的每個像素,它會按照一定的權重去乘以每個通道的像素值,并將其相加,得到最后的值就是灰度圖像中對應位置的像素值
權重可以自行設定,所使用的權重之和應該等于1,方法與平均值法相似只是多了
- 讀取一張圖片
- 創建一樣大的全0的圖像存放新的像素點
- 依次遍歷行列,然后取到每個像素對應三通道的值,加起來整除3(注意,此處和權重相乘后通常會出現小數,但像素值又只能為整數,系統會自動向下或向上取整,為了讓結果更準確,所以會用round()函數進行四舍五入)
- 最后再把計算得到的int類型的通道值轉為uint8
代碼實現:
import cv2 as cv import numpy as npimg = cv.imread('../images/pig.png')#創一個一樣大小的全0圖像 img1 = np.zeros((img.shape[0],img.shape[1]),dtype=np.uint8)# 定義權重 wb,wg,wr = 0.114,0.587,0.299#循環遍歷每一行,img[0,0,0] for i in range(img.shape[0]): # 遍歷行for j in range(img.shape[1]): # 遍歷列#為什么要先轉換為int類型呢?因為圖像數組通常都是uint8(無符號 8 位整數)取值范圍是 0 - 255,# 三個數直接相加可能會溢出,所以需要轉為int有符號的整數避免溢出,再用np.uint8給轉回來#//3 整除3是因為我們的像素值的通道都是整數#round()四舍五入img1[i,j]=np.uint8(round(wb*img[i,j,0]+wg*img[i,j,1]+wr*img[i,j,2])//3)cv.imshow('img1',img1) cv.waitKey(0) cv.destroyAllWindows()
三、圖像二值化處理
二值圖像的二維矩陣僅由0、1兩個值構成,“0”代表黑色,“1”代白色(也就是255的值),其操作的圖像是必須是灰度圖
(一)全局閾值法
語法:_,binary = cv2.threshold(img,thresh,maxval,type)
img
:輸入圖像,要進行二值化處理的灰度圖。
thresh
:設定的閾值。當像素值大于(或小于,取決于閾值類型)thresh
時,該像素被賦予的值。包括:
type
:閾值處理的類型,
閾值法:cv.threshold(img, thresh, max_value, cv.THRESH_BINARY)
反閾值法:cv.threshold(img, thresh, max_value, cv.THRESH_BINARY_INV)
截斷閾值法:cv.threshold(img, thresh, max_value, cv.THRESH_TRUNC)
低閾值零處理法:cv.threshold(img, thresh, max_value, cv.THRESH_TOZERO)
超閾值零處理法cv.threshold(img, thresh, max_value, cv.THRESH_TOZERO_INV)
OTSU閾值法:cv.threshold(img, thresh, max_value, cv.THRESH_OSTU)或cv.threshold(img, thresh, max_value, cv.THRESH_OSTU_INV)
返回值:
第一個值(通常用下劃線表示):計算出的閾值,若使用自適應閾值法,會根據算法自動計算出這個值。
第二個值(binary):二值化后的圖像矩陣。與輸入圖像尺寸相同。
? ? ? ? 閾值法:通過設置一個閾值,將灰度圖中的每一個像素值與該閾值進行比較,小于等于閾值的像素就被設置為0(通常代表背景),大于閾值的像素就被設置為maxval(通常代表前景)
? ? ? ? 反閾值法:與閾值法相反。反閾值法是當灰度圖的像素值大于閾值時,該像素值將會變成0(黑),當灰度圖的像素值小于等于閾值時,該像素值將會變成maxval。
? ? ? ? 截斷閾值法:將灰度圖中的所有像素與閾值進行比較,像素值大于閾值的部分將會被修改為閾值,小于等于閾值的部分不變
????????低閾值零處理:字面意思,就是像素值小于等于閾值的部分被置為0,大于閾值的部分不變
????????超閾值零處理:將灰度圖中的每個像素與閾值進行比較,像素值大于閾值的部分置為0(也就是黑色),像素值小于等于閾值的部分不變。
? ? ? ? OSTU閾值法:OSTU閾值法?并不是一個有效的閾值類型,THRESH_OTSU
本身并不是一個獨立的閾值化方法,通常與 THRESH_BINARY
或 THRESH_BINARY_INV
結合使用 默認情況下它會與 `THRESH_BINARY` 結合使用。也就是說,當你僅指定了 `cv2.THRESH_OTSU`,實際上等同于同時指定了 `cv2.THRESH_BINARY + cv2.THRESH_OTSU`
????????OSTU 閾值法是一種基于圖像灰度直方圖統計特性的閾值確定方法,目的是將圖像分為前景和背景兩部分,使得分割后的兩類之間的差異最大,也就是讓類間方差最大,從而找到一個最佳的閾值來實現二值化
? ? ? ? 遍歷所有可能的閾值,將設當前閾值為t,遍歷t到最大像素值減1,分別計算每個t對應的類間方差,使得類間方差值最大的那個t值就是OSTU算法所確定的最佳閾值,將圖像中灰度值小于等于這個閾值的像素設置為?
0
(黑色,代表背景),大于這個閾值的像素設置為?255
(白色,代表前景),就完成了基于 OSTU 閾值法的二值化處理。
代碼實現:
import cv2 as cvflower = cv.imread('../images/flower.png')
# 讀取灰色圖像
# flower1 = cv.imread('../images/flower.png',0)#h灰度化處理
flower1 = cv.cvtColor(flower,cv.COLOR_BGR2GRAY)
flower1 = cv.resize(flower1,(360,360))
cv.imshow('flower1',flower1)#二值化,閾值法
_,binary = cv.threshold(flower1,127,255,cv.THRESH_BINARY)
cv.imshow('binary',binary)# 反閾值法,為什么前面用下劃線,因為此處返回的值是thresh,本身就是我們傳的參數,所以沒必要接收,就可以用下劃線占位
_,binary_inv = cv.threshold(flower1,127,255,cv.THRESH_BINARY_INV)
cv.imshow('binary_inv',binary_inv)#截斷閾值法
_,binary_trunc= cv.threshold(flower1,180,255,cv.THRESH_TRUNC)
cv.imshow('binary_trunc',binary_trunc)#低閾值0處理
_,binary_zero =cv.threshold(flower1,127,255,cv.THRESH_TOZERO)
cv.imshow('binary_zero',binary_zero)#超閾值0處理
_,binary_zero_inv =cv.threshold(flower1,127,255,cv.THRESH_TOZERO_INV)
cv.imshow('binary_zero_inv',binary_zero_inv)#OTSU閾值法 默認結合了cv.THRESH_BINARY THRESH_BINARY=THRESH_BINARY+THRESH_OTSU
thresh1,otsu=cv.threshold(flower1,200,255,cv.THRESH_OTSU)
cv.imshow('otsu',otsu)
print(thresh1)
# otsu閾值結合反閾值法
thresh2,otsu_inv = cv.threshold(flower1,200,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
cv.imshow('otsu_inv',otsu_inv)
print(thresh2)#自適應二值化,小區域計算
#取均值
auto_mean =cv.adaptiveThreshold(flower1,255,cv.ADAPTIVE_THRESH_MEAN_C,cv.THRESH_BINARY,11,2)
cv.imshow('auto_mean',auto_mean)
#高斯加權法
auto_gauss = cv.adaptiveThreshold(flower1,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,11,2)
cv.imshow('auto_gauss',auto_gauss)#顯示效果
cv.waitKey(0)
cv.destroyAllWindows()
四、圖像翻轉和仿射變換
1、圖像的鏡像翻轉:
在OpenCV中,圖片的鏡像旋轉是以圖像的中心為原點進行鏡像翻轉的
- cv2.flip(img,flipcode)**
- 參數
? - img: 要翻轉的圖像
- flipcode: 指定翻轉類型的標志
- flipcode=0: 垂直翻轉,圖片像素點沿x軸翻轉
- flipcode>0: 水平翻轉,圖片像素點沿y軸翻轉
- flipcode<0: 水平垂直翻轉,水平翻轉和垂直翻轉的結合import cv2 as cv face= cv.imread('../images/face.png') cv.imshow('face',face) #翻轉鏡像旋轉,以圖像的中心為原點cv.flip(img,flipcode) #flipcode=0:垂直翻轉 沿x軸上下翻轉 flip_0 = cv.flip(face,0) cv.imshow('flip_0',flip_0)#flipcode>0:沿y軸水平翻轉(左右翻轉) flip_1 = cv.flip(face,1) cv.imshow('flip_1',flip_1)#flipcode<0:沿xy軸同時翻轉(垂直水平都翻轉) flip_2 = cv.flip(face,-1) cv.imshow('flip_2',flip_2)cv.waitKey(0) cv.destroyAllWindows()
2、仿射變換:
仿射變換(Affine Transformation)是一種線性變換,保持了點之間的相對距離不變。
仿射變換的基本性質:
????????保持直線
保持平行
比例不變性
不保持角度和長度常見的仿射變換類型:旋轉,平移,縮放,剪切
對于仿射變換,最重要的一步是獲得變換矩陣,通過變換矩陣是的目標點變換到預定位置
原理:
????????對于 2D 圖像,任意像素點的坐標可以表示為齊次坐標形式?
(x, y, 1)
(增加維度便于統一處理平移)。仿射變換矩陣?M
?是一個?3×3
?的矩陣實際使用中常簡化為?2×3
,通過補全最后一行?[0,0,1]
?變為齊次矩陣)
????????原像素點?
(x, y)
?經過矩陣?M
?變換后,得到新像素點?(x', y')
,計算公式為:
展開后
此處的思維是我們再思考的時候進行的正向思考,通過變換矩陣得到變換后的像素點對應的值。
????????實際在圖像變換中,,我們通常需要根據目標圖像的像素點?
(x', y')
?反推它在原圖像中的對應像素點?(x, y)
,這一過程稱為 “逆變換????????當我們對圖像進行旋轉得到新圖像后,新圖像中某個像素?
(x', y')
?的顏色,實際上來自原圖像中某個像素?(x, y)
?的顏色。為了準確填充新圖像的像素值,必須找到?(x', y')
?對應的?(x, y)
。????????此時,就需要通過仿射矩陣?
M
?的逆矩陣?M^{-1}
?來實現反推,通過逆矩陣來求解原像素點變換過程:
已知目標像素?
(x', y')
,通過逆矩陣?M^{-1}
?求解原像素?(x, y)
展開后:其中
(a', b', c', d', e', f')
?是逆矩陣的元素
在 OpenCV 的?warpAffine
?等變換函數中,內部會自動通過逆矩陣計算:
- 遍歷目標圖像的每個像素?
(x', y')
; - 用逆矩陣?
M^{-1}
?計算其在原圖像中的對應點?(x, y)
; - 取原圖像?
(x, y)
?的像素值,賦給目標圖像的?(x', y')
仿射變換函數:
cv2.warpAffine(img,M,dsize)
img:輸入圖像。
M:2x3的變換矩陣,類型為np.float32。
-dsize:輸出圖像的尺寸,形式為(width,height)
在實際運用時,最主要的是得到變換矩陣
? ????????圖像旋轉:
首先利用cv2.getRotationMatrix2D()函數得到旋轉矩陣:
????????cv2.getRotationMatrix2D(center,angle,scale)
- center:旋轉中心點的坐標,格式為`(x,y)`。
- angle:旋轉角度,單位為度,正值表示逆時針旋轉負值表示順時針旋轉。
- scale:縮放比例,若設為1,則不縮放。
- 返回值:M,2x3的旋轉矩陣。
import cv2 as cv #讀圖 cat = cv.imread("../images/1.jpg")#獲取旋轉矩陣 cv2.getRotationMatrix20(center,angle,scale) M = cv.getRotationMatrix2D((200,300),30,1) print(M)cat = cv.warpAffine(cat,M,(640,580)) cv.imshow("cat",cat)cv.waitKey(0) cv.destroyAllWindows()
? ? ? ? 圖像平移:
移操作可以將圖像中的每個點沿著某個方向移動一定的距離
假設我們有一個點 P(x,y),希望將其沿x軸方向平移t_x個單位,沿y軸方向平移t_y個單位到新的位置P′(x′,y′),那么平移公式如下:
? ? ? ? ? ? ? ? ? x′=x+tx
? ? ? ? ? ? ? ? ? y′=y+ty
在矩陣形式下,該變換可以表示為:
import cv2 as cv import numpy as np #讀圖 cat = cv.imread("../images/1.jpg")#定義平移量 tx=80 ty=120#定義平移矩陣 M = np.float32([[1,0,tx],[0,1,ty]])#仿射變換的API cat = cv.warpAffine(cat,M,(1000,1000)) cv.imshow("cat",cat)cv.waitKey(0) cv.destroyAllWindows()
????????圖像縮放
假設要把圖像的寬高分別縮放為0.5和0.8,那么對應的縮放因子sx=0.5,sy=0.8。
點$P(x,y)$對應到新的位置P'(x',y'),縮放公式為:
? ?x′=s_x*x
? ? y′=s_y*y
? 在矩陣形式下,該變換可以表示為
import cv2 as cv import numpy as np #讀圖 cat = cv.imread("../images/1.jpg")#定義縮放因子 sx=0.8 sy=0.5#定義縮放矩陣,最后一列是平移量 M = np.float32([[sx,0,0],[0,sy,0]])#仿射變換的API cat = cv.warpAffine(cat,M,(1000,1000)) cv.imshow("cat",cat)cv.waitKey(0) cv.destroyAllWindows()