From Here: https://zhuanlan.zhihu.com/p/24425116php
Python下使用OpenCVhtml
本篇將介紹和深度學習數據處理階段最相關的基礎使用,並完成4個有趣實用的小例子:python
- 延時攝影小程序git
- 視頻中截屏採樣的小程序github
- 圖片數據增長(data augmentation)的小工具算法
- 物體檢測框標註小工具編程
其中後兩個例子的代碼能夠在下面地址直接下載:canvas
frombeijingwithlove/dlcv_for_beginners小程序
OpenCV是計算機視覺領域應用最普遍的開源工具包,基於C/C++,支持Linux/Windows/MacOS/Android/iOS,並提供了Python,Matlab和Java等語言的接口,由於其豐富的接口,優秀的性能和商業友好的使用許可,無論是學術界仍是業界中都很是受歡迎。OpenCV最先源於Intel公司1998年的一個研究項目,當時在Intel從事計算機視覺的工程師蓋瑞·布拉德斯基(Gary Bradski)訪問一些大學和研究組時發現學生之間實現計算機視覺算法用的都是各自實驗室裏的內部代碼或者庫,這樣新來實驗室的學生就能基於前人寫的基本函數快速上手進行研究。因而OpenCV旨在提供一個用於計算機視覺的科研和商業應用的高性能通用庫。 第一個alpha版本的OpenCV於2000年的CVPR上發佈,在接下來的5年裏,又陸續發佈了5個beta版本,2006年發佈了第一個正式版。2009年隨着蓋瑞加入了Willow Garage,OpenCV從Willow Garage獲得了積極的支持,併發布了1.1版。2010年OpenCV發佈了2.0版本,添加了很是完備的C++接口,從2.0開始的版本很是用戶很是龐大,至今仍在維護和更新。2015年OpenCV 3正式發佈,除了架構的調整,還加入了更多算法,更多性能的優化和更加簡潔的API,另外也增強了對GPU的支持,如今已經在許多研究機構和商業公司中應用開來。數組
和Python同樣,當前的OpenCV也有兩個大版本,OpenCV2和OpenCV3。相比OpenCV2,OpenCV3提供了更強的功能和更多方便的特性。不過考慮到和深度學習框架的兼容性,以及上手安裝的難度,這部分先以2爲主進行介紹。
根據功能和需求的不一樣,OpenCV中的函數接口大致能夠分爲以下部分:
- core:核心模塊,主要包含了OpenCV中最基本的結構(矩陣,點線和形狀等),以及相關的基礎運算/操做。
- imgproc:圖像處理模塊,包含和圖像相關的基礎功能(濾波,梯度,改變大小等),以及一些衍生的高級功能(圖像分割,直方圖,形態分析和邊緣/直線提取等)。
- highgui:提供了用戶界面和文件讀取的基本函數,好比圖像顯示窗口的生成和控制,圖像/視頻文件的IO等。
若是不考慮視頻應用,以上三個就是最核心和經常使用的模塊了。針對視頻和一些特別的視覺應用,OpenCV也提供了強勁的支持:
- video:用於視頻分析的經常使用功能,好比光流法(Optical Flow)和目標跟蹤等。
- calib3d:三維重建,立體視覺和相機標定等的相關功能。
- features2d:二維特徵相關的功能,主要是一些不受專利保護的,商業友好的特徵點檢測和匹配等功能,好比ORB特徵。
- object:目標檢測模塊,包含級聯分類和Latent SVM
- ml:機器學習算法模塊,包含一些視覺中最經常使用的傳統機器學習算法。
- flann:最近鄰算法庫,Fast Library for Approximate Nearest Neighbors,用於在多維空間進行聚類和檢索,常常和關鍵點匹配搭配使用。
- gpu:包含了一些gpu加速的接口,底層的加速是CUDA實現。
- photo:計算攝像學(Computational Photography)相關的接口,固然這只是個名字,其實只有圖像修復和降噪而已。
- stitching:圖像拼接模塊,有了它能夠本身生成全景照片。
- nonfree:受到專利保護的一些算法,其實就是SIFT和SURF。
- contrib:一些實驗性質的算法,考慮在將來版本中加入的。
- legacy:字面是遺產,意思就是廢棄的一些接口,保留是考慮到向下兼容。
- ocl:利用OpenCL並行加速的一些接口。
- superres:超分辨率模塊,其實就是BTV-L1(Biliteral Total Variation – L1 regularization)算法
- viz:基礎的3D渲染模塊,其實底層就是著名的3D工具包VTK(Visualization Toolkit)。
從使用的角度來看,和OpenCV2相比,OpenCV3的主要變化是更多的功能和更細化的模塊劃分。
做爲最流行的視覺包,在Linux中安裝OpenCV是很是方便的,大多數Linux的發行版都支持包管理器的安裝,好比在Ubuntu 16.04 LTS中,只須要在終端中輸入:
>> sudo apt install libopencv-dev python-opencv
固然也能夠經過官網下載源碼編譯安裝,第一步先安裝各類依賴:
>> sudo apt install build-essential
>> sudo apt install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
>> sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
而後找一個clone壓縮包的文件夾,把源碼拿下來:
>> git clone opencv/opencv
而後進入OpenCV文件夾:
>> mkdir release
>> cd release
>> cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..
準備完畢,直接make並安裝:
>> make
>> sudo make install
Windows下的安裝也很簡單,直接去OpenCV官網下載:
執行exe安裝後,會在<安裝目錄>/build/python/2.7下發現一個叫cv2.pyd的文件,把這個文件拷貝到<Python目錄>\Lib\site-packages下,就能夠了。Windows下若是隻想在Python中體驗OpenCV還有個更簡單的方法是加州大學爾灣分校(University of California, Irvine)的Christoph Gohlke製做的Windows下的Python科學計算包網頁,下載對應版本的wheel文件,而後經過pip安裝:
http://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv
本書只講Python下OpenCV基本使用,Python中導入OpenCV很是簡單:
import cv2
就導入成功了。
前面章節已經提到過了單通道的灰度圖像在計算機中的表示,就是一個8位無符號整形的矩陣。在OpenCV的C++代碼中,表示圖像有個專門的結構叫作cv::Mat,不過在Python-OpenCV中,由於已經有了numpy這種強大的基礎工具,因此這個矩陣就用numpy的array表示。若是是多通道狀況,最多見的就是紅綠藍(RGB)三通道,則第一個維度是高度,第二個維度是高度,第三個維度是通道,好比圖6-1a是一幅3×3圖像在計算機中表示的例子:
圖6-1 RGB圖像在計算機中表示的例子
圖6-1中,右上角的矩陣裏每一個元素都是一個3維數組,分別表明這個像素上的三個通道的值。最多見的RGB通道中,第一個元素就是紅色(Red)的值,第二個元素是綠色(Green)的值,第三個元素是藍色(Blue),最終獲得的圖像如6-1a所示。RGB是最多見的狀況,然而在OpenCV中,默認的圖像的表示確實反過來的,也就是BGR,獲得的圖像是6-1b。能夠看到,前兩行的顏色順序都交換了,最後一行是三個通道等值的灰度圖,因此沒有影響。至於OpenCV爲何不是人民羣衆喜聞樂見的RGB,這是歷史遺留問題,在OpenCV剛開始研發的年代,BGR是相機設備廠商的主流表示方法,雖而後來RGB成了主流和默認,可是這個底層的順序卻保留下來了,事實上Windows下的最多見格式之一bmp,底層字節的存儲順序仍是BGR。OpenCV的這個特殊之處仍是須要注意的,好比在Python中,圖像都是用numpy的array表示,可是一樣的array在OpenCV中的顯示效果和matplotlib中的顯示效果就會不同。下面的簡單代碼就能夠生成兩種表示方式下,圖6-1中矩陣的對應的圖像,生成圖像後,放大看就能體會到區別:
import numpy as np import cv2 import matplotlib.pyplot as plt # 圖6-1中的矩陣 img = np.array([ [[255, 0, 0], [0, 255, 0], [0, 0, 255]], [[255, 255, 0], [255, 0, 255], [0, 255, 255]], [[255, 255, 255], [128, 128, 128], [0, 0, 0]], ], dtype=np.uint8) # 用matplotlib存儲 plt.imsave('img_pyplot.jpg', img) # 用OpenCV存儲 cv2.imwrite('img_cv2.jpg', img)
無論是RGB仍是BGR,都是高度×寬度×通道數,H×W×C的表達方式,而在深度學習中,由於要對不一樣通道應用卷積,因此用的是另外一種方式:C×H×W,就是把每一個通道都單獨表達成一個二維矩陣,如圖6-1c所示。
存取圖像
讀圖像用cv2.imread(),能夠按照不一樣模式讀取,通常最經常使用到的是讀取單通道灰度圖,或者直接默認讀取多通道。存圖像用cv2.imwrite(),注意存的時候是沒有單通道這一說的,根據保存文件名的後綴和當前的array維度,OpenCV自動判斷存的通道,另外壓縮格式還能夠指定存儲質量,來看代碼例子:
import cv2 # 讀取一張400x600分辨率的圖像 color_img = cv2.imread('test_400x600.jpg') print(color_img.shape) # 直接讀取單通道 gray_img = cv2.imread('test_400x600.jpg', cv2.IMREAD_GRAYSCALE) print(gray_img.shape) # 把單通道圖片保存後,再讀取,仍然是3通道,至關於把單通道值複製到3個通道保存 cv2.imwrite('test_grayscale.jpg', gray_img) reload_grayscale = cv2.imread('test_grayscale.jpg') print(reload_grayscale.shape) # cv2.IMWRITE_JPEG_QUALITY指定jpg質量,範圍0到100,默認95,越高畫質越好,文件越大 cv2.imwrite('test_imwrite.jpg', color_img, (cv2.IMWRITE_JPEG_QUALITY, 80)) # cv2.IMWRITE_PNG_COMPRESSION指定png質量,範圍0到9,默認3,越高文件越小,畫質越差 cv2.imwrite('test_imwrite.png', color_img, (cv2.IMWRITE_PNG_COMPRESSION, 5))
縮放,裁剪和補邊
縮放經過cv2.resize()實現,裁剪則是利用array自身的下標截取實現,此外OpenCV還能夠給圖像補邊,這樣能對一幅圖像的形狀和感興趣區域實現各類操做。下面的例子中讀取一幅400×600分辨率的圖片,並執行一些基礎的操做:
import cv2 # 讀取一張四川大錄古藏寨的照片 img = cv2.imread('tiger_tibet_village.jpg') # 縮放成200x200的方形圖像 img_200x200 = cv2.resize(img, (200, 200)) # 不直接指定縮放後大小,經過fx和fy指定縮放比例,0.5則長寬都爲原來一半 # 等效於img_200x300 = cv2.resize(img, (300, 200)),注意指定大小的格式是(寬度,高度) # 插值方法默認是cv2.INTER_LINEAR,這裏指定爲最近鄰插值 img_200x300 = cv2.resize(img, (0, 0), fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST) # 在上張圖片的基礎上,上下各貼50像素的黑邊,生成300x300的圖像 img_300x300 = cv2.copyMakeBorder(img, 50, 50, 0, 0, cv2.BORDER_CONSTANT, value=(0, 0, 0)) # 對照片中樹的部分進行剪裁 patch_tree = img[20:150, -180:-50] cv2.imwrite('cropped_tree.jpg', patch_tree) cv2.imwrite('resized_200x200.jpg', img_200x200) cv2.imwrite('resized_200x300.jpg', img_200x300) cv2.imwrite('bordered_300x300.jpg', img_300x300)
這些處理的效果見圖6-2。
色調,明暗,直方圖和Gamma曲線
除了區域,圖像自己的屬性操做也很是多,好比能夠經過HSV空間對色調和明暗進行調節。HSV空間是由美國的圖形學專家A. R. Smith提出的一種顏色空間,HSV分別是色調(Hue),飽和度(Saturation)和明度(Value)。在HSV空間中進行調節就避免了直接在RGB空間中調節是還須要考慮三個通道的相關性。OpenCV中H的取值是[0, 180),其餘兩個通道的取值都是[0, 256),下面例子接着上面例子代碼,經過HSV空間對圖像進行調整:
# 經過cv2.cvtColor把圖像從BGR轉換到HSV img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # H空間中,綠色比黃色的值高一點,因此給每一個像素+15,黃色的樹葉就會變綠 turn_green_hsv = img_hsv.copy() turn_green_hsv[:, :, 0] = (turn_green_hsv[:, :, 0]+15) % 180 turn_green_img = cv2.cvtColor(turn_green_hsv, cv2.COLOR_HSV2BGR) cv2.imwrite('turn_green.jpg', turn_green_img) # 減少飽和度會讓圖像損失鮮豔,變得更灰 colorless_hsv = img_hsv.copy() colorless_hsv[:, :, 1] = 0.5 * colorless_hsv[:, :, 1] colorless_img = cv2.cvtColor(colorless_hsv, cv2.COLOR_HSV2BGR) cv2.imwrite('colorless.jpg', colorless_img) # 減少明度爲原來一半 darker_hsv = img_hsv.copy() darker_hsv[:, :, 2] = 0.5 * darker_hsv[:, :, 2] darker_img = cv2.cvtColor(darker_hsv, cv2.COLOR_HSV2BGR) cv2.imwrite('darker.jpg', darker_img)
不管是HSV仍是RGB,咱們都較難一眼就對像素中值的分佈有細緻的瞭解,這時候就須要直方圖。若是直方圖中的成分過於靠近0或者255,可能就出現了暗部細節不足或者亮部細節丟失的狀況。好比圖6-2中,背景裏的暗部細節是很是弱的。這個時候,一個經常使用方法是考慮用Gamma變換來提高暗部細節。Gamma變換是矯正相機直接成像和人眼感覺圖像差異的一種經常使用手段,簡單來講就是經過非線性變換讓圖像從對曝光強度的線性響應變得更接近人眼感覺到的響應。具體的定義和實現,仍是接着上面代碼中讀取的圖片,執行計算直方圖和Gamma變換的代碼以下:
import numpy as np # 分通道計算每一個通道的直方圖 hist_b = cv2.calcHist([img], [0], None, [256], [0, 256]) hist_g = cv2.calcHist([img], [1], None, [256], [0, 256]) hist_r = cv2.calcHist([img], [2], None, [256], [0, 256]) # 定義Gamma矯正的函數 def gamma_trans(img, gamma): # 具體作法是先歸一化到1,而後gamma做爲指數值求出新的像素值再還原 gamma_table = [np.power(x/255.0, gamma)*255.0 for x in range(256)] gamma_table = np.round(np.array(gamma_table)).astype(np.uint8) # 實現這個映射用的是OpenCV的查表函數 return cv2.LUT(img, gamma_table) # 執行Gamma矯正,小於1的值讓暗部細節大量提高,同時亮部細節少許提高 img_corrected = gamma_trans(img, 0.5) cv2.imwrite('gamma_corrected.jpg', img_corrected) # 分通道計算Gamma矯正後的直方圖 hist_b_corrected = cv2.calcHist([img_corrected], [0], None, [256], [0, 256]) hist_g_corrected = cv2.calcHist([img_corrected], [1], None, [256], [0, 256]) hist_r_corrected = cv2.calcHist([img_corrected], [2], None, [256], [0, 256]) # 將直方圖進行可視化 import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() pix_hists = [ [hist_b, hist_g, hist_r], [hist_b_corrected, hist_g_corrected, hist_r_corrected] ] pix_vals = range(256)