opencv進(jìn)階一
人臉識(shí)別
在Opencv中人臉識(shí)別是基于Haar特征+Adaboost級(jí)聯(lián)分類器來實(shí)現(xiàn)人臉識(shí)別的!
要理解這節(jié)內(nèi)容,我們首先要明白什么是特征?
特征其實(shí)就是某個(gè)區(qū)域的像素點(diǎn)經(jīng)過運(yùn)算之后得到的結(jié)果! 例如haar特征其實(shí)就是用下圖列出的模板在圖像中滑動(dòng),計(jì)算白色區(qū)域覆蓋的像素之和減去黑色區(qū)域覆蓋的像素之和,運(yùn)算出來的結(jié)果就是haar特征值!
Haar特征一般和Adaboost分類器結(jié)合在一起進(jìn)行目標(biāo)識(shí)別!
這里需要運(yùn)動(dòng)機(jī)器學(xué)習(xí)的知識(shí)! 不過值得慶幸的是Opencv已經(jīng)為我們訓(xùn)練好了數(shù)據(jù),并且已經(jīng)提取出了人臉的特征,在opencv的源碼中有相應(yīng)的xml特征文件. 并且我們只需要調(diào)用opencv提供好的API即可快速完成人臉識(shí)別的功能!


核心api為:
# 加載已經(jīng)訓(xùn)練好的特征文件
faces_xml = cv.CascadeClassifier("assets/haarcascade_frontalface_default.xml")
# 根據(jù)特征文件去查找人臉
faces_xml.detectMultiScale(圖像, 縮放系數(shù), 至少檢驗(yàn)次數(shù))
實(shí)現(xiàn)步驟:
- 加載特征xml文件
- 加載圖片
- 灰度處理
- 判決
- 繪制出檢測出來的人臉

import cv2 as cv
# 第1步:加載xml文件
faces_xml = cv.CascadeClassifier("assets/haarcascade_frontalface_default.xml")
eyes_xml = cv.CascadeClassifier("assets/haarcascade_eye.xml")
# 第2步:加載圖片
img = cv.imread("img/lena.jpg", cv.IMREAD_COLOR)
# 第3步:將圖片轉(zhuǎn)成灰色圖片
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 第4步:使用api進(jìn)行人臉識(shí)別 參數(shù)2:縮放系數(shù) 參數(shù)3:至少要檢測幾次才算正確
faces = faces_xml.detectMultiScale(gray, 1.3, 5)
print("找到人臉的數(shù)量:",len(faces))
# 在人臉上繪制矩形
for (x,y,w,h) in faces:
# 在找到人臉上畫矩形
cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),5)
# 從灰色圖片中找到人臉
grayFace = gray[y:y+h,x:x+w]
colorFace = img[y:y+h,x:x+w]
# 在當(dāng)前人臉上找到眼睛的位置
eyes = eyes_xml.detectMultiScale(grayFace,1.3,5)
print("當(dāng)前人臉上眼睛數(shù)量:",len(eyes))
# 在眼睛上繪制矩形
for (e_x,e_y,e_w,e_h) in eyes:
cv.rectangle(colorFace,(e_x,e_y),(e_x+e_w,e_y+e_h),(0,0,255),3)
cv.imshow('result',img)
cv.waitKey(0)
cv.destroyAllWindows()
HSV顏色模型

HSV(Hue, Saturation, Value)是根據(jù)顏色的直觀特性由A. R. Smith在1978年創(chuàng)建的一種顏色空間, 也稱六角錐體模型(Hexcone Model)。
這個(gè)模型中顏色的參數(shù)分別是:色調(diào)(H),飽和度(S),明度(V)
色調(diào)H
用角度度量,取值范圍為0°~360°,從紅色開始按逆時(shí)針方向計(jì)算,紅色為0°,綠色為120°,藍(lán)色為240°。它們的補(bǔ)色是:黃色為60°,青色為180°,品紅為300°;
飽和度S
飽和度S表示顏色接近光譜色的程度。一種顏色,可以看成是某種光譜色與白色混合的結(jié)果。其中光譜色所占的比例愈大,顏色接近光譜色的程度就愈高,顏色的飽和度也就愈高。飽和度高,顏色則深而艷。光譜色的白光成分為0,飽和度達(dá)到最高。通常取值范圍為0%~100%,值越大,顏色越飽和。
明度V
明度表示顏色明亮的程度,對(duì)于光源色,明度值與發(fā)光體的光亮度有關(guān);對(duì)于物體色,此值和物體的透射比或反射比有關(guān)。通常取值范圍為0%(黑)到100%(白)。
結(jié)論:
當(dāng)S=1 V=1時(shí),H所代表的任何顏色被稱為純色;
當(dāng)S=0時(shí),即飽和度為0,顏色最淺,最淺被描述為灰色(灰色也有亮度,黑色和白色也屬于灰色),灰色的亮度由V決定,此時(shí)H無意義;
當(dāng)V=0時(shí),顏色最暗,最暗被描述為黑色,因此此時(shí)H(無論什么顏色最暗都為黑色)和S(無論什么深淺的顏色最暗都為黑色)均無意義。
注意: 在opencv中,H、S、V值范圍分別是[0,180],[0,255],[0,255],而非[0,360],[0,1],[0,1];
這里我們列出部分hsv空間的顏色值, 表中將部分紫色歸為紅色
[圖片上傳失敗...(image-4f70f7-1563843266225)]
.jpg)
判斷當(dāng)前是白天還是晚上

