将OpenCV2移植到嵌入式ARM平台(NanoPi Neo2)
将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
,出现下面窗口。
第一个文本框(Source Code)里填写OpenCV源码的路径。
第二个文本框(Where to build the binaries)填写使用Cmake配置后的OpenCV的编译路径。
填写完后,点击Configure按钮配置编译器。
这里选最后一项配置交叉编译工具,Next。
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一行,修改为编译后的库的输出目录。
点击Configure和Generate按钮,在配置目录下生成Makefile文件。
进入配置后的目录(第二个文本框),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
在目标板上的代码执行效果:
输入图像:
输出图像:
可以看到,本代码已经识别到了人脸和眼睛的轮廓(请忽视右下角水印处的错误识别,我的样品图片是直接右键参考链接里的)
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(); //退出时关闭摄像头
效果不大好,就不放图了。