欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

程序员文章站 2022-03-22 17:16:53
...

将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,出现下面窗口。

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

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

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

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

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

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

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

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一行,修改为编译后的库的输出目录。

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

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

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

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

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

3. 交叉编译测试代码

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

这里的测试代码参考了https://blog.csdn.net/zuidao3105/article/details/79346591的代码。

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方法各参数含义如下:
(参考:https://blog.csdn.net/yanrong1095/article/details/78685390

函数原型:

     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

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

输入图像:

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

输出图像:

将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)

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

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();                //退出时关闭摄像头

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

相关标签: OpenCV