【CV】OpenCV 入門(mén)之旅
OpenCV 是一個(gè)強(qiáng)大的圖片處理工具,尤其是隨著人工智能、圖片識(shí)別等行業(yè)的興起,這個(gè)第三方庫(kù)也越來(lái)越受到重視,今天我們就一起來(lái)開(kāi)啟 OpenCV 之旅

計(jì)算機(jī)視覺(jué)
我們先來(lái)看下到底什么是計(jì)算機(jī)視覺(jué)
其實(shí)這個(gè)是一個(gè)比較大的問(wèn)題了,我們先來(lái)簡(jiǎn)化下問(wèn)題,思考如下場(chǎng)景
相信很多朋友都會(huì)使用微博來(lái)曬出自己的旅游照片,當(dāng)然照片中會(huì)包含自己和家人朋友等等。那么該怎么快速的識(shí)別出照片中不同的人并標(biāo)注出來(lái)呢,這個(gè)時(shí)候就可以用到計(jì)算機(jī)視覺(jué)的知識(shí)了
計(jì)算機(jī)視覺(jué)是一個(gè)跨學(xué)科領(lǐng)域,涉及如何使計(jì)算機(jī)從數(shù)字圖像或視頻中獲得高級(jí)別的理解,并使得計(jì)算機(jī)能夠識(shí)別諸如人臉、燈柱甚至雕像之類的物體
計(jì)算機(jī)如何讀取圖像
比如說(shuō)下面這張圖片,計(jì)算機(jī)是怎么展示的呢

計(jì)算機(jī)會(huì)將任何圖像讀取為 0 到 255 之間的范圍值
對(duì)于任何彩色圖像,都有 3 個(gè)主要通道——紅色、綠色和藍(lán)色,它的工作原理非常簡(jiǎn)單:
為每種原色形成一個(gè)矩陣,然后這些矩陣組合起來(lái)為各個(gè) R、G、B 顏色提供像素值,然后矩陣的每個(gè)元素提供與像素亮度強(qiáng)度有關(guān)的數(shù)據(jù)
文字有些抽象,我們來(lái)看下面這張圖片

