使用 OpenCV 和火炬深度有趣

2016 年 6 月 1 日 撰寫:Egor Burkov

OpenCV 函式庫實作數十種有用的影像處理與電腦視覺演算法,以及高階 GUI API。用 C++ 編寫,在 Python、Java、MATLAB/Octave、C#、Perl 和 Ruby 中都可用。我們展示由 VisionLabs 製作的 Lua 繫結,這建構於火炬之上。

結合 OpenCV 與火炬的科學運算能力,取得更強大的架構,有能力處理電腦視覺常規(例如,人臉偵測),介面視訊串流(包含相機),更簡單的資料視覺化、GUI 互動等等。此外,大部分計算密集的演算法可在 GPU 上使用 cutorch。對那些運用深度學習到影像的人來說,所有這些功能基本上都很有用。

使用範例

即時影像分類

基本的範例可能是即時的基於 CNN 的影像分類。在以下的示範中,我們從網路攝影機擷取一個畫格,然後從中取一個中央裁切,並使用一個已訓練好的小型 ImageNet 分類網路來預測圖片的內容。之後,影像本身和 5 個最有可能的類別名稱會顯示出來。

ImageNet classification demo

註解應會清楚說明程式碼。注意:此範例假設您已經有訓練好的 CNN;請參閱 Sergey Zagoruyko 在 GitHub 上的原始程式碼,它會自動下載它。

local cv = require 'cv'
require 'cv.highgui' -- GUI
require 'cv.videoio' -- Video stream
require 'cv.imgproc' -- Image processing (resize, crop, draw text, ...)
require 'nn'

local capture = cv.VideoCapture{device=0}
if not capture:isOpened() then
   print("Failed to open the default camera")
   os.exit(-1)
end

-- Create a new window
cv.namedWindow{winname="Torch-OpenCV ImageNet classification demo", flags=cv.WINDOW_AUTOSIZE}
-- Read the first frame
local _, frame = capture:read{}

-- Using network in network http://openreview.net/document/9b05a3bb-3a5e-49cb-91f7-0f482af65aea
local net = torch.load('nin_nobn_final.t7'):unpack():float()
local synset_words = torch.load('synset.t7', 'ascii')

-- NiN input size
local M = 224

while true do
   local w = frame:size(2)
   local h = frame:size(1)

   -- Get central square crop
   local crop = cv.getRectSubPix{frame, patchSize={h,h}, center={w/2, h/2}}
   -- Resize it to 256 x 256
   local im = cv.resize{crop, {256,256}}:float():div(255)
   -- Subtract channel-wise mean
   for i=1,3 do
      im:select(3,i):add(-net.transform.mean[i]):div(net.transform.std[i])
   end
   -- Resize again to CNN input size and swap dimensions
   -- to CxHxW from HxWxC
   -- Note that BGR channel order required by ImageNet is already OpenCV's default
   local I = cv.resize{im, {M,M}}:permute(3,1,2):clone()

   -- Get class predictions
   local _, classes = net:forward(I):view(-1):float():sort(true)

   -- Caption the image
   for i=1,5 do
      cv.putText{
         crop,
         synset_words[classes[i]],
         {10, 10 + i * 25},
         fontFace=cv.FONT_HERSHEY_DUPLEX,
         fontScale=1,
         color={200, 200, 50},
         thickness=2
      }
   end

   -- Show it to the user
   cv.imshow{"Torch-OpenCV ImageNet classification demo", crop}
   if cv.waitKey{30} >= 0 then break end

   -- Grab the next frame
   capture:read{frame}
end

即時年齡和性別預測

一個更有趣的示範可以用 這裡描述 的 CNN 進行,訓練用臉來預測年齡和性別。在這裡,我們從即時串流取得一個畫格,然後使用熱門的 Viola-Jones cascade 物件偵測器 提取出臉來。此類偵測器與 OpenCV 配送,已訓練好以偵測臉部、眼睛、微笑等等。找到臉之後,我們基本上會裁切它們並輸入 CNN,利用它們產生年齡和性別的預測資料。影像中的臉會標記為矩形,然後標記為預測的年齡和性別。

