OpenCV4.1+python學習筆記
你好,我是一名大三的非計算機專業的學生,最近因為參加比賽需要實作用攝像頭尋找一個圓柱體坐标并測算距離的功能。方案打算用淘寶買的雙目鏡頭進行特征值檢測,識别到物體後進行測距,具體的實作過程需要進一步的學習。我打算先在我的台式的Ubuntu上成功搞定這個功能後移植到nano上。有一定python基礎,Opencv從零開始自學,希望能用大概一周的時間搞定掉這個功能。
寫這篇部落格記錄我的學習與實踐過程,同時也希望幫助有需要的人,歡迎大家在評論裡指出我的錯誤。
部分教程參考自EX2TRON
在這裡做幾個跳轉友善以後看這篇文章
- 環境搭建
- 讀取雙目鏡頭視訊信号
- 對圖像的基本操作
- 門檻值分割
- 顯示幀數
- 圖像幾何變換
- 模闆比對
- 函數庫
- 遇到的問題
環境搭建
環境搭建教程
裝環境時參考的這篇文章,中間遇到了一個在執行sudo apt install 時程序被占用的小問題,最開始用查找程序然後kill pid的方法,但始終有一個調用了apt的程序無法關閉,最後還是用了重新開機大法,重新開機後無卡頓每一步完美執行,最後進行測試
import cv2
print(cv2.__version__)
4.1.1
傳回opencv版本為4.1.1,結果無誤,成功配置環境
讀取雙目鏡頭視訊信号與加載圖檔
最開始讀取視訊隻有左攝像頭有輸出,以為是輸入源隻設定了一個,于是使用下面的指令檢視了一下我的雙目鏡頭的video編号。
$ ls /dev/video*
/dev/video0
隻有一個輸入,經查詢得知,我用的是雙目的鏡頭,雖然有兩個鏡頭的輸出但是隻有一根usb線,隻在usb總線上隻有一個視訊輸入源。
思考了一下,想起淘寶裡的商品描述寫着如果要輸出雙目的信号必須設定特定的分辨率,于是設定了一下分辨率,分辨率改為1280*480果然輸出了兩路視訊信号。
Attention !!一定要設定一個退出的按鍵,否則點×是不好使的,因為隻能×掉這一幀的圖檔,是關不掉程式的。在程式的末尾随便設定一個退出鍵就ok了
if cv2.waitKey(1) == ord('q'):
break
讀取雙目的的完整程式如下
import cv2
capture = cv2.VideoCapture(0)
# 設定攝像頭分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
# 擷取一幀
ret,frame = capture.read(1)
#轉換成灰階圖
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
cv2.imshow('frame',gray)
if cv2.waitKey(1) == ord('q'):
break
加載圖檔:
使用cv2.imgread()函數,該函數有兩個參數:
參數1:圖檔的檔案名
如果圖檔放在目前檔案夾下,直接寫檔案名就行了,如’lena.jpg’
否則需要給出絕對路徑,如’D:\OpenCVSamples\lena.jpg’
參數2:讀入方式,省略即采用預設值
cv2.IMREAD_COLOR:彩色圖,預設值(1)
cv2.IMREAD_GRAYSCALE:灰階圖(0)
cv2.IMREAD_UNCHANGED:包含透明通道的彩色圖(-1)
注意路徑名中不能有中文。
對圖像的基本操作
我們先讀入一張圖檔:
import cv2
img = cv2.imread('lena.jpg')
img[y,x]對應的是這張圖檔在y行x列像素點的BGR值(對于彩色圖),對于灰階或者是單通道的圖檔隻有一個值。
px = img[100, 90]
print(px) # [103 98 197]
# 隻擷取藍色blue通道的值
px_blue = img[100, 90, 0]
print(px_blue) # 103
修改像素的值也是同樣的方式:
img[100, 90] = [255, 255, 255]
print(img[100, 90]) # [255 255 255]
注意:這步操作隻是記憶體中的img像素點值變了,因為沒有儲存,是以原圖并沒有更改。
圖檔的屬性
img.shape擷取圖像的形狀,圖檔是彩色的話,傳回一個包含行數(高度)、列數(寬度)和通道數的元組,灰階圖隻傳回行數和列數:
print(img.shape) # (263, 247, 3)
# 形狀中包括行數、列數和通道數
height, width, channels = img.shape
# img是灰階圖的話:height, width = img.shape
img.dtype擷取圖像資料類型:
print(img.dtype) # uint8
img.size擷取圖像總像素數:
print(img.size) # 263*247*3=194883
ROI
Region of interest 感興趣區域,用來節省算力,一副圖檔我要提取眼睛,眼睛肯定在臉上是以不是臉的區域我都不care。
提取ROI
face=img[100:200,150:160] #提取第100行到200行中150列到160列為ROI
門檻值分割
固定門檻值分割
非常直接,就是像素點的門檻值大于定義的門檻值就是一類值,小于就是一類值。大二的時候做的智能車比賽用的鷹眼ov7725就是固定門檻值分割的,将灰階圖處理成黑白二值化的圖像,門檻值可以自己修改寄存器來制定大小。
自适應門檻值分割
将圖像分割為多個小塊,取每個小塊的均值之和進行權重計算得出的門檻值為整幅圖像的門檻值,适用于明暗分布不均的圖檔。使用函數為cv2.adaptiveThreshold(),具體說明見函數庫。
這裡使用了python的matplotlib庫進行圖像顯示,我第一次接觸這個庫,打算用多少學多少,看着函數名加上百度把這些函數的功能猜的七七八八,大概弄明白是幹什麼的就好。
看了半天matplotlib庫結果編譯提示GTK版本不相容,想要弄好好像挺費勁就沒用。
固定均值
小區域内取均值
小區域内取高斯和
自動門檻值完整代碼
import cv2
capture = cv2.VideoCapture(0)
# 設定攝像頭分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
# 擷取一幀
ret,frame = capture.read()
#轉換成灰階圖
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
# 固定門檻值
ret,th1 = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 自适應門檻值
th2 = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 4)
th3 = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 17, 6)
cv2.imshow('frame',th2)
if cv2.waitKey(1) == ord('q'):
break
大津法(OTSU)
Otsu的教程,寫的非常好
按照教程給列的式子自己推導了一下,數學太爛,非常簡單的東西推了快半個小時,不過推完感覺對這個算法了解了一些。
當時做智能車的時候隊友就一直吵吵要用這個算法不過當時時間比較緊張沒有學,以為是特别高端的算法其實也不算太難。
大津法将圖檔視為背景和前景,區分背景和前景的是門檻值,通過周遊所有門檻值找出使這幅圖像背景與前景區分最明顯的一個值,反應到數學上就是方差,周遊求方差的最大值。
非常适合用在那種對比度明顯的場合,比如說飛卡的賽道,用了這個算法就不用每次自己根據光照環境去手動調門檻值了,大津前輩真是強!
usb線太短了拍不到飛卡賽道,拿我的ipad做下示範。
大津算法完整代碼:
import cv2
import matplotlib.pyplot as plt
capture = cv2.VideoCapture(0)
# 設定攝像頭分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
# 擷取一幀
ret,frame = capture.read()
#轉換成灰階圖
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
# Otsu
ret,th1 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
plt.subplot(3, 1, 2)
plt.imshow(th1, 'gray')
plt.xticks([]), plt.yticks([])
plt.title('Otsu', fontsize=8)
plt.subplot(3, 3, 3)
plt.hist(frame.ravel(), 256)
plt.xticks([]), plt.yticks([])
plt.title('Histogram', fontsize=8)
plt.show()
if cv2.waitKey(1) == ord('q'):
break
顯示幀數
由于我做的是機器人比賽,在處理視訊信号時幀數越高,控制周期越短,響應的更及時一些。是以顯示幀數也是一個比較大的需求。
上網查了一下opencv中居然木有封裝好的函數,找了别人寫的c++版本,然後翻譯成了python的。
具體思路就是可以擷取作業系統啟動以來的計時的周期和cpu的頻率,每次開始擷取圖像時進行計時,擷取到圖像後将時間-開始時的時間可得擷取圖像所用周期,除以主頻就是時間,就是fps的倒數。
完整程式如下:
import cv2
capture = cv2.VideoCapture(0)
# 設定攝像頭分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
t = float (0)
while(True):
# 開始計時,作業系統從啟動到現在所經曆的周期數
t = cv2.getTickCount();
# 擷取一幀
ret,frame = capture.read()
if ret == True :
#擷取一幀消耗的時間=擷取一幀所用的周期數/cpu的頻率
t = (cv2.getTickCount()-t) / cv2.getTickFrequency()
#估算fps,就是擷取一幀時間所用的倒數
fps = round(1.0/t,2)
fps = str(fps)
#轉換成灰階圖
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
cv2.putText(gray,fps,(5,20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0))
cv2.imshow('frame',gray)
if cv2.waitKey(1) == ord('q'):
break
運作效果圖:
左上角是幀數,弄得有點太小了
以後有時間了把這個封裝成函數,這麼用太費勁了。
_(: 」∠)_
圖像幾何變換
縮放何旋轉都比較簡單就不做了,參照下邊的兩個函數就可以實作。
平移圖像
要平移圖檔,我們需要定義下面這樣一個矩陣,tx,ty是向x和y方向平移的距離:
M=
[
1 0 tx
0 1 ty
]
平移是用仿射變換函數cv2.warpAffine()實作的,這裡邊進行矩陣運算時調用了python的numpy的庫需要import一下,numpy是python的一個負責進行矩陣運算的拓展庫。
平移完整程式:
import cv2
import numpy as np
capture = cv2.VideoCapture(1)
# 設定攝像頭分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
# 擷取一幀
ret,img = capture.read(1)
rows,cols = img.shape[:2]
#定義平移矩陣
M = np.float32([[1,0,100],[0,1,50]])
#用放射變換實作平移
dst = cv2.warpAffine(img,M,(cols,rows))
#轉換成灰階圖
gray = cv2.cvtColor(dst,cv2.COLOR_BGR2GRAY)
cv2.imshow('frame',gray)
if cv2.waitKey(1) == ord('q'):
break
旋轉圖像
也是調用函數就可以實作,cv2.getRotationMatrix2D(),具體函數說明見函數庫。
模闆比對
模闆比對就是大圖中找下圖,适應性不好,在圖像大小和角度改變的情況下比對度比較低,不适合用做物體識别。
模闆比對完整代碼:
import numpy as np
import cv2
capture = cv2.VideoCapture(0)
# 設定攝像頭分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
template1 = cv2.imread('3.png',0)
h,w = template1.shape[:2]
threshold = 0.95
while(True):
# 擷取一幀
ret,img = capture.read(1)
#轉換成灰階圖
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(gray, template1, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
print(max_val)
left_top = max_loc # 左上角
right_bottom = (left_top[0] + w, left_top[1] + h) # 右下角
cv2.rectangle(gray, left_top, right_bottom, 255, 2) # 畫出矩形位置
cv2.imshow('frame',gray)
if cv2.waitKey(1) == ord('q'):
break
函數庫
cv2.VideoCapture()
要使用攝像頭,需要使用cv2.VideoCapture(0)建立VideoCapture對象,參數0指的是攝像頭的編号,如果你電腦上有兩個攝像頭的話,通路第2個攝像頭就可以傳入1,依此類推.
cv2.capture.read()
capture.read()函數傳回的第1個參數ret(return value縮寫)是一個布爾值,表示目前這一幀是否擷取正确,第二個就是目前這一幀的圖像了。
cv2.cvtColor()
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.cvtColor()用來轉換顔色,這裡将彩色圖轉成灰階圖。
cv2.waitKey()
if cv2.waitKey(1) == ord('q')://檢測按鍵q是否被按下
檢測按鍵是否被按下,十分友好,炒雞好用!!
cv2.imgread()
參數1:圖檔的檔案名
如果圖檔放在目前檔案夾下,直接寫檔案名就行了,如’lena.jpg’
否則需要給出絕對路徑,如’D:\OpenCVSamples\lena.jpg’
參數2:讀入方式,省略即采用預設值
cv2.IMREAD_COLOR:彩色圖,預設值(1)
cv2.IMREAD_GRAYSCALE:灰階圖(0)
cv2.IMREAD_UNCHANGED:包含透明通道的彩色圖(-1)
import cv2
# 加載灰階圖
img = cv2.imread('lena.jpg', 0)
cv2.threshold()
threshold是門檻值的意思
用來實作門檻值分割,ret是return value縮寫,代表目前的門檻值,暫時不用理會。函數有4個參數:
參數1:要處理的原圖,一般是灰階圖
參數2:設定的門檻值
參數3:最大門檻值,一般為255
參數4:門檻值的方式,主要有5種
五種門檻值的方式如下:
第一種THRESH_BIANARY:意思就是大于你設定的門檻值就是maxval(一般都是255),小于你設定的門檻值就是0。
第二種THRESH_BIANARY_INV:後邊那個inv是invertrd 翻轉的意思就是大于設定門檻值的變成0小于的變成maxval。
第三種THRESH_TRUNC: trunc的意思是取整,就是大于門檻值的都變成門檻值,其他的都變成maxval。
第四種THTRSH_TOZERO:大于門檻值的不變,其他的都取0.
第五種:大于門檻值的變成0,其他的不變。
應用5種不同的門檻值方法
ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, th2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, th3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, th4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, th5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)
cv2.adaptiveThreshold()
看得出來固定門檻值是在整幅圖檔上應用一個門檻值進行分割,它并不适用于明暗分布不均的圖檔。 cv2.adaptiveThreshold()自适應門檻值會每次取圖檔的一小部分計算門檻值,這樣圖檔不同區域的門檻值就不盡相同。它有6個參數
參數1:要處理的原圖
參數2:最大門檻值,一般為255
參數3:小區域門檻值的計算方式
ADAPTIVE_THRESH_MEAN_C:小區域内取均值
ADAPTIVE_THRESH_GAUSSIAN_C:小區域内權重求和,權重是個高斯核
參數4:門檻值方式(跟前面講的那5種相同)
參數5:小區域的面積,如11就是11*11的小塊
參數6:最終門檻值等于小區域計算出的門檻值再減去此值
我了解這個函數的意思是通過對整幅圖像進行分割,然後為每個小方塊進行權重求和最後得出的門檻值相對于手動設定的門檻值效果更好一點。
cv2.resize()
縮放圖檔
# 按照指定的寬度、高度縮放圖檔
res = cv2.resize(img, (132, 150))
# 按照比例縮放,如x,y軸均放大一倍
res2 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR
cv2.flip()
鏡像翻轉圖檔。
dst = cv2.flip(img, 1)
其中,參數2 = 0:垂直翻轉(沿x軸),參數2 > 0: 水準翻轉(沿y軸),參數2 < 0: 水準垂直翻轉。
cv2.getRotationMatrix2D()
旋轉同平移一樣,也是用仿射變換實作的,是以也需要定義一個變換矩陣。OpenCV直接提供了 cv2.getRotationMatrix2D()函數來生成這個矩陣,該函數有三個參數:
參數1:圖檔的旋轉中心
參數2:旋轉角度(正:逆時針,負:順時針)
參數3:縮放比例,0.5表示縮小一半
45°旋轉圖檔并縮小一半
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 0.5)
dst = cv2.warpAffine(img, M, (cols, rows))
cv2.imshow('rotation', dst)
cv2.waitKey(0)
遇到的問題
1 安裝環境
在我用筆記本裝opencv的環境時,使用同樣的步驟,每一步都沒有問題,到最後驗證版本時提示無法找到cv2子產品,重試了幾次都有這個問題。目前沒找到解決辦法,如果筆記本的ubuntu裝不上的話,可能會考慮在Windows下裝一下環境。
問題解決了 使用
sudo apt install python3-opencv
2 GTK版本不相容
在使用Matplotlib庫時提示過這個錯誤,一開始以為要重新安裝OpenCV後來發現調用的時候使用這個就沒問題了。
import matplotlib.pyplot as plt
Attention好像不是這麼一回事,調用matplotlib庫的時候不應使用顯示視訊的imshow,否則就會報版本不相容的錯誤