将OpenCV2移植到嵌入式ARM平台

0. 测试环境

操作系统:Ubuntu 12.04.5 LTS 64位

ARM平台:友善之臂 NanoPi Neo2 + OV5640 USB模块

编译工具链:gcc-linaro-aarch64

OpenCV版本:2.4.11

1. 安装交叉编译工具链

解压交叉编译工具链包gcc-linaro-aarch64.tar.xz/opt/下。

.tar.xz文件的解压命令为:

tar -xvJf gcc-linaro-aarch64.tar.xz

得到目录/opt/gcc-linaro-4.9-2015.02-3-x86_64_aarch64-linux-gnu/

接下来将其中的bin文件夹添加到Linux的PATH环境变量中即可。

这里使用修改/etc/bash.bashrc的方式实现。

打开该文件,添加/opt/gcc-linaro-4.9-2015.02-3-x86_64_aarch64-linux-gnu/bin到PATH变量中:

在最后一行添加:

PATH=$PATH:/opt/gcc-linaro-4.9-2015.02-3-x86_64_aarch64-linux-gnu/bin

保存退出,重新登录或执行. /etc/bash.bashrc

2. 使用Cmake配置OpenCV

2.1 安装Cmake

2.1.1 自动安装(apt方式)

使用自动安装方式安装Cmake:

sudo apt-get install cmake-gui

安装后,查看Cmake版本:

cmake –version

如果Cmake版本过低则无法正常编译OpenCV。

使用下列命令卸载Cmake,使用手动安装的方法进行安装:

apt-get autoremove cmake

2.1.2 手动安装

使用如下命令下载最新版本的Cmake,安装到/opt/cmake-3.9.1下,并将Cmake的可执行文件软连接到/usr/bin/目录