實(shí)現(xiàn)步驟
- 將圖片從BGR顏色空間,轉(zhuǎn)變成HSV顏色空間
- 獲取圖片的寬高信息
- 統(tǒng)計(jì)每個(gè)顏色點(diǎn)的亮度
- 計(jì)算整張圖片的亮度平均值
注意,這僅僅只能做一個(gè)比較粗糙的判定,按照我們?nèi)说恼K季S,在傍晚臨界點(diǎn)我們也無法判定當(dāng)前是屬于晚上還是白天!
import cv2 as cv
import numpy as np
def average_brightness(img):
"""封裝一個(gè)計(jì)算圖片平均亮度的函數(shù)"""
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]
hsv_img = cv.cvtColor(img, cv.COLOR_BGR2HSV)
# 提取出v通道信息
v_day = cv.split(hsv_img)[2]
# 計(jì)算亮度之和
result = np.sum(v_day)
# 返回亮度的平均值
return result/(height*width)
# 計(jì)算白天的亮度平均值
day_img = cv.imread("assets/day.jpg", cv.IMREAD_COLOR)
brightness1 = average_brightness(day_img)
print("day brightness1:",brightness1);
# 計(jì)算晚上的亮度平均值
night_img = cv.imread("assets/night.jpg", cv.IMREAD_COLOR)
brightness2 = average_brightness(night_img)
print("night brightness2:",brightness2)
cv.waitKey(0)
cv.destroyAllWindows()
顏色過濾
在一張圖片中,如果某個(gè)物體的顏色為純色,那么我們就可以使用顏色過濾inRange的方式很方便的來提取這個(gè)物體.
下面我們有一張網(wǎng)球的圖片,并且網(wǎng)球的顏色為一定范圍內(nèi)的綠色,在這張圖片中我們找不到其它顏色也為綠色的圖片,所以我們可以考慮使用綠色來提取它!
圖片的顏色空間默認(rèn)為BGR顏色空間,如果我們想找到提取純綠色的話,我們可能需要寫(0,255,0)這樣的內(nèi)容,假設(shè)我們想表示一定范圍的綠色就會(huì)很麻煩!
所以我們考慮將它轉(zhuǎn)成HSV顏色空間,綠色的色調(diào)H的范圍我們很容易知道,剩下的就是框定顏色的飽和度H和亮度V就可以啦!

實(shí)現(xiàn)步驟:
- 讀取一張彩色圖片
- 將RGB轉(zhuǎn)成HSV圖片
- 定義顏色的范圍,下限位(30,120,130),上限為(60,255,255)
- 根據(jù)顏色的范圍創(chuàng)建一個(gè)mask
import cv2 as cv
# 讀取圖片
rgb_img = cv.imread("assets/tenis1.jpg", cv.IMREAD_COLOR)
cv.imshow("rgb_img",rgb_img)
# 將BGR顏色空間轉(zhuǎn)成HSV空間
hsv_img = cv.cvtColor(rgb_img, cv.COLOR_BGR2HSV)
# 定義范圍 網(wǎng)球顏色范圍
lower_color = (30,120,130)
upper_color = (60,255,255)
# 查找顏色
mask_img = cv.inRange(hsv_img, lower_color, upper_color)
# 在顏色范圍內(nèi)的內(nèi)容是白色, 其它為黑色
cv.imshow("mask_img",mask_img)
cv.waitKey(0)
cv.destroyAllWindows()
替換背景案例