如圖所示,此處圖像的大小可以計(jì)算為 B x A x 3
注意:對(duì)于黑白圖像,只有一個(gè)通道
了解了前置基礎(chǔ)知識(shí)后,接下來(lái)讓我們看看 OpenCV 到底是什么
OpenCV 是什么
OpenCV 是一個(gè) Python 庫(kù),用于解決計(jì)算機(jī)視覺(jué)問(wèn)題。OpenCV 最初由 Intel 于 1999 年開(kāi)發(fā),后來(lái)得到 Willow Garage 的支持,從而發(fā)展的更加迅速
OpenCV 支持多種編程語(yǔ)言,如 C++、Python、Java 等,同時(shí)也支持多種平臺(tái),包括 Windows、Linux 和 MacOS
OpenCV Python 只不過(guò)是與 Python 一起使用的原始 C++ 庫(kù)的包裝類,所有 OpenCV 數(shù)組結(jié)構(gòu)都會(huì)被轉(zhuǎn)換為 NumPy 數(shù)組
這使得 OpenCV 更容易與其他使用 NumPy 的庫(kù)集成,例如,SciPy 和 Matplotlib 等
接下來(lái)讓我們看看使用 OpenCV 執(zhí)行的一些基本操作
OpenCV 基本操作
載入圖像
Import?cv2
#?彩色圖片
Img?=?cv2.imread?(Penguins.jpg,1)
#?黑白圖片
Img_1?=?cv2.imread?(Penguins.jpg,0)
如上一段代碼所示,首先我們需要導(dǎo)入 OpenCV 模塊
然后我們可以使用 imread 模塊讀取圖像,參數(shù)中的1表示是彩色圖像。如果該參數(shù)為 0 而不是 1,則表示導(dǎo)入的圖像是黑白圖像
圖像形狀/分別率
我們可以利用 shape 子函數(shù)來(lái)打印出圖像的形狀
Import?cv2
Img?=?cv2.imread?(Penguins.jpg,0)
Print(img.shape)
圖像的形狀是指 NumPy 數(shù)組的形狀,從執(zhí)行代碼可以看出,矩陣由 768 行和 1024 列組成
展示圖像
import?cv2
Img?=?cv2.imread(Penguins.jpg,0)
cv2.imshow(Penguins,?Img)
cv2.waitKey(0)
#?cv2.waitKey(2000)
cv2.destroyAllWindows()
我們首先使用 imread 導(dǎo)入圖像
接下來(lái)使用 imshow 函數(shù)通過(guò)打開(kāi)一個(gè)窗口來(lái)顯示圖像,imshow 函數(shù)有兩個(gè)參數(shù),分別是窗口的名稱和要顯示的圖像對(duì)象
然后我們等待用戶事件,waitKey 使窗口保持靜態(tài),直到用戶按下某個(gè)鍵,傳遞的參數(shù)是以毫秒為單位的時(shí)間
最后,我們使用 destroyAllWindows 根據(jù) waitForKey 參數(shù)關(guān)閉窗口
調(diào)整圖像大小
調(diào)整圖像大小也很容易
import?cv2
img?=?cv2.imread(Penguins.jpg,0)
resized_image?=?cv2.resize(img,?(650,500))
cv2.imshow(Penguins,?resized_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
在這里,resize 函數(shù)用于將圖像調(diào)整為所需的形狀,這里的參數(shù)是新調(diào)整大小的圖像的形狀
我們注意到,圖像對(duì)象從 img 變?yōu)?resized_image,因?yàn)楝F(xiàn)在圖像對(duì)象發(fā)生了變化
還有另一種方法可以將參數(shù)傳遞給 resize 函數(shù)
Resized_image?=?cv2.resize(img,?int(img.shape[1]/2),?int(img.shape[0]/2)))
這樣,我們得到的新圖像形狀會(huì)是原始圖像形狀的一半
接下來(lái)讓我們進(jìn)入實(shí)戰(zhàn)部分,使用 OpenCV 執(zhí)行人臉檢測(cè)
人臉檢測(cè)
人臉檢測(cè)?乍一看似乎很復(fù)雜,但是通過(guò) OpenCV 就非常容易了,只需要三步走即可!
第 1 步:我們首先拿到一個(gè)圖像,然后創(chuàng)建一個(gè)級(jí)聯(lián)分類器,它最終會(huì)給出我們?nèi)四樀奶卣?/p>
第 2 步:這一步涉及使用 OpenCV,它將讀取圖像和特征文件,主要就是操作 NumPy 數(shù)組
我們需要做的就是搜索人臉 NumPy ndarray 的行和列值,這是帶有人臉矩形坐標(biāo)的數(shù)組
第 3 步:使用矩形人臉框顯示圖像

首先,我們創(chuàng)建一個(gè) CascadeClassifier 對(duì)象來(lái)提取人臉的特征,參數(shù)就是包含面部特征的 XML 文件的路徑
下一步是讀取帶有人臉的圖像,并使用 COLOR_BGR2GREY 將其轉(zhuǎn)換為黑白圖像,接著,我們搜索圖像的坐標(biāo),這是使用 detectMultiScale 來(lái)完成的
什么是坐標(biāo)呢?就是面部矩形的坐標(biāo)。scaleFactor 用于將形狀值減少 5%,直到找到人臉。因此,總的來(lái)說(shuō) -- 值越小,準(zhǔn)確性越高
最后展示圖像
添加人臉框
一個(gè)比較簡(jiǎn)單的邏輯處理