wget https://cmake.org/files/v3.9/cmake-3.9.1-Linux-x86_64.tar.gz
tar -zxvf cmake-3.9.1-Linux-x86_64.tar.gz 
mv cmake-3.9.1-Linux-x86_64/ /opt/cmake-3.9.1
sudo mv cmake-3.9.1-Linux-x86_64/ /opt/cmake-3.9.1
sudo ln -sf /opt/cmake-3.9.1/bin/* /usr/bin/

使用cmake –version查看Cmake版本,若正常输出Cmake版本号,则说明安装成功。

2.2 使用Cmake配置OpenCV2源码

此处把OpenCV2源码解压到/home/clair/openCV/opencv-2.4.11下。

在Ubuntu的图形界面终端下,输入命令cmake-gui,出现下面窗口。

opencv移植openharmony opencv移植到嵌入式平台上_#include

第一个文本框(Source Code)里填写OpenCV源码的路径。

第二个文本框(Where to build the binaries)填写使用Cmake配置后的OpenCV的编译路径。

填写完后,点击Configure按钮配置编译器。

opencv移植openharmony opencv移植到嵌入式平台上_opencv移植openharmony_02

这里选最后一项配置交叉编译工具,Next。

opencv移植openharmony opencv移植到嵌入式平台上_人脸识别_03

Target System

此处的Operating System 一定 要写Linux,否则会没有V4L/V4L2的支持。

Version写OpenCV的版本号2.4.11

Compilers

这里是编译器的设置。

在对应位置指定使用的C++/C交叉编译器(g++/gcc)的路径即可。

(指定到可执行二进制文件)

Find Program…:

Target Root一栏选择编译器的根目录。

配置完成,Finish回到Cmake界面。

等待配置完成,根据自己的需要修改各个模块是否编译。

(此处取消WITH_GTK、WITH_TIFF和WITH_QT三项,请根据需要实际进行配置 )

之后查找关键词INSTALL,会看到CMAKE_INSTALL_PREFIX一行,修改为编译后的库的输出目录。

opencv移植openharmony opencv移植到嵌入式平台上_xml_04

点击Configure和Generate按钮,在配置目录下生成Makefile文件。

opencv移植openharmony opencv移植到嵌入式平台上_xml_05

进入配置后的目录(第二个文本框),make && make install,编译并安装OpenCV库。

安装好后,将安装目录下的所有文件夹复制到目标板上的/usr目录下。

3. 交叉编译测试代码

这里使用一个简单的人脸识别测试代码进行交叉编译测试。

这里的测试代码参考了的代码。

3.1 图片中的人脸识别

代码如下:

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/calib3d/calib3d.hpp>

using namespace std;
using namespace cv;

int main()
{
    Mat image, image_gray;      //定义两个Mat变量,用于存储图像

    image = imread("./1.png");  //imread函数用于读取本地图像

    cvtColor(image, image_gray, CV_BGR2GRAY);//转为灰度图
    equalizeHist(image_gray, image_gray);//直方图均衡化,增加对比度方便处理

    CascadeClassifier eye_Classifier;  //载入分类器
    CascadeClassifier face_cascade;    //载入分类器

    //加载分类训练器,OpenCv官方文档提供的xml文档,可以直接调用
    //xml文档路径  opencv\sources\data\haarcascades 
    if (!eye_Classifier.load("./haarcascade_eye.xml"))  //需要将xml文档放在自己指定的路径下
    {  
        cout << "Load haarcascade_eye.xml failed!" << endl;
        return 0;
    }
    if (!face_cascade.load("./haarcascade_frontalface_alt.xml"))
    {
        cout << "Load haarcascade_frontalface_alt failed!" << endl;
        return 0;
    }
    //vector 是个类模板 需要提供明确的模板实参 vector<Rect>则是个确定的类 模板的实例化
    vector<Rect> eyeRect;
    vector<Rect> faceRect;
    //检测关于眼睛部位位置
    eye_Classifier.detectMultiScale(image_gray, eyeRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
    cout << "Eye find size: " << eyeRect.size() << endl;
    for (size_t eyeIdx = 0; eyeIdx < eyeRect.size(); eyeIdx++)
    {   
        rectangle(image, eyeRect[eyeIdx], Scalar(0, 0, 255));   //用矩形画出检测到的位置
    }
    //检测关于脸部位置
    face_cascade.detectMultiScale(image_gray, faceRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
    cout << "Face find size: " << faceRect.size() << endl;
    for (size_t i = 0; i < faceRect.size(); i++)
    {   
        rectangle(image, faceRect[i], Scalar(255, 0, 0));      //用矩形画出检测到的位置
    }
    imwrite("./out.png",image);
    //imshow("人脸识别图", image);         //显示当前帧
    return 0;
}

本代码会读取可执行文件目录下的1.png文件,经过预处理(转换为灰度图并均衡化)后,使用分类训练器xml文件进行人脸识别。

人脸识别的关键代码如下:

CascadeClassifier eye_Classifier;    //定义分类器
vector<Rect> eyeRect;               //存放检测结果
if (!eye_Classifier.load("./haarcascade_eye.xml"))  //载入分类器的训练结果,需要将xml文档放在自己指定的路径下
{  
    cout << "Load haarcascade_eye.xml failed!" << endl;
    return 0;
}
eye_Classifier.detectMultiScale(image_gray, eyeRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));

其中,detectMultiScale方法各参数含义如下:
(参考:)

函数原型:

void detectMultiScale( InputArray image,
                      CV_OUT std::vector<Rect>& objects,
                      double scaleFactor = 1.1,
                      int minNeighbors = 3, int flags = 0,
                      Size minSize = Size(),
                      Size maxSize = Size() );

参数1:image–待检测图片,一般为灰度图像加快检测速度;

参数2:objects–被检测物体的矩形框向量组;为输出量,如人脸检测矩阵Mat

参数3:scaleFactor–表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%;一般设置为1.1

参数4:minNeighbors–表示构成检测目标的相邻矩形的最小个数(默认为3个)。 如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。 如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框, 这种设定值一般用在用户自定义对检测结果的组合程序上;

参数5:flags–要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,因此这些区域通常不会是人脸所在区域;

参数6、7:minSize和maxSize用来限制得到的目标区域的范围。也就是我本次训练得到实际项目尺寸大小 。

最后通过rectangle方法将检测到的轮廓画出。

编译命令行:

aarch64-linux-gnu-g++ opencv1.cpp -o opencv -I/home/clair/NanoPINeo/shareLib/openCV2_lib/include -L/home/clair/NanoPINeo/shareLib/openCV2_lib/lib -lopencv_core -lopencv_imgproc -lopencv_objdetect -lopencv_highgui

在目标板上的代码执行效果:

输入图像:

opencv移植openharmony opencv移植到嵌入式平台上_xml_06

输出图像:

opencv移植openharmony opencv移植到嵌入式平台上_xml_07

可以看到,本代码已经识别到了人脸和眼睛的轮廓(请忽视右下角水印处的错误识别,我的样品图片是直接右键参考链接里的)

3.2 使用OpenCV调用USB摄像头进行人脸识别

代码如下:

#include <iostream>
#include <string>
#include <cstdlib>
#include <sstream>
#include <cstring>
#include <opencv2/opencv.hpp>
#include <opencv2/calib3d/calib3d.hpp>

using namespace std;
using namespace cv;

int main()
{
    Mat image, image_gray;      //定义两个Mat变量,用于存储每一帧的图像
    VideoCapture capture(0);
    //image = imread("./1.png");
    int faceCount = 0;

    CascadeClassifier eye_Classifier;  //载入分类器
    CascadeClassifier face_cascade;    //载入分类器

    //加载分类训练器,OpenCv官方文档提供的xml文档,可以直接调用
    //xml文档路径  opencv\sources\data\haarcascades
    if (!eye_Classifier.load("./haarcascade_eye.xml"))  //需要将xml文档放在自己指定的路径下
    {
        cout << "Load haarcascade_eye.xml failed!" << endl;
        return 0;
    }

    if (!face_cascade.load("./haarcascade_frontalface_alt.xml"))
    {
        cout << "Load haarcascade_frontalface_alt failed!" << endl;
        return 0;
    }

    //vector 是个类模板 需要提供明确的模板实参 vector<Rect>则是个确定的类 模板的实例化
    while (faceCount < 50)
    {
        capture >> image;   //Get One Frame.
        cvtColor(image, image_gray, CV_BGR2GRAY);//转为灰度图
        equalizeHist(image_gray, image_gray);//直方图均衡化,增加对比度方便处理

        vector<Rect> eyeRect;
        vector<Rect> faceRect;

        //检测关于眼睛部位位置
        eye_Classifier.detectMultiScale(image_gray, eyeRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
        cout << "Eye find size: " << eyeRect.size() << endl;
        //检测关于脸部位置
        face_cascade.detectMultiScale(image_gray, faceRect, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30));
        cout << "Face find size: " << faceRect.size() << endl;
        if(eyeRect.size() > 0 || faceRect.size() > 0)
        {
            ostringstream oss1,oss2;
            oss1 << "./Capture/cap" << faceCount << ".jpg";
            oss2 << "./Capture/face" << faceCount << ".jpg";
            // String outNormal = "./Capture/cap" + itoa(faceCount) + ".jpg";
            // String outFace = "./Capture/face" + itoa(faceCount) + ".jpg";
            cout << "Get Photo #" << faceCount << endl;
            imwrite(oss1.str(), image);
            for (size_t eyeIdx = 0; eyeIdx < eyeRect.size(); eyeIdx++)
            {
                rectangle(image, eyeRect[eyeIdx], Scalar(0, 0, 255));   //用矩形画出检测到的位置
            }
            for (size_t i = 0; i < faceRect.size(); i++)
            {
                rectangle(image, faceRect[i], Scalar(255, 0, 0));      //用矩形画出检测到的位置
            }
            imwrite(oss2.str(), image);
            faceCount++;
        }
    }
    capture.release();
    cout << "Test OK!" << endl;
    //imshow("人脸识别图", image);         //显示当前帧
    return 0;
}

人脸识别代码和3.1相同。

从摄像头获取图像的关键代码:

VideoCapture capture(0);          //打开默认摄像头
capture >> image;                 //获取一帧图像
......
capture.release();                //退出时关闭摄像头

效果不大好,就不放图了。