實(shí)現(xiàn)步驟
- 從綠幕圖片中過濾出綠幕
- 將獅子從綠幕中摳出來
- 在itheima圖片上摳出獅子的位置
- 將獅子和黑馬圖片進(jìn)行相加得到最終的圖片
import cv2 as cv
# 1.讀取綠幕圖片
green_img = cv.imread("assets/lion.jpg", cv.IMREAD_COLOR)
hsv_img = cv.cvtColor(green_img,cv.COLOR_BGR2HSV)
# 2. 定義綠幕的顏色范圍
lower_green = (35,43,60)
upper_green = (77,255,255)
# 3. 使用inrange找出所有的背景區(qū)域
mask_green = cv.inRange(hsv_img, lower_green, upper_green)
# 復(fù)制獅子綠幕圖片
mask_img = green_img.copy()
# 將綠幕圖片,對(duì)應(yīng)蒙板圖片中所有不為0的地方全部改成0
mask_img[mask_green!=0]=(0,0,0)
cv.imshow("dst",mask_img)
# itheima圖片 對(duì)應(yīng)蒙板圖片為0的地方全都改成0,摳出獅子要存放的位置
itheima_img = cv.imread("assets/itheima.jpg", cv.IMREAD_COLOR)
itheima_img[mask_green==0]=(0,0,0)
cv.imshow("itheima",itheima_img)
# 將摳出來的獅子與處理過的itheima圖片加載一起
result = itheima_img+mask_img
cv.imshow("result",result)
cv.waitKey(0)
cv.destroyAllWindows()
圖像的二值化
圖像二值化( Image Binarization)就是將圖像上的像素點(diǎn)的灰度值設(shè)置為0或255,也就是將整個(gè)圖像呈現(xiàn)出明顯的黑白效果的過程。
在數(shù)字圖像處理中,二值圖像占有非常重要的地位,圖像的二值化使圖像中數(shù)據(jù)量大為減少,從而能凸顯出目標(biāo)的輪廓。
cv.threshold(img,閾值,最大值,類型)
[圖片上傳失敗...(image-a31052-1563843266226)]
| THRESH_BINARY | 高于閾值改為255,低于閾值改為0 |
|---|---|
| THRESH_BINARY_INV | 高于閾值改為0,低于閾值改為255 |
| THRESH_TRUNC | 截?cái)?高于閾值改為閾值,最大值失效 |
| THRESH_TOZERO | 高于閾值不改變,低于閾值改為0 |
| THRESH_TOZERO_INV | 高于閾值該為0,低于閾值不改變 |
簡單閾值
import cv2 as cv
# 讀取圖像
img = cv.imread("assets/thresh1.jpg",cv.IMREAD_GRAYSCALE)
# 顯示圖片
cv.imshow("gray",img)
# 獲取圖片信息
imgInfo = img.shape
height = imgInfo[0]
width = imgInfo[1]
# 定義閾值
thresh = 55
for row in range(height):
for col in range(width):
# 獲取當(dāng)前灰度值
grayValue = img[row,col]
if grayValue>thresh:
img[row,col]=255
else:
img[row,col]=0
# 直接調(diào)用api處理 返回值1:使用的閾值, 返回值2:處理之后的圖像
# ret,thresh_img = cv.threshold(img, thresh, 255, cv.THRESH_BINARY)
# 顯示修改之后的圖片
cv.imshow("thresh",img);
cv.waitKey(0)
cv.destroyAllWindows()
自適應(yīng)閾值
我們使用一個(gè)全局值作為閾值。但是在所有情況下這可能都不太好,例如,如果圖像在不同區(qū)域具有不同的照明條件。在這種情況下,自適應(yīng)閾值閾值可以幫助。這里,算法基于其周圍的小區(qū)域確定像素的閾值。因此,我們?yōu)橥粓D像的不同區(qū)域獲得不同的閾值,這為具有不同照明的圖像提供了更好的結(jié)果。
除上述參數(shù)外,方法cv.adaptiveThreshold還有三個(gè)輸入?yún)?shù):
該adaptiveMethod決定閾值是如何計(jì)算的:
- cv.ADAPTIVE_THRESH_MEAN_C:該閾值是該附近區(qū)域減去恒定的平均?。
- cv.ADAPTIVE_THRESH_GAUSSIAN_C:閾值是鄰域值減去常數(shù)C的高斯加權(quán)和。
該BLOCKSIZE確定附近區(qū)域的大小和?是從平均值或附近的像素的加權(quán)和中減去一個(gè)常數(shù)。
import cv2 as cv
# 讀取圖像
img = cv.imread("assets/thresh1.jpg",cv.IMREAD_GRAYSCALE)
# 顯示圖片
cv.imshow("gray",img)
# 獲取圖片信息
imgInfo = img.shape
# 直接調(diào)用api處理 參數(shù)1:圖像數(shù)據(jù) 參數(shù)2:最大值 參數(shù)3:計(jì)算閾值的方法, 參數(shù)4:閾值類型 參數(shù)5:處理塊大小 參數(shù)6:算法需要的常量C
thresh_img = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY,11,5)
# 顯示修改之后的圖片
cv.imshow("thresh",thresh_img);
cv.waitKey(0)
cv.destroyAllWindows()
THRESH_OTSU
采用日本人大津提出的算法,又稱作最大類間方差法,被認(rèn)為是圖像分割中閾值選取的最佳算法,采用這種算法的好處是執(zhí)行效率高!
<img src="./img2/otsu.jpg" width="500" />
import cv2 as cv
# 讀取圖像
img = cv.imread("assets/otsu_test.png",cv.IMREAD_GRAYSCALE)
cv.imshow("src",img)
ret,thresh_img = cv.threshold(img, 225, 255, cv.THRESH_BINARY_INV)
cv.imshow("normal", thresh_img);
gaussian_img = cv.GaussianBlur(img,(5,5),0)
cv.imshow("g",gaussian_img)
ret,thresh_img = cv.threshold(gaussian_img, 0, 255, cv.THRESH_BINARY|cv.THRESH_OTSU)
cv.imshow("otsu", thresh_img);
print("閾值:",ret)
cv.waitKey(0)
cv.destroyAllWindows()
圖像的噪聲
如果我們把圖像看作信號(hào),那么噪聲就是干擾信號(hào)。我們?cè)诓杉瘓D像時(shí)可能因?yàn)楦鞣N各樣的干擾而引入圖像噪聲。在計(jì)算機(jī)中,圖像就是一個(gè)矩陣, 給原始圖像增加噪聲, 我們只需要讓像素點(diǎn)加上一定灰度即可.
f(x, y) = I(x, y) + noise
常見的噪聲有椒鹽噪聲(salt and pepper noise),為什么叫椒鹽噪聲?因?yàn)閳D像的像素點(diǎn)由于噪聲影響隨機(jī)變成了黑點(diǎn)(dark spot)或白點(diǎn)(white spot)。這里的“椒”不是我們常見的紅辣椒或青辣椒,而是外國的“胡椒”(香料的一種)。我們知道,胡椒是黑色的,鹽是白色的,所以才取了這么個(gè)形象的名字.
接下來我們來生成10%的椒噪聲和鹽噪聲:
# 創(chuàng)建和原圖同大小隨機(jī)矩陣 胡椒噪聲
pepper_noise = np.random.randint(0,256,(height,width))
# 創(chuàng)建和原圖同大小隨機(jī)矩陣 鹽噪聲
salt_noise = np.random.randint(0,256,(height,width))
# 定義10%的噪聲 256×10%=25.6
ratio = 0.1
# 若值小于25.6 則置為-255,否則為0
pepper_noise = np.where(pepper_noise < ratio*256,-255,0)
# 若值大于25.6 則置為255,否則為0
salt_noise = np.where(salt_noise < ratio*256,255,0)
我們還要注意,opencv的圖像矩陣類型是uint8,低于0和高于255的值并不截?cái)啵鞘褂昧四2僮鳌<?00+60=260 % 256 = 4。所以我們需要先將原始圖像矩陣和噪聲圖像矩陣都轉(zhuǎn)成浮點(diǎn)數(shù)類型進(jìn)行相加操作,然后再轉(zhuǎn)回來。
# 將uint8類型轉(zhuǎn)成浮點(diǎn)類型
img.astype("float")
pepper_noise.astype("float")
salt_noise.astype("float")
# 將 胡椒噪聲 添加到原圖中
dst_img = img + pepper_noise
# 校驗(yàn)越界問題
dst_img = np.where(dst_img > 255,255,dst_img)
dst_img = np.where(dst_img < 0,0,dst_img)
cv.imshow("pepper img",dst_img.astype("uint8"))
# 將 鹽噪聲 添加到原圖中
dst_img = img + salt_noise
# 校驗(yàn)越界問題
dst_img = np.where(dst_img > 255,255,dst_img)
dst_img = np.where(dst_img < 0,0,dst_img)
cv.imshow("salt img",dst_img.astype("uint8"))
# 將 椒鹽噪聲 添加到原圖中
dst_img = img + pepper_noise + salt_noise
# 校驗(yàn)越界問題
dst_img = np.where(dst_img > 255,255,dst_img)
dst_img = np.where(dst_img < 0,0,dst_img)
cv.imshow("pepper salt img",dst_img.astype("uint8"))