如何從視頻文件中分解出一張張獨立的圖片?算法
視頻其實就是一張張的圖片,利用opencv庫能夠很容易的從視頻文件中抽離出圖像文件來,下面咱們就看一段示例代碼是如何從視頻文件中抽取出多張圖片的。數組
例子:網絡
import cv2 cap = cv2.VideoCapture("F:/ai/a_002.mp4") success, frame = cap.read() index = 1 while success : index = index+1 cv2.imwrite(str(index)+".png",frame) if index > 20: break; success,frame = cap.read() cap.release()
執行完上述代碼後咱們能夠看到視頻文件目錄下生成了無數的圖片文件,如圖:app
好了,圖片抽取出來以後就能夠用各類神經網絡模型來玩圖像識別等應用了。 dom
二、圖像處理scipy.ndimageide
scipy.ndimage是一個處理多維圖像的函數庫,它其中又包括如下幾個模塊:函數
更強大的圖像處理庫。性能
scipy.ndimage只提供了一些基礎的圖像處理功能,下面是一些更強大的圖像處理庫:ui
三、熱點圖spa
首先載入地圖圖片,並建立一些隨機分佈的散列點,這些散列點以某些座標爲中心正態分佈,構成一些熱點。使用numpy.histogram2d()能夠在地圖圖片的網格中統計二維散列點的頻度。因爲散列點數量較少,histogram2d()的結果並不能造成足夠的熱點信息:
img = plt.imread("images/china010.png") h, w, _ = img.shape xs, ys = [], [] for i in range(100): mean = w*np.random.rand(), h*np.random.rand() a = 50 + np.random.randint(50, 200) b = 50 + np.random.randint(50, 200) c = (a + b)*np.random.normal()*0.2 cov = [[a, c], [c, b]] count = 200 x, y = np.random.multivariate_normal(mean, cov, size=count).T xs.append(x) ys.append(y) x = np.concatenate(xs) y = np.concatenate(ys) hist, _, _ = np.histogram2d(x, y, bins=(np.arange(0, w), np.arange(0, h))) hist = hist.T plt.imshow(hist);
調用scipy.ndimage.filters.gaussian_filter對頻度圖進行高斯模糊處理,則至關與在上圖中每一個亮點處描繪一個高斯曲面,讓每一個亮點增長其周圍的像素的亮度。其第二個參數爲高斯曲面的寬度,即高斯分佈的標準差。這個值越大,曲面的影響範圍越大,最終的熱點圖也越平滑。
from scipy.ndimage import filters heat = filters.gaussian_filter(hist, 10.0) plt.imshow(heat);
下面經過修改熱點圖的alpha通道,將熱點圖與地圖疊加顯示。
四、高斯濾波
高斯濾波在圖像處理概念下,將圖像頻域處理和時域處理相聯繫,做爲低通濾波器使用,能夠將低頻能量(好比噪聲)濾去,起到圖像平滑做用。
高斯濾波是一種線性平滑濾波,適用於消除高斯噪聲,普遍應用於圖像處理的減噪過程。通俗的講,高斯濾波就是對整幅圖像進行加權平均的過程,每個像素點的值,都由其自己和鄰域內的其餘像素值通過加權平均後獲得。高斯濾波的具體操做是:用一個模板(或稱卷積、掩模)掃描圖像中的每個像素,用模板肯定的鄰域內像素的加權平均灰度值去替代模板中心像素點的值用。高斯平滑濾波器對於抑制服從正態分佈的噪聲很是有效。
咱們常說的高斯模糊就是使用高斯濾波器完成的,高斯模糊是低通濾波的一種,也就是濾波函數是低通高斯函數,可是高斯濾波是指用高斯函數做爲濾波函數,至因而不是模糊,要看是高斯低通仍是高斯高通,低通就是模糊,高通就是銳化。
在圖像處理中,高斯濾波通常有兩種實現方式,一是用離散化窗口滑窗卷積,另外一種經過傅里葉變換。最多見的就是第一種滑窗實現,只有當離散化的窗口很是大,用滑窗計算量很是大(即便用可分離濾波器的實現)的狀況下,可能會考慮基於傅里葉變化的實現方法。
因爲高斯函數能夠寫成可分離的形式,所以能夠採用可分離濾波器實現來加速。所謂的可分離濾波器,就是能夠把多維的卷積化成多個一維卷積。具體到二維的高斯濾波,就是指先對行作一維卷積,再對列作一維卷積。這樣就能夠將計算複雜度從O(M*M*N*N)降到O(2*M*M*N),M,N分別是圖像和濾波器的窗口大小。
高斯模糊是一個很是典型的圖像卷積例子,本質上,高斯模糊就是將(灰度)圖像和一個高斯核進行卷積操做:
其中 * 表示卷積操做; Gσ 是標準差爲σ 的二維高斯核,定義爲:
這裏補充如下卷積的知識:
卷積是分析數學中一種重要的運算。
設:f(x),g(x)是R1上的兩個可積函數,做積分:
能夠證實,關於幾乎全部的實數x,上述積分是存在的。這樣,隨着x的不一樣取值,這個積分就定義了一個新函數h(x),稱爲函數f與g的卷積,記爲h(x)=(f*g)(x)。
卷積是一個單純的定義,自己沒有什麼意義可言,可是其在各個領域的應用是十分普遍的,在濾波中能夠理解爲一個加權平均過程,每個像素點的值,都由其自己和鄰域內的其餘像素值通過加權平均後獲得,而如何加權則是依據核函數高斯函數。
平均的過程:
對於圖像來講,進行平滑和模糊,就是利用周邊像素的平均值。
「中間點」取」周圍點」的平均值,就會變成1。在數值上,這是一種」平滑化」。在圖形上,就至關於產生」模糊」效果,」中間點」失去細節。
顯然,計算平均值時,取值範圍越大,」模糊效果」越強烈。
使用opencv2進行高斯濾波很方便,參考下面代碼:
import cv2 #兩個回調函數 def GaussianBlurSize(GaussianBlur_size): global KSIZE KSIZE = GaussianBlur_size * 2 +3 print KSIZE, SIGMA dst = cv2.GaussianBlur(scr, (KSIZE,KSIZE), SIGMA, KSIZE) cv2.imshow(window_name,dst)
咱們對圖片lena.png添加噪音做爲輸入圖片,進行高斯濾波後看看結果如何。這個例子中咱們採用的核大小是3,代碼以下:
from __future__ import print_function import os import struct import numpy as np import cv2 KSIZE = 3 SIGMA = 3 image = cv2.imread("d:/ai/lena.png") print("image shape:",image.shape) dst = cv2.GaussianBlur(image, (KSIZE,KSIZE), SIGMA, KSIZE) cv2.imshow("img1",image) cv2.imshow("img2",dst) cv2.waitKey()
最終生成的結果以下圖所示(左邊是噪音圖像,右邊是處理後的結果):
五、圖片翻轉
openCv2提供圖像的翻轉函數getRotationMatrix2D和warpAffine,詳細用法可查看相關API文檔,這裏給出一個簡單的例子。
from __future__ import print_function import os import struct import math import numpy as np import cv2 def rotate( img, #image matrix angle #angle of rotation ): height = img.shape[0] width = img.shape[1] if angle%180 == 0: scale = 1 elif angle%90 == 0: scale = float(max(height, width))/min(height, width) else: scale = math.sqrt(pow(height,2)+pow(width,2))/min(height, width) #print 'scale %f\n' %scale rotateMat = cv2.getRotationMatrix2D((width/2, height/2), angle, scale) rotateImg = cv2.warpAffine(img, rotateMat, (width, height)) return rotateImg #rotated image image = cv2.imread("d:/ai/lena.png") dst = rotate(image,60) cv2.imshow("img1",image) cv2.imshow("img2",dst) cv2.waitKey()
經過如下這兩個方法得到翻轉後的圖像矩陣:
rotateMat = cv2.getRotationMatrix2D((width/2, height/2), angle, scale) rotateImg = cv2.warpAffine(img, rotateMat, (width, height))
運行以後得出結果以下圖所示:
六、輪廓檢測
輪廓檢測也是圖像處理中常常用到的。OpenCV2使用findContours()函數來查找檢測物體的輪廓。
import cv2 img = cv2.imread('D:\\test\\contour.jpg') gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY) contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(img,contours,-1,(0,0,255),3) cv2.imshow("img", img) cv2.waitKey(0)
須要注意的是cv2.findContours()函數接受的參數爲二值圖,即黑白的(不是灰度圖),因此讀取的圖像要先轉成灰度的,再轉成二值圖。
原圖以下:
檢測結果以下:
注意,findcontours函數會「原地」修改輸入的圖像。這一點可經過下面的語句驗證:
cv2.imshow("binary", binary) contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE) cv2.imshow("binary2", binary)
執行這些語句後會發現原圖被修改了。
cv2.findContours()函數
函數的原型爲
cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])
返回兩個值:contours:hierarchy。
參數
第一個參數是尋找輪廓的圖像;
第二個參數表示輪廓的檢索模式,有四種(本文介紹的都是新的cv2接口):
cv2.RETR_EXTERNAL表示只檢測外輪廓。
cv2.RETR_LIST檢測的輪廓不創建等級關係。
cv2.RETR_CCOMP創建兩個等級的輪廓,上面的一層爲外邊界,裏面的一層爲內孔的邊界信息。若是內孔內還有一個連通物體,這個物體的邊界也在頂層。
cv2.RETR_TREE創建一個等級樹結構的輪廓。
第三個參數method爲輪廓的近似辦法。
cv2.CHAIN_APPROX_NONE存儲全部的輪廓點,相鄰的兩個點的像素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1。
cv2.CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點座標,例如一個矩形輪廓只需4個點來保存輪廓信息。
cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法。
返回值
cv2.findContours()函數返回兩個值,一個是輪廓自己,還有一個是每條輪廓對應的屬性。
contour返回值
cv2.findContours()函數首先返回一個list,list中每一個元素都是圖像中的一個輪廓,用numpy中的ndarray表示。這個概念很是重要。在下面drawContours中會看見。
print (type(contours))
print (type(contours[0]))
print (len(contours))
能夠驗證上述信息。會看到本例中有兩條輪廓,一個是五角星的,一個是矩形的。每一個輪廓是一個ndarray,每一個ndarray是輪廓上的點的集合。
因爲咱們知道返回的輪廓有兩個,所以可經過
cv2.drawContours(img,contours,0,(0,0,255),3)
和
cv2.drawContours(img,contours,1,(0,255,0),3)
分別繪製兩個輪廓,關於該參數可參見下面一節的內容。同時經過
print (len(contours[0]))
print (len(contours[1]))
輸出兩個輪廓中存儲的點的個數,能夠看到,第一個輪廓中只有4個元素,這是由於輪廓中並非存儲輪廓上全部的點,而是隻存儲能夠用直線描述輪廓的點的個數,好比一個「正立」的矩形,只需4個頂點就能描述輪廓了。
七、角點
角點的定義和特性:
Open 中的函數cv2.cornerHarris(src, blockSize, ksize, k[, dst[, borderType]]) → dst 能夠用來進行角點檢測。參數以下:
# coding=utf-8 import cv2 import numpy as np '''Harris算法角點特徵提取''' img = cv2.imread('chess_board.png') gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) gray = np.float32(gray) # {標記點大小,敏感度(3~31,越小越敏感)} # OpenCV函數cv2.cornerHarris() 有四個參數 其做用分別爲 : dst = cv2.cornerHarris(gray,2,23,0.04) img[dst>0.01 * dst.max()] = [0,0,255] cv2.imshow('corners',img) cv2.waitKey() cv2.destroyAllWindows()
最後檢測出角點並在圖像上標註出來,示例以下:
八、直方圖
圖像的構成是有像素點構成的,每一個像素點的值表明着該點的顏色(灰度圖或者彩色圖)。所謂直方圖就是對圖像的中的這些像素點的值進行統計,獲得一個統一的總體的灰度概念。直方圖的好處就在於能夠清晰瞭解圖像的總體灰度分佈,這對於後面依據直方圖處理圖像來講相當重要。
通常狀況下直方圖都是灰度圖像,直方圖x軸是灰度值(通常0~255),y軸就是圖像中每個灰度級對應的像素點的個數。
那麼如何得到圖像的直方圖?首先來了解繪製直方圖須要的一些量:灰度級,正常狀況下就是0-255共256個灰度級,從最黑一直到最亮(白)(也有可能統計其中的某部分灰度範圍),那麼每個灰度級對應一個數來儲存該灰度對應的點數目。也就是說直方圖其實就是一個1*m(灰度級)的一個數組而已。可是有的時候咱們不但願一個一個灰度的遞增,好比如今我想15個灰度一塊兒做爲一個灰度級來花直方圖,這個時候咱們可能只須要1*(m/15)這樣一個數組就夠了。那麼這裏的15就是直方圖的間隔寬度了。
OpenCV給咱們提供的函數是cv2.calcHist(),該函數有5個參數:
除此以外,強大的numpy也有函數用於統計直方圖的,通用的一個函數np.histogram,還有一個函數是np.bincount()(用於覺得統計直方圖,速度更快)。這三個方式的傳入參數基本上差很少,不一樣的是opencv自帶的須要中括號括起來。
對於直方圖的顯示也是比較簡單的,直接plt.plot()就能夠。一個實例以下:
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread('flower.jpg',0) #直接讀爲灰度圖像 #opencv方法讀取-cv2.calcHist(速度最快) #圖像,通道[0]-灰度圖,掩膜-無,灰度級,像素範圍 hist_cv = cv2.calcHist([img],[0],None,[256],[0,256]) #numpy方法讀取-np.histogram() hist_np,bins = np.histogram(img.ravel(),256,[0,256]) #numpy的另外一種方法讀取-np.bincount()(速度=10倍法2) hist_np2 = np.bincount(img.ravel(),minlength=256) plt.subplot(221),plt.imshow(img,'gray') plt.subplot(222),plt.plot(hist_cv) plt.subplot(223),plt.plot(hist_np) plt.subplot(224),plt.plot(hist_np2)
如今來考慮opencv的直方圖函數中掩膜的使用,這個掩膜就是一個區域大小,表示你接下來的直方圖統計就是這個區域的像素統計。一個例子以下:
import cv2 import numpy as np import matplotlib.pyplot as plt img = cv2.imread('flower.jpg',0) #直接讀爲灰度圖像 mask = np.zeros(img.shape[:2],np.uint8) mask[100:200,100:200] = 255 masked_img = cv2.bitwise_and(img,img,mask=mask) #opencv方法讀取-cv2.calcHist(速度最快) #圖像,通道[0]-灰度圖,掩膜-無,灰度級,像素範圍 hist_full = cv2.calcHist([img],[0],None,[256],[0,256]) hist_mask = cv2.calcHist([img],[0],mask,[256],[0,256]) plt.subplot(221),plt.imshow(img,'gray') plt.subplot(222),plt.imshow(mask,'gray') plt.subplot(223),plt.imshow(masked_img,'gray') plt.subplot(224),plt.plot(hist_full),plt.plot(hist_mask)
九、形態學圖像處理
本節介紹如何使用morphology模塊實現二值圖像處理。二值圖像中的每一個像素的顏色只有兩種:黑色和白色,在NumPy中能夠用二維布爾數組表示:False表示黑色,True表示白色。也能夠用無符號單字節整型(uint8)數組表示:0表示黑色,非0表示白色。
下面的兩個函數用於顯示形態學圖像處理的結果。
import numpy as np def expand_image(img, value, out=None, size = 10): if out is None: w, h = img.shape out = np.zeros((w*size, h*size),dtype=np.uint8) tmp = np.repeat(np.repeat(img,size,0),size,1) out[:,:] = np.where(tmp, value, out) out[::size,:] = 0 out[:,::size] = 0 return out def show_image(*imgs): for idx, img in enumerate(imgs, 1): ax = plt.subplot(1, len(imgs), idx) plt.imshow(img, cmap="gray") ax.set_axis_off() plt.subplots_adjust(0.02, 0, 0.98, 1, 0.02, 0)
9.1 膨脹和腐蝕
二值圖像最基本的形態學運算是膨脹和腐蝕。膨脹運算是將與某物體(白色區域)接觸的全部背景像素(黑色區域)合併到該物體中的過程。簡單地說,就是對於原始圖像中的每一個白色像素進行處理,將其周圍的黑色像素都設置爲白色像素。這裏的「周圍」是一個模糊概念,在實際運算時,須要明確給出「周圍」的定義。下圖是膨脹運算的一個例子,其中左圖是原始圖像,中間的圖是四連通定義的「周圍」的膨脹效果,右圖是八連通定義的「周圍」的膨脹效果。圖中用灰色方塊表示由膨脹處理添加進物體的像素。
from scipy.ndimage import morphology def dilation_demo(a, structure=None): b = morphology.binary_dilation(a, structure) img = expand_image(a, 255) return expand_image(np.logical_xor(a,b), 150, out=img) a = plt.imread("images/scipy_morphology_demo.png")[:,:,0].astype(np.uint8) img1 = expand_image(a, 255) img2 = dilation_demo(a) img3 = dilation_demo(a, [[1,1,1],[1,1,1],[1,1,1]]) show_image(img1, img2, img3)
四連通包括上下左右四個像素,而八連通則還包括四個斜線方向上的鄰接像素。它們均可以使用下面的正方形矩陣定義,其中正中心的元素表示當前要進行運算的像素,而其周圍的1和0表示對應位置的像素是否算做其「周圍」像素。這種矩陣描述了周圍像素和當前像素之間的關係,被稱做結構元素(structuring element)。
假設數組a是一個表示二值圖像的數組,能夠用以下語句對其進行膨脹運算:
binary_dilation(a)
binary_dilation()缺省使用四連通進行膨脹運算,經過structure參數能夠指定其它的結構元素,下面是進行八連通膨脹運算的語句:
binary_dilation(a, structure=[[1,1,1],[1,1,1],[1,1,1]])
經過設置不一樣的結構元素,可以製做出各類不一樣的效果,下面顯示了三個種不一樣結構元素的膨脹效果。圖中的結構元素分別爲:
img4 = dilation_demo(a, [[0,0,0],[1,1,1],[0,0,0]]) img5 = dilation_demo(a, [[0,1,0],[0,1,0],[0,1,0]]) img6 = dilation_demo(a, [[0,1,0],[0,1,0],[0,0,0]]) show_image(img4, img5, img6)
binary_erosion()的腐蝕運算正好和膨脹相反,它將「周圍」有黑色像素的白色像素設置爲黑色。下面是四連通和八連通腐蝕的效果,圖中用灰色方塊表示被腐蝕的像素。
def erosion_demo(a, structure=None): b = morphology.binary_erosion(a, structure) img = expand_image(a, 255) return expand_image(np.logical_xor(a,b), 100, out=img) img1 = expand_image(a, 255) img2 = erosion_demo(a) img3 = erosion_demo(a, [[1,1,1],[1,1,1],[1,1,1]]) show_image(img1, img2, img3)
9.2 Hit和Miss
Hit和Miss是二值形態學圖像處理中最基本的運算,由於幾乎全部的其它的運算均可以用Hit和Miss的組合推演出來。它對圖像中的每一個像素周圍的像素進行模式判斷,若是周圍像素的黑白模式符合指定的模式,則將此像素設爲白色,不然設置爲黑色。由於它須要同時對白色和黑色像素進行判斷,所以須要指定兩個結構元素。進行Hit和Miss運算的binary_hit_or_miss()的調用形式以下:
binary_hit_or_miss(input, structure1=None, structure2=None, ...)
其中structure1參數指定白色像素的結構元素,而structure2參數則指定黑色像素的結構元素。下圖是binary_hit_or_miss()的運算結果。其中左圖爲原始圖像,中圖爲使用下面兩個結構元素進行運算的結果:
在這兩個結構元素中,0表示不關心其對應位置的像素的顏色,1表示其對應位置的像素必須爲結構元素所表示的顏色。所以經過這兩個結構元素能夠找到「下方三個像素爲白色,而且左上像素爲黑色的白色像素」。
與右圖對應的結構元素以下。經過它能夠找到「下方三個像素爲白色、左上像素爲黑色的黑色像素」。
def hitmiss_demo(a, structure1, structure2): b = morphology.binary_hit_or_miss(a, structure1, structure2) img = expand_image(a, 100) return expand_image(b, 255, out=img) img1 = expand_image(a, 255) img2 = hitmiss_demo(a, [[0,0,0],[0,1,0],[1,1,1]], [[1,0,0],[0,0,0],[0,0,0]]) img3 = hitmiss_demo(a, [[0,0,0],[0,0,0],[1,1,1]], [[1,0,0],[0,1,0],[0,0,0]]) show_image(img1, img2, img3)
使用Hit和Miss運算的組合,能夠實現很複雜的圖像處理。例如文字識別中經常使用的細線化運算就能夠用一系列的Hit和Miss運算實現。下圖顯示了細線化處理的效果。
def skeletonize(img): h1 = np.array([[0, 0, 0],[0, 1, 0],[1, 1, 1]]) m1 = np.array([[1, 1, 1],[0, 0, 0],[0, 0, 0]]) h2 = np.array([[0, 0, 0],[1, 1, 0],[0, 1, 0]]) m2 = np.array([[0, 1, 1],[0, 0, 1],[0, 0, 0]]) hit_list = [] miss_list = [] for k in range(4): hit_list.append(np.rot90(h1, k)) hit_list.append(np.rot90(h2, k)) miss_list.append(np.rot90(m1, k)) miss_list.append(np.rot90(m2, k)) img = img.copy() while True: last = img for hit, miss in zip(hit_list, miss_list): hm = morphology.binary_hit_or_miss(img, hit, miss) # 從圖像中刪除hit_or_miss所獲得的白色點 img = np.logical_and(img, np.logical_not(hm)) # 若是處理以後的圖像和處理前的圖像相同,則結束處理 if np.all(img == last): break return img a = plt.imread("images/scipy_morphology_demo2.png")[:,:,0].astype(np.uint8) b = skeletonize(a) _, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 3)) ax1.imshow(a, cmap="gray", interpolation="nearest") ax2.imshow(b, cmap="gray", interpolation="nearest") ax1.set_axis_off() ax2.set_axis_off() plt.subplots_adjust(0.02, 0, 0.98, 1, 0.02, 0)
細線化算法的實現程序以下,這裏只列出其中真正進行細線化算法的函數skeletonize():
根據上圖所示的兩個結構元素爲基礎,構造四個3*3的二維數組:h一、m一、h二、m2。其中h1和m1對應圖中左邊的結構元素,而h2和m2對應圖中右邊的結構元素,h1和h2對應白色結構元素,m1和m2對應黑色結構元素。將這些結構元素進行90、180、270度旋轉以後一共獲得8個結構元素。
依次使用這些結構元素進行Hit和Miss運算,並從圖像中刪除運算所獲得的白色像素,其效果就是依次從8個方向刪除圖像的邊緣上的像素。重複運算直到沒有像素可刪除爲止。