计算机视觉编程——OpenCV
文章目錄
- OpenCV
- 1 OpenCV的Python接口
- 2 OpenCV基礎知識
- 2.1 讀取和寫入圖像
- 2.2 顏色空間
- 2.3 顯示圖像及結果
- 3 處理視頻
- 3.1 視頻輸入
- 3.2 將視頻讀取到NumPy數組
- 4 跟蹤
- 4.1 光流
- 4.2 Lucas-Kanade算法
- 4.2.1 使用跟蹤器
- 4.2.2 使用發生器
- 5 應用實例
- 5.1 圖像修復
- 5.2 利用分水嶺變換進行分割
- 5.3 利用霍夫變換檢測直線
OpenCV
OpenCV是一個基于BSD許可(開源)發行的跨平臺計算機視覺和機器學習軟件庫,可以運行在Linux、Windows、Android和Mac OS操作系統上。 它輕量級而且高效——由一系列 C 函數和少量 C++ 類構成,同時提供了Python、Ruby、MATLAB等語言的接口,實現了圖像處理和計算機視覺方面的很多通用算法。
1 OpenCV的Python接口
OpenCV是一個C++庫,它包含了計算機視覺領域的很多模塊。除了C++和C,Python作為一種簡潔的腳本語言,在C++代碼基礎上的Pyhton接口得到了越來越廣泛的支持。
可以通過以下方式導入CV2模塊:
import cv2 OpenCV基礎知識
OpenCV自帶讀取、寫入圖像函數以及矩陣操作和數學庫,在這部分介紹一些基本的組件及其使用方法。
2.1 讀取和寫入圖像
下面的例子會載入一幅圖像,打印出圖像大小,對圖像進行轉換并保存為.png格式:
import cv2im = cv2.imread('empire.jpg') h, w = im.shape[:2] print(h, w)cv2.imwrite('result_empire.png', im)函數imread()返回圖像為一個標準的NumPy數組,并且該函數能夠處理很多不同格式的圖像。函數imwrite()會根據文件后綴自動轉換圖像。
得到的打印出的圖像大小如圖:
2.2 顏色空間
在OpenCV中,圖像不是按照傳統的RGB顏色通道,而是按照BGR順序存儲的。讀取圖像時默認的是BGR,但是還有一些可用的轉換函數。顏色空間的轉換使用函數cvColor()來實現。可以使用下面的代碼將原圖像轉為灰度圖像:
import cv2im = cv2.imread('empire.jpg')gray = cv2.cvColor(im, cv2.COLOR_BGR2GRAY)在讀取圖像之后,緊接其后的是OpenCV顏色轉換代碼,其中最有用的一些轉換代碼如下:
- cv2.COLOR_BGR2GRAY
- cv2.COLOR_BGR2RGB
- cv2.COLOR_GRAY2BGR
上面的每個轉換代碼中,轉換后的圖像顏色通道數與對應的轉換代碼相匹配。例如,對于灰度圖像只有一個通道,對于RGB和BGR圖像則有三個通道。最后的cv2.COLOR_GRAY2BGR將灰度圖像轉換為BGR彩色圖像。
2.3 顯示圖像及結果
下面這個例子是從文件中讀取一幅圖像,并創建一個整數圖像表示:
import cv2im = cv2.imread('fisherman.jpg')gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)intim = cv2.integral(gray)intim = (255.0 * intim) / intim.max() cv2.imwrite('result_fisherman.jpg', intim)讀取圖像后,將其轉化為灰度圖像,函數integral()創建一幅圖像,該圖像每個像素值是原圖上方和左邊強度值相加后的結果。在保存圖像前,通過除以圖像中的像素最大值將其歸一化到0至255之間。得到的結果如下圖所示:
下面這個例子是從一個種子像素進行泛洪填充:
import cv2 from numpy import *filename = 'fisherman.jpg' im = cv2.imread(filename) h, w = im.shape[:2]diff = (6, 6, 6) mask = zeros((h + 2, w + 2), uint8) cv2.floodFill(im, mask, (10, 10), (255, 255,0),diff, diff)cv2.imshow('flood fill', im) cv2.waitKey()cv2.imwrite('result_fisherman_1.jpg', im)在這個例子中,對圖像應用泛洪填充并在OpenCV窗口顯示。waitKey()函數一直處于暫停狀態,直到有按鍵按下,此時窗口才會自動關閉。這里的floodFill()函數獲取(灰色或彩色)圖像、一個掩模、一個種子像素以及新的顏色代替下限和上限閾值差的泛洪像素。泛洪填充以種子像素為起始,只要能在閾值的差異范圍內添加新的像素,泛洪填充就會持續擴展,結果如下圖所示:
3 處理視頻
單純使用Python處理視頻有些困難,因為需要考慮速度、編碼器、攝像機、操作系統和文件格式。使用OpenCV的Python接口是一個很好的選擇。
3.1 視頻輸入
OpenCV可以很好地支持從攝像頭讀取視頻。下面給出一個捕獲視頻幀并在OpenCV窗口中顯示這些視頻幀的完整例子:
import cv2cap = cv2.VideoCapture(0) while True:ret,im = cap.read()cv2.imshow('video test',im)key = cv2.waitKey(10)if key == 27:breakif key == ord(' '):cv2.imwrite('video_result.jpg',im)捕獲對象VideoCapture從攝像頭或文件捕獲視頻。通過一個整數進行初始化,該整數為視頻設備的id。如果僅有一個攝像頭和計算機相連,那么該攝像頭的id為0。read()方法解碼并返回下一視頻幀,第一個變量ret是一個判斷視頻幀是否成功讀入的標志,第二個變量則是實際讀入的圖像數組。函數waitKey()等待用戶按鍵:若按下Esc則退出,若按下空格則保存該視頻幀。
得到如下結果:
拓展上面的例子,將攝像頭捕獲的數據作為輸入,并在 OpenCV 窗口中實時顯示經模糊的(彩色)圖像:
import cv2cap = cv2.VideoCapture(0) while True:ret,im = cap.read()blur = cv2.GaussianBlur(im, (0, 0), 5)cv2.imshow('camera blur', blur)if cv2.waitKey(10) == 27:break每一視頻幀都會被傳遞給 GaussianBlur() 函數,該函數會用高斯濾波器對傳入的該幀圖像進行濾波。這里,我們傳遞的是彩色圖像,所以 Gaussian Blur() 函數會錄入對彩色圖像的每一個通道單獨進行模糊。該函數需要為高斯函數設定濾波器尺寸 (保存在元組中)及標準差;在本例中標準差設為 5。如果該濾波器尺寸設為 0,則它由標準差自動決定,顯示出的結果與上圖相似。
3.2 將視頻讀取到NumPy數組
使用 OpenCV 可以從一個文件讀取視頻幀,并將其轉換成 NumPy 數組。下面是一個從攝像頭捕獲視頻并將視頻幀存儲在一個 NumPy 數組中的例子:
import cv2 from numpy import *cap = cv2.VideoCapture(0) frames = []while True:ret,im = cap.read()cv2.imshow('video',im)frames.append(im)if cv2.waitKey(10) == 27:break frames = array(frames)print (im.shape) print (frames.shape)上述代碼將每一視頻幀數組添加到列表末,直到捕獲結束。最終得到的數組會有幀數、幀高、幀寬及顏色通道數(3 個),打印出的結果如下:
4 跟蹤
跟蹤是在圖像序列或視頻里對其中的目標進行跟蹤的過程。
4.1 光流
光流是目標、場景或攝像機在連續兩幀圖像間運動時造成的目標的運動。它是圖像在平移過程中的二維矢量場。
光流法主要依賴于三個假設。
- 亮度恒定 (圖像中目標的像素強度在連續幀之間不會發生變化)。
- 時間規律 (相鄰幀之間的時間足夠短,以至于在考慮運行變化時可以忽略它們之 間的差異。該假設用于導出下面的核心方程)。
- 空間一致性 (相鄰像素具有相似的運動)。
在很多情況下這些假設并不成立,但是對于相鄰幀間的小運動以及短時間跳躍,它還是一個非常好的模型。
下面就是一個利用calcOpticalFlowFarneback()在視頻中尋找運動矢量的例子:
import cv2 import numpy as np from pylab import * from PIL import Imagedef draw_flow(im,flow,step=16):h,w = im.shape[:2]y,x = mgrid[step/2:h:step,step/2:w:step].reshape(2,-1)fx, fy = flow[y, x].Tlines = vstack([x,y,x+fx,y+fy]).T.reshape(-1,2,2)lines = int32(lines)vis = cv2.cvtColor(im, cv2.COLOR_GRAY2BGR)for (x1, y1), (x2, y2) in lines:cv2.line(vis, (x1, y1), (x2, y2), (0, 255, 0), 1)cv2.circle(vis, (x1, y1), 1, (0, 255, 0), -1)return viscap = cv2.VideoCapture(0) ret,im = cap.read() prev_gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)while True:ret,im = cap.read()gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)flow = cv2.calcOpticalFlowFarneback(prev_gray,gray,None,0.5,3,15,3,5,1.2,0)prev_gray = graycv2.imshow('Optical flow',draw_flow(gray,flow))if cv2.waitKey(10) == 27:break得到的結果如下圖所示:
在上述例子中,利用攝像頭捕獲圖像,并對每個連續圖像進行光流估計。由calcOpticalFlowFarneback()返回運動光流矢量保存在雙通道圖像變量flow中。輔助函數draw_flow()會在圖像均勻間隔的點處繪制光流矢量,利用到了OpenCV的繪圖函數line()和cirle(),并用變量step控制流樣本的間距。
4.2 Lucas-Kanade算法
跟蹤最基本的形式是跟隨感興趣點,比如角點。對此,一個流行的算法是Lucas-Kanade算法,它利用了稀疏光流算法。
Lucas-Kanade算法可以應用于任何一種特征,不過通常使用一些角點,例如Harris角點。如果基于每一個像素考慮,該光流方程是欠定方程,即每個方程中含很多未知變量。利用相鄰像素有相同運動這一假設,對于n個相鄰像素,可以寫成一個系統方程,并用最小二乘法求解。對于周圍像素的貢獻可以進行加權處理,使越遠的像素影響越小。最后求解超定方程組得出運動矢量。
這些Lucas-Kanade包含在OpenCV中,接下來創建一個Python跟蹤類:
from numpy import * import cv2lk_params = dict(winSize=(15,15),maxLevel=2,criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,10,0.03)) subpix_params = dict(zeroZone=(-1,-1),winSize=(10,10),criteria = (cv2.TERM_CRITERIA_COUNT | cv2.TERM_CRITERIA_EPS,20,0.03))feature_params = dict(maxCorners=500,qualityLevel=0.01,minDistance=10)class LKTracker(object):def __init__(self,imnames):self.imnames = imnamesself.features = []self.tracks = []self.current_frame = 0用一個文件名列表對跟蹤對象進行初始化,變量features和tracks分別保存角點和對這些角點進行跟蹤的位置,同時利用一個變量對當前幀進行跟蹤。
在開始檢測角點時,需要載入實際圖像并轉換為灰度圖像,提取“利用跟蹤好的特征”點。OpenCV的函數goodFeaturesToTrack()方法可以完成這一主要工作:
def detect_points(self):self.image = cv2.imread(self.imnames[self.current_frame])self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)features = cv2.goodFeaturesToTrack(self.gray, **feature_params)cv2.cornerSubPix(self.gray,features, **subpix_params)self.features = featuresself.tracks = [[p] for p in features.reshape((-1,2))]self.prev_gray = self.gray上述代碼中的cornerSubPix()提煉角點位置,并保存在成員變量features和tracks中。需要注意的是,運行該函數會清楚跟蹤歷史。
檢測角點之后,還要對其進行跟蹤。首先要獲取一幅圖像,然后應用OpenCV中的calcOpticalFlowPyrLK()找出這些點運動到哪里,最后清除這些包含跟蹤點的列表。下面的函數track_points()可以完成該過程:
def track_points(self):if self.features != []:self.step() self.image = cv2.imread(self.imnames[self.current_frame])self.gray = cv2.cvtColor(self.image,cv2.COLOR_BGR2GRAY)tmp = float32(self.features).reshape(-1, 1, 2)features,status,track_error = cv2.calcOpticalFlowPyrLK(self.prev_gray,self.gray,tmp,None,**lk_params)self.features = [p for (st,p) in zip(status,features) if st]features = array(features).reshape((-1,2))for i,f in enumerate(features):self.tracks[i].append(f)ndx = [i for (i,st) in enumerate(status) if not st]ndx.reverse() for i in ndx:self.tracks.pop(i)self.prev_gray = self.gray下面定義一個輔助函數step()用于移動下一視頻幀:
def step(self,framenbr=None):if framenbr is None:self.current_frame = (self.current_frame + 1) % len(self.imnames)else:self.current_frame = framenbr % len(self.imnames)該方法會跳轉到一個給定的視頻幀,如果沒有參數則直接跳轉到下一幀。
最后,添加draw()方法繪出跟蹤的結果:
def draw(self):for point in self.features:cv2.circle(self.image,(int(point[0][0]),int(point[0][1])),3,(0,255,0),-1)cv2.imshow('LKtrack',self.image)cv2.waitKey()4.2.1 使用跟蹤器
我們將該跟蹤類應用于真是的場景中。下面的腳本初始化一個跟蹤對象,對視頻序列進行角點檢測、跟蹤,并畫出跟蹤結果:
import lktrack imnames = ['bt.003.pgm', 'bt.002.pgm', 'bt.001.pgm', 'bt.000.pgm']lkt = lktrack.LKTracker(imnames)lkt.detect_points() lkt.draw() for i in range(len(imnames)-1):lkt.track_points()lkt.draw()每次畫出一幀,并顯示當前跟蹤到的點,按任意鍵會轉移到序列的下一幀。
4.2.2 使用發生器
將下面的方法添加到LKTracker類:
def track(self):for i in range(len(self.imnames)):if self.features == []:self.detect_points()else:self.track_points()f = array(self.features).reshape(-1,2)im = cv2.cvtColor(self.image,cv2.COLOR_BGR2RGB)yield im,f上面的方法創建一個發生器,可以使遍歷整個序列并將獲得的跟蹤點和這些圖像以RGB數組保存,以便畫出跟蹤結果。
import lktrack import cv2 import numpy as np from pylab import * from PIL import Imageimnames = ['bt.003.pgm', 'bt.002.pgm', 'bt.001.pgm', 'bt.000.pgm']lkt = lktrack.LKTracker(imnames) for im,ft in lkt.track():print ('tracking %d features' % len(ft))figure() imshow(im) for p in ft:plot(p[0],p[1],'bo') for t in lkt.tracks:plot([p[0] for p in t],[p[1] for p in t]) axis('off') show()畫出的跟蹤點的軌跡如圖所示:
5 應用實例
5.1 圖像修復
對圖像丟失或損壞的部分進行重建的過程叫做修復,既包括以復原為目的的對圖像丟失數據或損壞部分進行恢復的算法。典型例子是,圖像的一個區域標記為“損壞”,并需要利用余下的數據對該區域進行填補。
運行下邊的命令:
import cv2 from matplotlib import pyplot as plt from pylab import * from PIL import Image import numpy as npdef inpaint(img, threshold=1):h, w = img.shape[:2]if len(img.shape) == 3: # RGBmask = np.all(img == 0, axis=2).astype(np.uint8)img = cv2.inpaint(img, mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA)else: # depthmask = np.where(img > threshold)xx, yy = np.meshgrid(np.arange(w), np.arange(h))xym = np.vstack((np.ravel(xx[mask]), np.ravel(yy[mask]))).Timg = np.ravel(img[mask])interp = interpolate.NearestNDInterpolator(xym, img)img = interp(np.ravel(xx), np.ravel(yy)).reshape(xx.shape)return imgim = cv2.imread('empire.jpg') img = inpaint(im)imshow(img)最后顯示出修復的情況:
5.2 利用分水嶺變換進行分割
分水嶺是一種可以用于分割的圖像處理技術。圖像可以看成是一幅有很多種子區域“淹沒”后形成的拓撲地貌。由于梯度幅值圖像在突出的邊緣有脊,而且分割通常在這些邊緣處停止,所以通常會用到梯度幅值圖像。
運行下邊的代碼:
import numpy as np import cv2 from matplotlib import pyplot as pltsrc = cv2.imread('empire_1.jpg') img = src.copy() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)kernel = np.ones((3, 3), np.uint8) opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)sure_bg = cv2.dilate(opening, kernel, iterations=3)dist_transform = cv2.distanceTransform(opening, 1, 5) ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)sure_fg = np.uint8(sure_fg) unknown = cv2.subtract(sure_bg, sure_fg)ret, markers1 = cv2.connectedComponents(sure_fg)markers = markers1 + 1markers[unknown == 255] = 0markers3 = cv2.watershed(img, markers) img[markers3 == -1] = [0, 0, 255]plt.subplot(241), plt.imshow(cv2.cvtColor(src, cv2.COLOR_BGR2RGB)), plt.title('Original'), plt.axis('off') plt.subplot(242), plt.imshow(thresh, cmap='gray'), plt.title('Threshold'), plt.axis('off') plt.subplot(243), plt.imshow(sure_bg, cmap='gray'), plt.title('Dilate'), plt.axis('off') plt.subplot(244), plt.imshow(dist_transform, cmap='gray'), plt.title('Dist Transform'), plt.axis('off') plt.subplot(245), plt.imshow(sure_fg, cmap='gray'), plt.title('Threshold'), plt.axis('off') plt.subplot(246), plt.imshow(unknown, cmap='gray'), plt.title('Unknow'), plt.axis('off') plt.subplot(247), plt.imshow(np.abs(markers), cmap='jet'), plt.title('Markers'), plt.axis('off') plt.subplot(248), plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.title('Result'), plt.axis('off')plt.show()得到的結果如下:
5.3 利用霍夫變換檢測直線
霍夫變換是一種用于在圖像中尋找各種形狀的方法,原理是在參數空間中使用投票機制。霍夫變換常用于在圖像中尋找直線結構,在該情況下,可以在二維直線參數空間對相同的直線參數進行投票,將邊緣和線段組合在一起。
運行下面的這段代碼:
import cv2 import numpy as npimg = cv2.imread("empire.jpg", 0)img = cv2.GaussianBlur(img, (3, 3), 0) edges = cv2.Canny(img, 50, 150, apertureSize=3) lines = cv2.HoughLines(edges, 1, np.pi / 180, 118) result = img.copy() for line in lines[0]:rho = line[0] theta = line[1] print (rho)print (theta)if (theta < (np.pi / 4.)) or (theta > (3. * np.pi / 4.0)): pt1 = (int(rho / np.cos(theta)), 0)pt2 = (int((rho - result.shape[0] * np.sin(theta)) / np.cos(theta)), result.shape[0])cv2.line(result, pt1, pt2, (255))else: pt1 = (0, int(rho / np.sin(theta)))pt2 = (result.shape[1], int((rho - result.shape[1] * np.cos(theta)) / np.sin(theta)))cv2.line(result, pt1, pt2, (255), 1)cv2.imshow('Canny', edges) cv2.imshow('Result', result) cv2.waitKey(0) cv2.destroyAllWindows()得到的結果如下圖所示:
總結
以上是生活随笔為你收集整理的计算机视觉编程——OpenCV的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机视觉编程——图像分割
- 下一篇: Python基础——Anaconda的安