對於單一影像,偵測臉部並為火炬繪製結果就是這麼簡單

require 'cv.objdetect'
local faceDetector = cv.CascadeClassifier{'haarcascade_frontalface_default.xml'}
local faces = faceDetector:detectMultiScale{image}

for i=1,faces.size do
   local f = faces.data[i]
   cv.rectangle{image, {f.x, f.y}, {f.x+f.w, f.y+f.h}, color={255,0,255,0}}
end

當然,這是一種非常沒有效率的臉部偵測方式,只提供一個簡單範例。例如,它可以包括追蹤技術。

完整的程式碼可以在 GitHub 上取得。這是 IMAGINE 實驗室 Sergey Zagoruyko 的範例

Age & Gender Demo

以下是一個有趣但體積較大的 GIF

And here is is a heavy just-for-fun GIF

NeuralTalk2

一個好的影像標題範例是 Andrej Karpathy 的 NeuralTalk2。透過 OpenCV,可以輕鬆讓這個模型為即時影片或相機串流加上標題

NeuralTalk2 Demo 1

NeuralTalk2 Demo 2

NeuralTalk2 Demo 3

腳本 可以在 NeuralTalk2 儲存庫本身找到。

使用 GPU 的互動式臉部辨識

已提到另一個 OpenCV 優點是 NVIDIA CUDA 支援。它可以協助您顯著加速影像處理和電腦視覺例程。這些包括影像處理特定矩陣運算、背景分割、影片 [en/de] 編碼/解碼、特徵偵測與描述、影像過濾、物件偵測、計算光流、立體對應等。

以下提供展示上述部分特性的另一個程式碼範例。這是一個互動式臉部辨識應用程式。啟動後,它會請使用者手動將出現在影片串流中的人分類至 N(也由使用者定義)個類別。當標籤臉部數量足夠能進行自動辨識時,會使用卷積神經網路臉部描述項萃取出判別描述項,並訓練 SVM 分類。接著程式會切換至辨識模式,並為串流中的「人名」預測標題。

為了速度,我們使用的臉部描述項是 OpenFace 模型中最輕量級的(約 370 萬參數),它們是根據 2015 年 CVPR 論文 FaceNet:統一的人臉辨識嵌入。它使用 FaceScrubCASIA-WebFace 臉部辨識資料集組合預先訓練。

screenshot
screenshot 1
screenshot 2
screenshot 3

讓我們介紹 OpenCV 針對 Lua 的介面在這種情況下的樣子。和往常一樣,各別 OpenCV 套件都有一個單一 require

local cv = require 'cv'
require 'cv.highgui'       -- GUI: windows, mouse
require 'cv.videoio'       -- VideoCapture
require 'cv.imgproc'       -- resize, rectangle, putText
require 'cv.cudaobjdetect' -- CascadeClassifier
require 'cv.cudawarping'   -- resize
require 'cv.cudaimgproc'   -- cvtColor
cv.ml = require 'cv.ml'    -- SVM

GUI 指令層級很高

-- create two windows
cv.namedWindow{'Stream window'}
cv.namedWindow{ 'Faces window'}
cv.setWindowTitle{'Faces window', 'Grabbed faces'}
cv.moveWindow{'Stream window', 5, 5}
cv.moveWindow{'Faces window', 700, 100}

local function onMouse(event, x, y, flags)
   if event == cv.EVENT_LBUTTONDBLCLK then
      -- do something
   end
end

cv.setMouseCallback{'Stream window', onMouse}

cv.imshow{'Stream window', frame}
cv.imshow{'Faces window', gallery}

以下是如何設定 SVM

-- SVM to classify descriptors in recognition phase
local svm = cv.ml.SVM{}
svm:setType         {cv.ml.SVM_C_SVC}
svm:setKernel       {cv.ml.SVM_LINEAR}
svm:setDegree       {1}
svm:setTermCriteria 