我們定義了使用 cv2.rectangle 通過(guò)傳遞圖像對(duì)象、框輪廓的 RGB 值和矩形的寬度等參數(shù)來(lái)創(chuàng)建矩形的方法。
讓我們看看人臉檢測(cè)的完整代碼:
import?cv2
#?Create?a?CascadeClassifier?Object
face_cascade?=?cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
#?Reading?the?image?as?it?is
img?=?cv2.imread("photo.jpg")
#?Reading?the?image?as?gray?scale?image
gray_img?=?cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#?Search?the?co-ordintes?of?the?image
faces?=?face_cascade.detectMultiScale(gray_img,?scaleFactor?=?1.05,minNeighbors=5)
for?x,y,w,h?in?faces:
????img?=?cv2.rectangle(img,?(x,y),?(x+w,y+h),(0,255,0),3)
resized?=?cv2.resize(img,(int(img.shape[1]/7),int(img.shape[0]/7)))
cv2.imshow("Gray",?resized)
cv2.waitKey(0)
cv2.destroyAllWindows()
接下來(lái)看看如何使用 OpenCV 捕獲帶有計(jì)算機(jī)網(wǎng)絡(luò)攝像頭的視頻
使用 OpenCV 捕獲視頻
使用 OpenCV 捕獲視頻也非常簡(jiǎn)單

一張一張地讀取圖像,由于幀的快速處理已經(jīng)我們眼睛的機(jī)制(生物學(xué)范疇?)使單個(gè)圖像移動(dòng)起來(lái),就生成了視頻

首先,我們先導(dǎo)入 OpenCV 庫(kù),接下來(lái)我們使用一個(gè)名為 VideoCapture 的方法,用于創(chuàng)建 VideoCapture 對(duì)象,該方法用于觸發(fā)用戶機(jī)器上的攝像頭。此函數(shù)的參數(shù)表示程序應(yīng)使用內(nèi)置攝像頭還是附加攝像頭,“0”表示內(nèi)置攝像頭
最后的釋放方法用于在幾毫秒內(nèi)釋放系統(tǒng)相機(jī)
但是當(dāng)我們嘗試執(zhí)行上面的代碼時(shí),會(huì)注意到相機(jī)燈亮起一秒鐘然后關(guān)閉這是因?yàn)闆](méi)有時(shí)間延遲來(lái)保持相機(jī)功能
我們來(lái)增加延遲

我們?cè)黾恿?秒鐘的延遲,網(wǎng)絡(luò)攝像頭將開(kāi)啟 3 秒鐘
添加一個(gè)窗口來(lái)顯示視頻輸出

在這里,我們定義了一個(gè) NumPy 數(shù)組,我們用它來(lái)表示視頻捕獲的第一張圖像——存儲(chǔ)在幀數(shù)組中
我們還有一個(gè) check 變量——這是一個(gè)布爾數(shù)據(jù)類型,如果 Python 能夠訪問(wèn)和讀取 VideoCapture 對(duì)象,那么它返回 True
下面是代碼的輸出情況

我們得到的輸出為 True,并打印了幀數(shù)組的一部分
但是我們需要從讀取視頻的第一幀開(kāi)始,以此,我們需要首先創(chuàng)建一個(gè)幀對(duì)象,它將讀取 VideoCapture 對(duì)象的圖像

如上所示, imshow 方法用于捕獲視頻的第一幀
直接捕獲視頻
為了捕獲視頻,我們將使用 while 循環(huán)

我們使用 cvtColor 函數(shù)將每一幀轉(zhuǎn)換為灰度圖像
waitKey(1) 將確保在每毫秒間隔后生成一個(gè)新幀
這里還有一個(gè)用戶事件觸發(fā)器,一旦用戶按下“q”鍵,程序窗口就會(huì)關(guān)閉
下面我們看看如何使用 OpenCV 做一個(gè)非常有趣的運(yùn)動(dòng)檢測(cè)器
基于 OpenCV 的運(yùn)動(dòng)檢測(cè)器
問(wèn)題場(chǎng)景:通過(guò)一個(gè)網(wǎng)絡(luò)攝像頭,可以檢測(cè)到攝像頭前任何運(yùn)動(dòng)物體,并且返回一個(gè)圖表,這個(gè)圖表包含人/物體在相機(jī)前面的時(shí)間
問(wèn)題場(chǎng)景示意圖如下:

下面我們來(lái)思考下解決方案

首先我們將圖像保存在特定幀中
接下來(lái)將圖像轉(zhuǎn)換為高斯模糊圖像,這樣做是為了確保我們計(jì)算出模糊圖像和實(shí)際圖像之間的明顯差異
此時(shí),圖像仍然不是對(duì)象,我們定義了一個(gè)閾值來(lái)去除圖像中的瑕疵,例如陰影和其他噪聲等等
再接下來(lái)定義對(duì)象的邊框,我們?cè)趯?duì)象周圍添加一個(gè)矩形框
最后,我們計(jì)算對(duì)象出現(xiàn)和退出幀的時(shí)間
思路還是蠻清晰的

我們首先導(dǎo)入包并創(chuàng)建 VideoCapture 對(duì)象以確保我們使用網(wǎng)絡(luò)攝像頭捕獲視頻。
while 循環(huán)遍歷視頻的各個(gè)幀,我們將彩色幀轉(zhuǎn)換為灰度圖像,然后將此灰度圖像轉(zhuǎn)換為高斯模糊模型
我們使用 if 語(yǔ)句來(lái)存儲(chǔ)視頻的第一個(gè)圖像
接下來(lái)我們繼續(xù)深入

我們使用 absdiff 函數(shù)來(lái)計(jì)算第一個(gè)出現(xiàn)的幀與所有其他幀之間的差異
閾值函數(shù)提供閾值,將小于30的差值轉(zhuǎn)換為黑色。如果差異大于 30,它會(huì)將這些像素轉(zhuǎn)換為白色
之后我們使用 findContours 函數(shù)來(lái)定義圖像的輪廓區(qū)域
就像前面說(shuō)的,contourArea 函數(shù)可去除噪聲和陰影。為簡(jiǎn)單起見(jiàn),將只保留那部分為白色,其面積大于我們?yōu)榇硕x的 1000 像素

幀每 1 毫秒更改一次,當(dāng)用戶輸入“q”時(shí),循環(huán)中斷并關(guān)閉窗口
最后計(jì)算對(duì)象在相機(jī)前的時(shí)間

我們使用 DataFrame 來(lái)存儲(chǔ)對(duì)象檢測(cè)和移動(dòng)出現(xiàn)在幀中的時(shí)間值
在這里我們定義了一個(gè)狀態(tài)標(biāo)志位,我們?cè)阡浿崎_(kāi)始時(shí)使用此狀態(tài)為零,因?yàn)閷?duì)象最初不可見(jiàn)

當(dāng)檢測(cè)到對(duì)象時(shí),我們將狀態(tài)標(biāo)志更改為 1

我們將列出每個(gè)掃描幀的狀態(tài),如果發(fā)生更改以及發(fā)生更改的位置,則在列表中使用 datetime 記錄日期和時(shí)間

我們將時(shí)間值存儲(chǔ)在 DataFrame 中并寫(xiě)入 CSV 文件
繪制運(yùn)動(dòng)檢測(cè)圖
最后一步是顯示結(jié)果

首先,我們從 motion_detector.py 文件中導(dǎo)入DataFrame
接下來(lái)將時(shí)間轉(zhuǎn)換為可以解析的可讀字符串格式
最后,使用散景圖在瀏覽器上繪制時(shí)間值的圖表

好了,這就是今天的 OpenCV 入門(mén)實(shí)戰(zhàn),怎么樣,看過(guò)之后是不是有一種動(dòng)手的沖動(dòng)呢,一起玩起來(lái)吧!
往期精彩回顧 本站qq群955171419,加入微信群請(qǐng)掃碼:
