深度有趣:OpenCV 和 Torch 的结合

2016 年 6 月 1 日 作者:Egor Burkov

OpenCV 库实现了大量的有用图像处理和计算机视觉算法,以及高级 GUI API。它用 C++ 编写,并提供 Python、Java、MATLAB/Octave、C#、Perl 和 Ruby 的绑定。我们在这里介绍了基于 Torch 的 Lua 绑定,由 VisionLabs 团队开发。

通过将 OpenCV 与 Torch 的科学计算能力结合,我们获得了一个更强大的框架,它能够处理计算机视觉例程(例如人脸检测)、与视频流(包括摄像头)交互、更轻松地进行数据可视化、GUI 交互等等。此外,大多数计算密集型算法可以通过 cutorch 在 GPU 上运行。所有这些功能对于那些将深度学习应用于图像的人来说可能非常有用。

使用示例

实时图像分类

一个基本的示例可以是基于 CNN 的实时图像分类。在以下演示中,我们从网络摄像头获取一帧,然后从中截取一个中心区域,并使用一个小型的 ImageNet 分类预训练网络来预测图片中的内容。然后,会显示图像本身和五个最有可能的类别名称。

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 来进行年龄和性别预测,这些 CNN 在此处进行了描述,并经过训练,可以根据人脸预测年龄和性别。在这里,我们从实时流中获取一帧,然后使用流行的 Viola-Jones 级联目标检测器 来从中提取人脸。这类检测器与 OpenCV 一起提供,经过预训练,可以检测人脸、眼睛、微笑等。在找到人脸后,我们基本上将其裁剪并输入到 CNN 中,CNN 会输出年龄和性别预测数据。图像中的人脸用矩形标记,然后用预测的年龄和性别进行标注。

使用 Torch 从单个图像中检测人脸并绘制结果非常容易

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 上获取。以下是 Sergey Zagoruyko 的 IMAGINE Lab 提供的示例:

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 万个参数),这些模型基于 CVPR 2015 年论文 FaceNet:用于人脸识别的统一嵌入。它是在 FaceScrubCASIA-WebFace 人脸识别数据集的组合上进行预训练的。

screenshot
screenshot 1
screenshot 2
screenshot 3

让我们介绍一下这种情况下的 Lua OpenCV 接口。像往常一样,每个单独的 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

-- 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 团队创建和维护。我们感谢所有通过 PR 或帮助查找错误来为项目做出贡献的人。

评论由 Disqus 提供