… 及使用它

-- svmDataX is a FloatTensor of size (#dataset x #features)
-- svmDataY is an IntTensor of labels of size (1 x #dataset)
svm:train{svmDataX, cv.ml.ROW_SAMPLE, svmDataY}

...

-- recognition phase
-- :predict() input is a FloatTensor of size (1 x #features)
local person = svm:predict{descriptor}

GPU 呼叫及其 CPU 類比看起來很像,有兩個不同處:第一,它們放置在一個單獨的表格中,第二,它們會處理 torch.CudaTensor。p>

-- convert to grayscale and store result in original image's blue (first) channel
cv.cuda.cvtColor{frameCUDA, frameCUDA:select(3,1), cv.COLOR_BGR2GRAY}

...

cv.cuda.resize{smallFaceCUDA, {netInputSize, netInputSize}, dst=netInputHWC}

請不要忘記,這些函式會適應於 Cutorch 串流和裝置設定,因此呼叫 cutorch.setStream()cutorch.streamWaitFor()cutorch.setDevice() 等有其重要性。

整個可跑的腳本 可以在這裡取得

即時影像樣式化

紋理網路:紋理和樣式化影像的正向合成 論文提出了一種以正向網路進行影像樣式化的架構,隨附的一個 Torch 開放原始碼實作。使用 Tesla K40 GPU 處理單一影像約需 20 毫秒,使用 CPU 約需 1000 毫秒。有了這個,一個小修改就能讓我們即時以特定樣式呈現任何場景

Demo 1

Demo 2

話說這些 GIF 動畫(起初是編碼過的影片)也是用 OpenCV 渲染的。有一個 VideoWriter 類別可以作為影片編解碼器的簡單介面。以下是一個範例程式,它將一系列類似的畫格編碼成一個影片檔案並儲存到磁碟上

local cv = require 'cv'
require 'cv.videoio' -- VideoWriter
require 'cv.imgproc' -- resize

local size = 256
local frameToSaveSize = {sz*2, sz}

local videoWriter = cv.VideoWriter{
   "sample.mp4",
   cv.VideoWriter.fourcc{'D', 'I', 'V', 'X'}, -- or any other codec
   fps = 25,
   frameSize = frameToSaveSize
}

if not videoWriter:isOpened() then
   print('Failed to initialize video writer. Possibly wrong codec or file name/path trouble')
   os.exit(-1)
end

for i = 1,numFrames do
   -- get next image; for example, read it from camera
   local frame = cv.resize{retrieveNextFrame(), {sz, sz}}

   -- the next frame in the resulting video
   local frameToSave = torch.Tensor(frameToSaveSize[2], frameToSaveSize[1], 3)

   -- first, copy the original frame into the left half of frameToSave:
   frameToSave:narrow(2, 1, sz):copy(frame)

   -- second, copy the processed (for example, rendered in painter style)
   -- frame into the other half:
   frameToSave:narrow(2, sz+1, sz):copy(someCoolProcessingFunction(frame))

   -- finally, tell videoWriter to push frameToSave into the video
   videoWriter:write{frameToSave}
end

這裡 提供完整的程式碼,包括以《星夜》訓練的模型。這個程式碼的一個版本在 http://likemo.net 執行

有了這些範例,我們展示了一些用 OpenCV+Torch7 可以做的事,並預計社群將出現更多棒的電腦視覺與深度學習應用程式和研究工具。

致謝

Sergey Zagoruyko 提供大部分範例程式碼,並建立範例螢幕截圖。
Soumith Chintala 在 Torch 這方面提供支援。
Dmitry Ulyanov 提供紋理網路的範例和程式碼

這個專案是由 VisionLabs 團隊建立與維護的。我們感謝每一位提供公關並協助捕捉錯誤的專案貢獻者。

留言由 Disqus 提供技術支援