Iniciando com OpenCV nos processadores i.MX 6

quarta-feira, 22 de novembro de 2017
ColibriApalis
Introduction

Como diz o ditado, uma imagem vale mais que mil palavras. De fato, isso é verdade até certo ponto. Uma imagem pode conter informações sobre objetos, textos, pessoas, idades, situações, dentre outros. Isso também pode ser estendido à videos, que podem ser interpretados como uma série de imagens.

Esta pode ser uma boa dica de porquê a visão computacional (CV, de Computer Vision) tem sido um campo de estudo que tem seus horizontes sendo expandidos todos os dias. Então nos vem a questão: O que é visão computacional? Ela é a habilidade de extrair informações de uma imagem, ou uma série de imagens. Ela não deve ser confundida com aquisição de imagens digitais nem com processamento de imagens, que são respectivamente a produção de uma imagem de entrada e uma aplicação de operações matemáticas para imagens. Entretanto, ambas são necessárias para tornar a CV possível.

Embora algumas tarefas sejam triviais para seres-humanos, como ler ou reconhecer pessoas, isso nem sempre é verdade quando estamos falando sobre computadores interpretando imagens. Entretanto, nos dias de hoje existem diversas aplicações que já são bem conhecidas e utilizadas no dia a dia, como por exemplo detecção de faces em câmeras digitais, detecção de caracteres ópticos em scanners de livros (OCR) e leitura de placas dos veículos no trânsito. Estes são campos que não estavam nem perto de existir há 15 anos. Carros autônomos que vão de ambientes controladores para as ruas são uma boa medida do quão avançada está essa tecnologia, e uma das ferramentas que torna CV possível é o avanço do poder computacional em hardwares menores.

Assim, este blog é uma introdução de como utilizar a visão computacional em sistemas embarcados, utilizando as versões 2.4 e 3.1 do OpenCV em computadores em módulos (CoMs) equipados com o processador NXP i.MX 6. Os CoMs que escolhemos neste artigo foram das famílias Colibri e Apalis da Toradex.

OpenCV significa Open Source Computer Vision Library, um conjunto de bibliotecas que contém centenas de algoritmos relacionados a visão computacional. O OpenCV tem uma estrutura modular, dividida em uma biblioteca principal (core) e várias outras como um módulo de processamento de imagens, um módulo de análise de vídeo, um módulo de interface com o usuário, entre outros.

Considerations about OpenCV, i.MX 6 processors and the Toradex modules

O OpenCV é um conjunto de bibliotecas que computa operações matemáticas na CPU por padrão. Ele tem suporte para processamento multicore ao usar algumas bibliotecas externas como o OpenMP (Open Multi-processing) e o TBB (Threading Building Blocks). Este blog não irá se aprofundar na comparação entre as bibliotecas disponíveis, mas o desempenho de algumas aplicações provavelmente irá diferir entre testes com diferentes bibliotecas.

Falando um pouco sobre o suporte ao coprocessador de ponto flutuante NEON, a versão 3.0 do OpenCV menciona que aproximadamente 40 funções foram aceleradas e um novo HAL (de Hardware Abstraction Layer ou Camada de Abstração de Hardware) provê um fácil meio para criar um código otimizado com NEON, o que é um bom caminho para aprimorar o desempenho na maioria dos sistemas embarcados Arm. Neste artigo não iremos nos aprofundar neste assunto, mas caso você tenha interesse em ir mais fundo nisso, uma dica é dar uma olhada no código fonte do OpenCV (1, 2).

Esse post apresentará como usar o OpenCV 2.4 e também o OpenCV 3.1 - isso foi decidido porque pode haver usuários com aplicações legadas que necessitam da versão mais antiga. É, também, uma boa oportunidade para comparar o desempenho entre as versões e ter uma noção sobre os ganhos de otimização com NEON.

O SoC i.MX 6 Solo/DualLite tem uma GPU gráfica (GC880) com suporte a OpenGL ES, enquanto o SoC i.MX6 Dual/Quad, tem uma GPU gráfica 3D (GC2000) que suporta OpenGL ES e também OpenCL Embedded Profile, mas não o Full Profile. O i.MX 6 também tem uma GPU 2D (GC320), uma IPU, e para a versão Dual/Quad, uma GPU de vetores (GC335), mas neste blog nós não iremos discutir sobre a possibilidade de utilizar esses hardwares com o OpenCV – é suficiente dizer que o código fonte do OpenCV não os suporta por padrão. Assim, seria necessária uma quantidade considerável de trabalho para aproveitar os hardwares específicos do i.MX 6.

Enquanto o OpenCL é uma linguagem de programação de propósitos gerais para GPU, seu uso não é o objetivo deste blog. O OpenGL é uma API para renderização de gráficos 2D e 3D na GPU, e portanto, não tem como objetivo principal ser utilizado para propósitos gerais de computação. Entretanto, alguns experimentos (1) demonstraram que é possível utilizar o OpenGL ES para propósitos gerais em processamento de imagens, existindo até uma nota de aplicação feita pela NXP para aqueles que se interessarem. Se você tem interesse em usar a aceleração de GPU no OpenCV out-of-the-box, a Toradex tem um módulo que suporta CUDA – o Apalis TK1. Veja este blog post para mais detalhes.

Apesar de todas os recursos de hardware disponíveis e possivelmente utilizáveis para obter desempenho, de acordo com esta apresentação, a otimização do código-fonte do OpenCV, focando apenas em software e no coprocessador NEON, poderia proporcionar uma melhoria de desempenho em 2-3x para o algoritmo e, com outras otimizações com NEON, poderia alcançar uma melhoria de 3-4x.

As imagens 1 e 2 apresentam, respectivamente, o módulo Colibri iMX6DL + Colibri Evaluation Board e o Apalis iMX6Q + Apalis Evaluation Board, ambos contendo os cabos da fonte, UART de debug, ethernet, câmera USB e monitor VGA conectados.

Colibri iMX6DL Colibri Evaluation Board setup
Colibri iMX6DL Colibri Evaluation Board setup
Apalis iMX6Q Apalis Evaluation Board setup
Apalis iMX6Q Apalis Evaluation Board setup

É importante mencionar que diferentes modelos de câmera USB podem apresentar diferentes desempenhos, e a câmera utilizada neste blog post é uma câmera genérica que encontramos – o driver foi listado como “Aveo Technology Corp”. No mercado também existem câmeras profissionais, USB ou não, como por exemplo as soluções providenciadas pela Basler AG, que tem como objetivo serem utilizadas em soluções embarcadas quando se é necessário desenvolver uma solução no mundo real.

Professional camera from Basler AG
Câmera profissional da Basler AG

Ainda, a Toradex disponibiliza o CSI Camera Module 5MP OV5640. É um periférico feito para a família Apalis que utiliza a interface MIPI-CSI. Ela contém o sensor de câmera OmniVision OV5640 com auto foco embutido. O OV5640 é um sensor de imagem CMOS de 1/4 polegadas e 5 megapixels de baixa tensão e alto desempenho que provê a funcionalidade total de uma única câmera de 5 megapixels (2592x1944). O CSI Camera Module 5MP OV5640 pode ser conectado ao conector MIPI-CSI presente na Ixora Carrier Board V1,1 utilizando um cabo FFC de 24 vias com passo de 0.5mm.

Toradex CSI Camera Module 5MP OV5640
Toradex CSI Camera Module 5MP OV5640

No final deste blog post, é fornecido um resumo das instruções de como instalar o OpenCV e como implementar aplicações no target.

Construindo uma imagem de Linux com o OpenCV

Imagens com o OpenCV podem ser criadas utilizando a ferramenta OpenEmbedded. Todas as instruções que iremos executar a seguir estão neste artigo. O primeiro passo é instalar os requisitos para o OpenEmbedded. Abaixo é dado um exemplo para o Ubuntu 16.04 – para outras versões do Ubuntu ou Fedora, por favor consulte ao artigo mencionado anteriormente:

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install g++-5-multilib
sudo apt-get install curl dosfstools gawk g++-multilib gcc-multilib lib32z1-dev libcrypto++9v5:i386 libcrypto++-dev:i386 liblzo2-dev:i386 libstdc++-5-dev:i386 libusb-1.0-0:i386 libusb-1.0-0-dev:i386 uuid-dev:i386
cd /usr/lib; sudo ln -s libcrypto++.so.9.0.0 libcryptopp.so.6

Também é necessário instalar a ferramenta repo para buscar os diversos repositórios utilizados para construir a imagem:

mkdir ~/bin
export PATH=~/bin:$PATH
curl http://commondatastorage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo

Vamos realizar o build da imagens com o OpenCV 2.4 e o 3.1 em diretórios diferentes. Alguns passos podem ser omitidos caso você tenha interesse em apenas uma versão. Também será criado um diretório para compartilhar o conteúdo baixado pelo OpenEmbedded.

cd
mkdir oe-core-opencv2.4 oe-core-opencv3.1 oe-core-downloads
cd oe-core-opencv2.4
repo init -u http://git.toradex.com/toradex-bsp-platform.git -b LinuxImageV2.6.1
repo sync
cd ../oe-core-opencv3.1
repo init -u http://git.toradex.com/toradex-bsp-platform.git -b LinuxImageV2.7
repo sync
OpenCV 2.4

O OpenCV 2.4 será incluído na versão de imagem 2.6.1, pule essa seção caso queira utilizar a versão 3.1 do OpenCV.

A receita incluída por padrão usa a versão 2.4.11 que não tem suporte ao processamento multicore por padrão.

Remova o append presente na meta-fsl-arm e o append presente na meta-toradex-demos:

rm layers/meta-fsl-arm/openembedded-layer/recipes-support/opencv/opencv_2.4.bbappend
rm layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend

Vamos criar um append para utilizar a versão 2.4.13.2 e adicionar o TBB para termos a vantagem dos múltiplos núcleos.

Acesse o diretório oe-core-opencv2.4:

cd oe-core-opencv2.4

A receita deve ser criada na camada meta-toradex-demos (layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend) com o seguinte conteúdo:

gedit layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend
-------------------------------------------------------------------------------------
SRCREV = "d7504ecaed716172806d932f91b65e2ef9bc9990"
SRC_URI = "git://github.com/opencv/opencv.git;branch=2.4"
 
PV = "2.4.13.2+git${SRCPV}"
 
PACKAGECONFIG += " tbb"
PACKAGECONFIG[tbb] = "-DWITH_TBB=ON,-DWITH_TBB=OFF,tbb,"

Alternativamente, podemos também utilizar o OpenMP no lugar do TBB:

gedit layers/meta-toradex-demos/recipes-support/opencv/opencv_2.4.bbappend
-------------------------------------------------------------------------------------
SRCREV = "d7504ecaed716172806d932f91b65e2ef9bc9990"
SRC_URI = "git://github.com/opencv/opencv.git;branch=2.4"
 
PV = "2.4.13.2+git${SRCPV}"
 
EXTRA_OECMAKE += " -DWITH_OPENMP=ON"

Configure o ambiente em seu computador ao executar o comando abaixo dentro do diretório oe-core-opencv2.4:

. export

Você automaticamente irá entrar em um diretório chamado build, o próximo passo é editar o arquivo conf/local.conf adicionando as variáveis abaixo:

gedit conf/local.conf
-------------------------------------------------------------------------------
MACHINE ?= "apalis-imx6" # or colibri-imx6 depending on the CoM you have
 
# Use the previously created folder for shared downloads, e.g.
DL_DIR ?= "/home/user/oe-core-downloads"
 
ACCEPT_FSL_EULA = "1"
 
# libgomp is optional if you use TBB
IMAGE_INSTALL_append = " opencv opencv-samples libgomp"

Agora, você pode realizar o primeiro build da imagem. Esse processo irá demorar algumas horas. Execute o comando abaixo:

bitbake -k angstrom-lxde-image
OpenCV 3.1

O OpenCV 3.1 será incluído na versão de imagem 2.7.

Nesta receita o TBB já vem incluído por padrão, entretanto, uma flag de compilação deve ser adicionada para que o processo não falhe. Acesse o diretório oe-core-opencv3.1:

cd oe-core-opencv3.1

Vamos criar um append adicionando essa flag na camada meta-openembedded (layers/meta-openembedded/meta-oe/recipes-support/opencv/opencv_3.1.bb) com o seguinte conteúdo:

gedit  layers/meta-toradex-demos/recipes-support/opencv/opencv_3.1.bbappend
-------------------------------------------------------------------------------------
CXXFLAGS += " -Wa,-mimplicit-it=thumb"

Alternativamente, podemos também utilizar o OpenMP no lugar do TBB:

gedit  layers/meta-toradex-demos/recipes-support/opencv/opencv_3.1.bbappend
-------------------------------------------------------------------------------------
CXXFLAGS_armv7a += " -Wa,-mimplicit-it=thumb"
PACKAGECONFIG_remove = "tbb"
EXTRA_OECMAKE += " -DWITH_OPENMP=ON"

Configure o ambiente em seu computador ao executar o comando abaixo dentro do diretório oe-core-opencv3.1:

. export

Você automaticamente irá entrar em um diretório chamado build. O próximo passo é editar o arquivo conf/local.conf adicionando as variáveis abaixo:

gedit conf/local.conf
-------------------------------------------------------------------------------
MACHINE ?= "apalis-imx6" # or colibri-imx6 depending on the CoM you have
 
# Use the previously created folder for shared downloads, e.g.
DL_DIR ?= "/home/user/oe-core-downloads"
 
ACCEPT_FSL_EULA = "1"
 
# libgomp is optional if you use TBB
IMAGE_INSTALL_append = " opencv libgomp"

Agora, você pode realizar o primeiro build da imagem. Esse processo irá demorar algumas horas. Para isso, execute o comando abaixo:

bitbake -k angstrom-lxde-image
Atualizando o módulo

Após o término do build, você poderá encontrar os arquivos gerados no diretório oe-core-opencv<versão>/deploy/images/<nome_do_módulo> com o nome <nome_do_módulo>_LXDE-Image-Tezi_2.8b6-<data>.tar. Siga as instruções presentes neste artigo para atualizar seu módulo.

Gerando a SDK

Para gerar a SDK que será utilizada para cross-compilar as aplicações, execute o seguinte comando:

bitbake -c populate_sdk angstrom-lxde-image

Após o término do processo, você irá encontrar a SDK no diretório /deploy/sdk, execute o script para instalar e mantenha o caminho de instalação padrão:

./angstrom-glibc-x86_64-armv7at2hf-vfp-neon-v2015.12-toolchain.sh
 
Angstrom SDK installer version nodistro.0
=========================================
Enter target directory for SDK (default: /usr/local/oecore-x86_64):

Nos próximos passos, serão assumidos os seguintes diretórios para as SDKs:

Para OpenCV 2.4: /usr/local/oecore-opencv2_4
Para OpenCV 3.1: /usr/local/oecore-opencv3_1

Preparando para a compilação cruzada

Tendo a SDK instalada já é possível utilizá-la para compilar nossa aplicação. Para isso, vamos utilizar alguns Makefiles, portanto será necessário instalar o CMake:

sudo apt-get install cmake

Crie um arquivo chamado CMakeLists.txt dentro da pasta do seu projeto com o conteúdo abaixo. Tenha certeza de que o caminho para a toolchain esteja correto e de que o nome do sysroot dentro da pasta da SDK é o mesmo que no script (e.g. armv7at2hf-neon-angstrom-linux-gnueabi)

cd ~
mkdir my_project
gedit CMakeLists.txt
--------------------------------------------------------------------------------
cmake_minimum_required(VERSION 2.8)
project( MyProject )
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
add_executable( myApp src/myApp.cpp )

if(OCVV EQUAL 2_4)
    message(STATUS "OpenCV version required: ${OCVV}")
    SET(CMAKE_PREFIX_PATH /usr/local/oecore-opencv${OCVV}/sysroots/armv7at2hf-vfp-neon-angstrom-linux-gnueabi)
elseif(OCVV EQUAL 3_1)
    message(STATUS "OpenCV version required: ${OCVV}")
    SET(CMAKE_PREFIX_PATH /usr/local/oecore-opencv${OCVV}/sysroots/armv7at2hf-neon-angstrom-linux-gnueabi)
else()
    message(FATAL_ERROR "OpenCV version needs to be passed. Make sure it matches your SDK version. Use -DOCVV=<version>, currently supported 2_4 and 3_1. E.g. -DOCVV=3_1")
endif()
SET(OpenCV_DIR ${CMAKE_PREFIX_PATH}/usr/lib/cmake/OpenCV)

find_package( OpenCV REQUIRED )
include_directories( ${OPENCV_INCLUDE_DIRS} )
target_link_libraries( myApp ${OPENCV_LIBRARIES} )

Também é necessário ter um arquivo CMake para dizer quais as bibliotecas serão utilizadas. Para isso, iremos criar arquivos CMake dentro do diretório sysroot de cada SDK. Vamos primeiro criar para a versão 2.4

cd /usr/local/oecore-opencv2_4/sysroots/armv7at2hf-vfp-neon-angstrom-linux-gnueabi/usr/lib/cmake
mkdir OpenCV
gedit OpenCV/OpenCVConfig.cmake
-----------------------------------------------------------------------------------
set(OPENCV_FOUND TRUE)
 
get_filename_component(_opencv_rootdir ${CMAKE_CURRENT_LIST_DIR}/../../../ ABSOLUTE)
 
set(OPENCV_VERSION_MAJOR 2)
set(OPENCV_VERSION_MINOR 4)
set(OPENCV_VERSION 2.4)
set(OPENCV_VERSION_STRING "2.4")
 
set(OPENCV_INCLUDE_DIR ${_opencv_rootdir}/include)
set(OPENCV_LIBRARY_DIR    ${_opencv_rootdir}/lib)
set(OPENCV_LIBRARY      -L${OPENCV_LIBRARY_DIR} -lopencv_calib3d -lopencv_contrib -lopencv_core
-lopencv_features2d -lopencv_flann -lopencv_gpu -lopencv_highgui -lopencv_imgproc
-lopencv_legacy -lopencv_ml -lopencv_nonfree -lopencv_objdetect -lopencv_ocl
-lopencv_photo -lopencv_stitching -lopencv_superres -lopencv_video -lopencv_videostab)
 
if(OPENCV_FOUND)
set( OPENCV_LIBRARIES ${OPENCV_LIBRARY} )
set( OPENCV_INCLUDE_DIRS ${OPENCV_INCLUDE_DIR} )
endif()
 
mark_as_advanced(OPENCV_INCLUDE_DIRS OPENCV_LIBRARIES)

O mesmo deve ser feito para a versão 3.1. Note que as bibliotecas mudam da versão 2 para a 3:

cd /usr/local/oecore-opencv3_1/sysroots/armv7at2hf-vfp-neon-angstrom-linux-gnueabi/usr/lib/cmake
mkdir OpenCV
gedit OpenCV/OpenCVConfig.cmake
-----------------------------------------------------------------------------------
set(OPENCV_FOUND TRUE)
 
get_filename_component(_opencv_rootdir ${CMAKE_CURRENT_LIST_DIR}/../../../ ABSOLUTE)
 
set(OPENCV_VERSION_MAJOR 3)
set(OPENCV_VERSION_MINOR 1)
set(OPENCV_VERSION 3.1)
set(OPENCV_VERSION_STRING "3.1")
 
set(OPENCV_INCLUDE_DIR ${_opencv_rootdir}/include)
set(OPENCV_LIBRARY_DIR    ${_opencv_rootdir}/lib)
set(OPENCV_LIBRARY      -L${OPENCV_LIBRARY_DIR} -lopencv_aruco -lopencv_bgsegm
-lopencv_bioinspired -lopencv_calib3d -lopencv_ccalib -lopencv_core
-lopencv_datasets -lopencv_dnn -lopencv_dpm -lopencv_face -lopencv_features2d
-lopencv_flann -lopencv_fuzzy -lopencv_highgui -lopencv_imgcodecs
-lopencv_imgproc -lopencv_line_descriptor -lopencv_ml -lopencv_objdetect
-lopencv_optflow -lopencv_photo -lopencv_plot -lopencv_reg -lopencv_rgbd
-lopencv_saliency -lopencv_shape -lopencv_stereo -lopencv_stitching
-lopencv_structured_light -lopencv_superres -lopencv_surface_matching
-lopencv_text -lopencv_tracking -lopencv_videoio -lopencv_video
-lopencv_videostab -lopencv_xfeatures2d -lopencv_ximgproc -lopencv_xobjdetect
-lopencv_xphoto)
 
if(OPENCV_FOUND)
set( OPENCV_LIBRARIES ${OPENCV_LIBRARY} )
set( OPENCV_INCLUDE_DIRS ${OPENCV_INCLUDE_DIR} )
endif()
 
mark_as_advanced(OPENCV_INCLUDE_DIRS OPENCV_LIBRARIES)

Após isso, retorne à pasta do projeto e exporte as variáveis de ambiente e rode o script do CMake:

# For OpenCV 2.4
source /usr/local/oecore-opencv2_4/environment-setup-armv7at2hf-vfp-neon-angstrom-linux-gnueabi
cmake  -DOCVV=2_4 .
 
# For OpenCV 3.1
source /usr/local/oecore-opencv3_1/environment-setup-armv7at2hf-neon-angstrom-linux-gnueabi
cmake -DOCVV=3_1 .
Cross-compilando e rodando na placa

Para testar o ambiente de cross-compilação, iremos compilar uma aplicação de “Olá Mundo” que pegará uma imagem de um arquivo e mostrará na nossa tela. O código é o mesmo encontrado neste tutorial de OpenCV:

mkdir src
gedit src/myApp.cpp
---------------------------------------------------------------------------------
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv ){
cout << "OpenCV version: " << CV_MAJOR_VERSION << '.' << CV_MINOR_VERSION << "\n";

    if ( argc != 2 ){
        cout << "usage: ./myApp \n";
        return -1;
    }

    Mat image;
    image = imread( argv[1], 1 );

    if ( !image.data ){
        cout << "No image data \n";
        return -1;
    }
bitwise_not(image, image);
    namedWindow("Display Image", WINDOW_AUTOSIZE );
    imshow("Display Image", image);

    waitKey(0);

    return 0;
}

Para compilar e passar o binário gerado para a placa, siga as instruções abaixo. A placa deve ter acesso à LAN - ligando um cabo Ethernet ou através de um adaptador WiFi-USB, por exemplo. Para descobrir o IP da placa, use o comando ifconfig.

make
scp bin/myApp root@<board-IP>/home/root
# Also copy some image to test!
scp <path-to-image>/myimage.jpg root@<board-IP>:/home/root

Na sua placa, rode a aplicação.

root@colibri-imx6:~# ./myApp

Você deverá ver a imagem na tela, como na Imagem 3:

Hello World application
Aplicação Hello World

Lendo da câmera

Nesta seção, testaremos um algoritmo que lê a entrada da câmera, processando-a usando a detecção de borda inteligente. Este é um dos diversos algoritmos já implementados no OpenCV. A propósito, o OpenCV tem uma extensa documentação, com tutoriais, exemplos e bibliotecas de referência.

No Linux, quando uma câmera é conectada, ela pode ser acessada através do sistema de arquivos. O dispositivo normalmente é montado no diretório /dev. Vamos dar uma olhada no conteúdo deste diretório em um Apalis iMX6 antes de conectar uma câmera USB:

root@apalis-imx6:~# ls /dev/video*
/dev/video0   /dev/video1   /dev/video16  /dev/video17  /dev/video18  /dev/video19  /dev/video2   /dev/video20

E após plugar:

root@apalis-imx6:~# ls /dev/video*
/dev/video0   /dev/video1   /dev/video16  /dev/video17  /dev/video18  /dev/video19  /dev/video2   /dev/video20  /dev/video3

Note que a câmera foi listada como /dev/video3. Isso pode ser confirmado com o utilitário de linha de comando video4linux2 (rode v4l2-ctl –help para mais detalhes):

root@apalis-imx6:~# v4l2-ctl --list-devices
[ 3846.876041] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE
[ 3846.881940] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE
[ 3846.887923] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE
DISP4 BG ():[ 3846.897425] ERROR: v4l2 capture: slave not found! V4L2_CID_HUE
 
/dev/video16
/dev/video17
/dev/video18
/dev/video19
/dev/video20
 
UVC Camera (046d:081b) (usb-ci_hdrc.1-1.1.3):
/dev/video3
 
Failed to open /dev/video0: Resource temporarily unavailable

Além disso, também é possível obter os parâmetros do dispositivo. Note que a webcam usada nos testes tem uma resolução de 640x480 pixels.

root@apalis-imx6:~# v4l2-ctl -V --device=/dev/video3
Format Video Capture:
Width/Height  : 640/480
Pixel Format  : 'YUYV'
Field         : None
Bytes per Line: 1280
Size Image    : 614400
Colorspace    : SRGB

Ainda, uma vez que todas as interfaces de vídeo são abstraídas pelo kernel Linux, se você optasse por usar o CSI Camera Module 5MP OV5640 da Toradex, ele também seria listado como outra interface de vídeo. No Apalis iMX6, os drivers do kernel são carregados por padrão.

Agora vamos ao código. O objeto do OpenCV que faz o manuseio da entrada da câmera, VideoCapture, aceita o índice da câmera (que é 0 para o nosso exemplo de /dev/video0) ou -1 para detectar automaticamente o dispositivo de câmera.

Um loop infinito processa o vídeo quadro-a-quadro. O nosso exemplo aplica filtros de conversão para a escala de cinza → desfoque glaussiano → detecção de borda Canny e então envia a imagem processada para a saída de vídeo. O código pode ser visto abaixo:

#include <iostream>
#include <cstdlib>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv ){
    cout << "OpenCV version: " << CV_MAJOR_VERSION << '.' << CV_MINOR_VERSION << '\n';

    VideoCapture cap(-1); // searches for video device
    if(!cap.isOpened()){
      cout << "Video device could not be opened\n";
      return -1;
  }

    Mat edges;
    namedWindow("edges",1);

    double t_ini, fps;
    for(; ; ){
        t_ini = (double)getTickCount();

        // Image processing
        Mat frame;
        cap >> frame; // get a new frame from camera
        cvtColor(frame, edges, COLOR_BGR2GRAY);
        GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
        Canny(edges, edges, 0, 30, 3);

        // Display image and calculate current FPS
        imshow("edges", edges);
        if(waitKey(30) >= 0) break;
        fps = getTickFrequency()/((double)getTickCount() - t_ini);
        cout << "Current fps: " << fps << '\r' << flush;
    }
    return 0;
}

A fotografia abaixo apresenta o resultado. Observe que os parâmetros dos algoritmos Glaussian e Canny não foram ajustados.

Capture from USB camera being processed with Canny edge detection
Captura da câmera USB processada com detector de bordas Canny

Para compararmos os valores entre o Colibri iMX6S e o iMX6DL e nos certificarmos que o processamento multicore estava sendo utilizado, uma média de 1000 amostras foram medidas e os resultados estão presentes na tabela 1. O mesmo foi feito para o Apalis iMX6 com um heatsink embutido. Todos os testes foram feitos com a escalonamento de frequência desabilitado.

Tabela 1 – Comparação da implementação de Canny entre configurações de módulos e OpenCV (640x480)

FPS médio (1000 amostras) Colibri iMX6S 256MB IT Colibri iMX6DL 512MB Apalis iMX6Q 1GB
OpenCV 3.1 (TBB) 8.84 11.85 12.16
OpenCV 3.1 (OpenMP) 8.67 10.04 10.10
OpenCV 2.4 (TBB) 7.35 8.82 8.67
OpenCV 2.4 (OpenMP) 7.42 8.23 8.72

Como comparação, o algoritmo Canny foi substituido no código pelas derivadas de Sobel, baseado neste exemplo. Os resultados são mostrados na tabela 2:

Tabela 2 – Comparação da implementação de Sobel entre configurações módulos e OpenCV (640x480)

FPS médio (1000 amostras) Colibri iMX6S 256MB IT Colibri iMX6DL 512MB Apalis iMX6Q 1GB
OpenCV 3.1 (TBB) 9.62 11.09 11.31
OpenCV 3.1 (OpenMP) 9.57 11.23 11.00
OpenCV 2.4 (TBB) 7.04 8.33 8.23
OpenCV 2.4 (OpenMP) 7.03 7.80 8.28

É interessante notar que o TBB foi mais rápido que o OpenMP na maioria dos casos. Também é interessante notar que a diferença é pequena até nos testes de um único núcleo.

Além disso, as melhorias feitas do OpenCV 2.4 para o OpenCV 3.1 tiveram um impacto significativo no desempenho da aplicação - o que pode ser explicado pelas otimização com NEON. Baseado nos testes preliminares, é interessante realizar um teste em uma aplicação específica com diversas combinações de otimizações e também utilizar apenas a última versão do OpenCV.

Testando o exemplo de reconhecimento de face do OpenCV

Também foi testado um exemplo de detecção de rostos cujo código vem com o OpenCV, já que é um teste mais pesado que os anteriores e pode demonstrar melhor a diferença de desempenho entre Colibri e Apalis na hora de decidir o hardware mais apropriado para o seu projeto.

O primeiro passo foi copiar o código fonte da aplicação do GitHub e substituir o conteúdo do arquivo myApp.cpp, ou criar um novo arquivo e modificar o script CMake. Também será necessário o Haar Cascade disponibilizado para reconhecimento de face e de olhos. Uma sugestão para quem quer se aprofundar mais é clonar o repositório do OpenCV e testar os outros exemplos.

Algumas pequenas modificações devem ser feitas para que o código fonte funcione também na versão 2.4 do OpenCV: incluir a biblioteca stdio.h, modificar as bibliotecas incluídas do OpenCV (dê uma olhada nesta seção para mais informações) e por último realizar algumas modificações no CommandLineParser, já que as versões 2 e 3 do OpenCV não são compatíveis. A imagem 5 apresenta a aplicação sendo executada:

Face and eye tracking application
Aplicação de detecção de rosto e olhos

O código já apresenta o tempo necessário para executar a detecção de face. A tabela 3 abaixo apresenta os resultados para 100 amostras:

Tabela 3 – Comparação de detecção de rosto entre configurações de módulos e OpenCV (640x480)
FPS médio (1000 amostras) Colibri iMX6S 256MB IT Colibri iMX6DL 512MB Apalis iMX6Q 1GB
OpenCV 3.1 (TBB) 800.72 342.02 199.97
OpenCV 3.1 (OpenMP) 804.33 357.56 189.32
OpenCV 2.4 (TBB) 2671.00 1081.10 637.46
OpenCV 2.4 (OpenMP) 2701.00 1415.00 623.44

Novamente a diferença entre versões do OpenCV é significante – para o Apalis iMX6Q (quatro núcleos) o ganho de desempenho foi de 3x, e o OpenCV 3.1 com o Colibri iMX6DL entregou um desempenho melhor que o OpenCV 2.4 com o Apalis iMX6Q.

O Colibri iMX6S de temperatura industrial, que tem uma frequência de clock reduzida em comparação aos outros, apresentou um desempenho muito abaixo do que o esperado. O clock reduzido pode explicar por que a melhora de desempenho do módulo com um único núcleo para o de dois núcleos foi maior de que o de dois núcleos para quatro núcleos

Conclusão

Como o OpenCV é provavelmente o conjunto de bibliotecas mais popular para visão computacional e com a melhoria na performance dos computadores embarcados, saber como começar com o OpenCV para aplicativos embarcados fornece uma ferramenta poderosa para resolver problemas dos dias de hoje e do futuro próximo.

Este blog post escolheu uma família amplamente conhecida de microprocessadores como exemplo e ponto de partida, seja para aqueles que estudam apenas a visão computacional ou para aqueles que já se concentram no desenvolvimento de aplicativos do mundo real. Também demonstramos que as coisas estão apenas começando - veja, por exemplo, as melhorias de desempenho do OpenCV 2 para 3 para os processadores Arm - que também apontam para a necessidade de entender a arquitetura do sistema e se concentrar em otimizar o aplicativo o máximo possível - mesmo sob o capô. Espero que este blog post tenha sido útil e até a próxima!


Resumo

1) Construindo imagem com OpenCV
Para criar uma imagem com o OpenCV 3.1, primeiro siga as etapas deste artigo para configurar o OpenEmbedded para a imagem Toradex V2.7. Edite a receita OpenCV do meta-openembedded e adicione a seguinte linha:

CXXFLAGS += " -Wa,-mimplicit-it=thumb"

No arquivo local.conf, descomente sua MACHINE, aceite a licença da Freescale e adicione o OpenCV:

MACHINE ?= "apalis-imx6" # or colibri-imx6 depending on the CoM you have
ACCEPT_FSL_EULA = "1"
IMAGE_INSTALL_append = " opencv"

2) Gerando SDK
Assim você já poderá rodar a build e também gerar o SDK:

bitbake -k angstrom-lxde-image
bitbake -c populate_sdk angstrom-lxde-image

Para gravar a imagem no sistema embarcado, siga os passos deste artigo.

3) Preparando para compilação cruzada
Após instalar a SDK, haverá um sysroot para o target (Arm) dentro do diretório do SDK. Crie um diretório OpenCV dentro de <caminho_do_sysroot_Arm>/usr/lib/cmake e, dentro deste novo diretório, crie um arquivo chamado OpenCVConfig.cmake com o seguinte conteúdo:

cd /usr/lib/cmake
mkdir OpenCV
vim OpenCVConfig.cmake
---------------------------------------------------------------------------
set(OPENCV_FOUND TRUE)

get_filename_component(_opencv_rootdir ${CMAKE_CURRENT_LIST_DIR}/../../../ ABSOLUTE)

set(OPENCV_VERSION_MAJOR 3)
set(OPENCV_VERSION_MINOR 1)
set(OPENCV_VERSION 3.1)
set(OPENCV_VERSION_STRING "3.1")

set(OPENCV_INCLUDE_DIR ${_opencv_rootdir}/include)
set(OPENCV_LIBRARY_DIR    ${_opencv_rootdir}/lib)
set(OPENCV_LIBRARY      -L${OPENCV_LIBRARY_DIR} -lopencv_aruco -lopencv_bgsegm
-lopencv_bioinspired -lopencv_calib3d -lopencv_ccalib -lopencv_core
-lopencv_datasets -lopencv_dnn -lopencv_dpm -lopencv_face -lopencv_features2d
-lopencv_flann -lopencv_fuzzy -lopencv_highgui -lopencv_imgcodecs
-lopencv_imgproc -lopencv_line_descriptor -lopencv_ml -lopencv_objdetect
-lopencv_optflow -lopencv_photo -lopencv_plot -lopencv_reg -lopencv_rgbd
-lopencv_saliency -lopencv_shape -lopencv_stereo -lopencv_stitching
-lopencv_structured_light -lopencv_superres -lopencv_surface_matching
-lopencv_text -lopencv_tracking -lopencv_videoio -lopencv_video
-lopencv_videostab -lopencv_xfeatures2d -lopencv_ximgproc -lopencv_xobjdetect
-lopencv_xphoto)

if(OPENCV_FOUND)
  set( OPENCV_LIBRARIES ${OPENCV_LIBRARY} )
  set( OPENCV_INCLUDE_DIRS ${OPENCV_INCLUDE_DIR} )
endif()

mark_as_advanced(OPENCV_INCLUDE_DIRS OPENCV_LIBRARIES)

Dentro da pasta do seu projeto, crie um script do CMake para gerar os Makefiles para a compilação cruzada com o seguinte conteúdo:

cd 
vim CMakeLists.txt
--------------------------------------------------------------------------------
cmake_minimum_required(VERSION 2.8)
project( MyProject )
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
add_executable( myApp src/myApp.cpp )

SET(CMAKE_PREFIX_PATH /usr/local/oecore-x86_64/sysroots/armv7at2hf-neon-angstrom-linux-gnueabi)
SET(OpenCV_DIR ${CMAKE_PREFIX_PATH}/usr/lib/cmake/OpenCV)

find_package( OpenCV REQUIRED )
include_directories( ${OPENCV_INCLUDE_DIRS} )
target_link_libraries( myApp ${OPENCV_LIBRARIES} )

Exporte as variáveis de ambiente do SDK e então rode o script do CMake. Para fazer isso, você pode executar:

source /usr/local/oecore-x86_64/environment-setup-armv7at2hf-neon-angstrom-linux-gnueabi
cmake  .

4) Compilação cruzada e deploy
Agora você pode criar um diretório src e um arquivo chamado myApp.cpp dentro dele, onde você escreverá uma aplicação "Hello World" como a seguinte:

mkdir src
vim src/myApp.cpp
--------------------------------------------------------------------------------
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, char** argv ){
    Mat image;
    image = imread( "/home/root/myimage.jpg", 1 );

    if ( !image.data ){
        cout << "No image data \n";
        return -1;
    }

    namedWindow("Display Image", WINDOW_AUTOSIZE );
    imshow("Display Image", image);

    waitKey(0);
    return 0;
}

Cross compile e faça o deploy para o target:

make
scp bin/myApp root@<board-ip>:/home/root
# Also copy some image to test!
scp <path-to-image>/myimage.jpg root@<board-ip>:/home/root

No seu sistema embarcado:

root@colibri-imx6:~# ./myApp
#Camera #Computer Vision #Machine Vision #MIPI CSI-2 #NXP® i.MX6 #OpenCV #OV5640
Author Leonardo Veiga, Field Application Engineer, Toradex Brasil

Leave a comment

Your email ID will be kept confidential. Required fields are marked *



* Your comment will be reviewed and then added. Thank you.