基于opencv和树莓派的人脸追踪
一、 项目准备
在开始介绍项目之前,要在树莓派上编译安装opencv,wiringPi,最后配置一下SMB服务器(可选,方便在windows下打开和编辑树莓派的文件)。
1.1、opencv在树莓派上的编译安装
欲先善其事必先利器,我选用opencv-3.4.10加上opencv_contrib-3.4.10。注意,opencv_contrib必须选择和opencv版本号相同,避免不必要的麻烦。
(1) 安装依赖软件
sudo apt-get install build-essential
sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev
sudo apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
(2) 准备opencv和opencv_contrib的源码
下载opencv和opencv_contrib的源码可以去opencv官方的Github上下载,这是opencv源码的下载链接: https://github.com/opencv/opencv/tags.
以下是opencv_contrib源码的下载链接: https://github.com/opencv/opencv_contrib/tags.
可以通过以上的链接下载自己想要的opencv版本,我这里下载的是opencv-3.4.10和opencv_contrib-3.4.10。
(3)编译前的简单配置
我之前在编译OpenCV 以及 openc_contrib 提示缺少boostdesc_bgm.i等一些文件出错,经过多方的百度寻求答案,最后在一个博主那里得到了解决:https://blog.csdn.net/u011736771/article/details/85960300
由于在编译的时候他会执行文件里面的脚本下载一些文件,但由于网络等问题这些文件下载失败了。日志文件里就有它的下载地址,直接复制其下载地址到网页可以看该到文件的源码,直接拷贝源码并生存同名文件,放在 opencv_contrib/modules/xfeatures2d/src/ 路径下即可。这里我就不过多的赘述了,大家可以参考上面的链接。相应的配置文件我也已经放在Github上(地址在文末)
因为我是把源码放在服务器上编译的所以 以下的路径前缀/mnt/USER_lvzhe/换成自己主机相应的路径
在/mnt/USER_lvzhe/opencv-3.4.10/modules中的CMakeLists.txt中加上:
include_directories(
/mnt/USER_lvzhe/opencv_contrib-3.4.10/modules/xfeatures2d/include
)
接下来我们开始编译和安装
/**进入opencv源码的目录**/
1. cd opencv-3.4.10/
/**创建存放编译的文件夹**/
2. mkdir build
/**通过cmake生成编译所用到的Makefile脚本文件**/
3. cmake -D CMAKE_BUILD_TYPE=Release -D OPENCV_GENERATE_PKGCONFIG=ON -D CMAKE_INSTALL_PREFIX=/usr/local -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-3.4.10/modules ..
/**通过生成的Makefile编译整个源码,启动4个线程编译有可能导致树莓派死机,不嫌弃时间过长可以不用带上后面的参**/
4. sudo make -j4
/**编译完后需要安装**/
5. sudo make install
6. sudo vim /etc/ld.so.conf.d/opencv.conf
7. /usr/local/lib //添加/usr/local/lib,并保存
8. sudo ldconfig
/**我通过pkg-config这个软件来管理包,需要配置一下环境变量**/
9. sudo vim /etc/bash.bashrc
10. PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
11. export PKG_CONFIG_PATH //打开文件,在末尾添加
/**保存退出,然后**/
12. source /etc/bash.bashrc
通过cmake的一些配置生成Makefile,以下是cmake的一些详细的讲解
> cmake
> -D CMAKE_BUILD_TYPE=Release
> -D OPENCV_GENERATE_PKGCONFIG=ON //使能pkg-config管理opencv的包
> -D CMAKE_INSTALL_PREFIX=/usr/local //opencv的安装路径
> -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-3.4.10/modules //配置opencv_contrib的路径,我是将opencv和contrib放在同一级目录下,当前目录有在build,我使用了相对路径
> .. //最后这两个点不能少,这是由于要编译的文件处于上一级目录
最后出现configuring done就算配置成功,然后就可以编译了。
最后这样就算成功了。
1.2、wiringPi的简单介绍和安装
树莓派的GPIO可以像单片机(51单片机,Arduino,STM32等)一样进行IO控制(输出高、低电平,IIC,SPI,串口通信,PWM输出等),在此使用常用的WiringPi库来进行GPIO的操作。
wiringPi的安装可以使用:
方案一:使用git工具安装wiringPi库。
方案二:手动下载wiringPi源码然后在本地安装。
这里我使用第一种方案,采用git工具安装wiringPi库,以下是一些配置
1.#通过git在线获得wiringPi的源代码
git clone git://git.drogon.net/wiringPi
2.#进入wiringPi目录并安装wiringPi库
cd wiringPi
./build
执行build程序将会自动完成wiringPi库的编译和安装,安装完成如下…
以下就是安装成功…
1.3、SMB服务器的简单配置
我们使用树莓派的时候经常要在 windows 和树莓派之间进行文件传输,使用 samba 服务可实现文件共享。在 windows 的网上邻居即可访问树莓派文件系统,非常方便。
1.运行以下命令安装 samba 软件
sudo apt-get install samba samba-common-bin
2.安装完成后,修改配置文件/etc/samba/smb.conf
sudo vi /etc/samba/smb.conf
下面的配置是让用户可以访问自己的 home 目录。
a) 开启用户认证,找到‚##### Authentication #####‛,将‚# security = user ‛
的#号去掉。
b) 配置每个用户可以读写自己的 home 目录,在‚"[homes]"节中,把 "read only = yes" 改为 "read only = no"。
3.重启 samba 服务
sudo /etc/init.d/samba restart
4.添加默认用户 pi 到 samba
sudo smbpasswd -a pi
输入密码确定即可。
5.访问树莓派文件
使用文件管理器添加一个网络位置输入 ip 地址(ip 地址改为树莓派 IP 地址),输入用户密码,则可以访问树莓派 home 目录。
二、 项目说明
说完了前面的铺垫,最后来说一下整个项目的框架:
人脸检测采用opencv的harr级联分类器进行检测,将人脸的大小和中心点和整个图像的大小和中心点分别进行比较,调整舵机的位置使人脸处于图像的中心。源码如下:
/*=============================================================================
# COPYRIGHT NOTICE
# Copyright (c) 2019
# All rights reserved
#
# @author :lvzhe
# @mail :1750895316@qq.com
# @file :opencv_faceCheck.cpp
# @date :2020/03/06 15:52
# @algorithm :
=============================================================================*/
#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <stdio.h>
#include <iostream>
#include <wiringPi.h>
using namespace std;
using namespace cv;
#define PwmMaxValue (250)
#define PwmMinValue (50)
/** Function Headers */
void detectAndDisplay();
void steeringPwmInit(void);
void steeringControlDir(Point face_center,Point frame_center,int face_size);
/** Global variables */
Mat frame;
int pitchPwmPin=18;
int yawPwmPin=13;
String face_cascade_name;
CascadeClassifier face_cascade;
String window_name = "Capture - Face detection";
/** @function main */
int main(int argc, const char **argv)
{
face_cascade_name = "xml/haarcascade_frontalface_alt.xml";
VideoCapture capture;
//-- 1. Load the cascades
if (!face_cascade.load(face_cascade_name))
{
printf("--(!)Error loading face cascade\n");
return -1;
}
steeringPwmInit();
//-- 2. Read the video stream
capture.open(0);
capture.set(CV_CAP_PROP_FRAME_WIDTH,640);//最大
capture.set(CV_CAP_PROP_FRAME_HEIGHT,480);
if (!capture.isOpened())
{
printf("--(!)Error opening video capture\n");
return -1;
}
while (capture.read(frame))
{
if (frame.empty())
{
printf(" --(!) No captured frame -- Break!");
break;
}
Size dsize = Size(frame.cols*0.5, frame.rows*0.5);
resize(frame, frame, dsize);
//-- 3. Apply the classifier to the frame
detectAndDisplay();
if (waitKey(10) == 27)
{
break;
} // escape
}
return 0;
}
/** @function detectAndDisplay */
void detectAndDisplay()
{
std::vector<Rect> faces;
Mat frame_gray;
cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
equalizeHist(frame_gray, frame_gray);
Point frame_center(frame.size().width/2,frame.size().height/2);
//-- Detect faces
face_cascade.detectMultiScale(frame_gray, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(60, 60));
for (size_t i = 0; i < faces.size(); i++)
{
Point face_center(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height / 2);
rectangle(frame,faces[static_cast<int>(i)],Scalar(255,0,0),2,8,0);
steeringControlDir(face_center,frame_center,faces[i].area());
// std::cout << "face size:" << faces[i].size()<<endl;
}
//-- Show what you got
imshow(window_name, frame);
}
/** @function steeringPwmInit */
void steeringPwmInit(void)
{
wiringPiSetupGpio();
pinMode(pitchPwmPin,PWM_OUTPUT);
pinMode(yawPwmPin,PWM_OUTPUT);
pwmSetMode(PWM_MODE_MS);
pwmSetClock(192);
pwmSetRange(2000);
}
int pitch_value=150;
int yaw_value=150;
float kx=0.2;
float ky=0.3;
/** @function steeringControlDir */
void steeringControlDir(Point face_center,Point frame_center,int face_size)
{
int frame_size=frame.cols*frame.rows;
int k =frame_size/face_size;
float dx,dy;
// std::cout << "k:" << k<<endl;
if(k>=7){//如果整个图像相对人脸的大小来说很大,就要限制舵机的摆幅
dx=(frame_center.x-face_center.x)*kx/k;
dy=(frame_center.y-face_center.y)*ky/k;
yaw_value+=dx;
if(yaw_value >= PwmMaxValue)
yaw_value=PwmMaxValue;
else if(yaw_value <= PwmMinValue)
yaw_value=PwmMinValue;
pwmWrite(yawPwmPin,(int)yaw_value);
pitch_value+=dy;
if(pitch_value >= PwmMaxValue)
pitch_value=PwmMaxValue;
else if(pitch_value <= PwmMinValue)
pitch_value=PwmMinValue;
pwmWrite(pitchPwmPin,(int)pitch_value);
}else {//如果整个图像相对人脸的大小来说很小,就要限制舵机的摆动的频率
dx=(frame_center.x-face_center.x)*0.05;
dy=(frame_center.y-face_center.y)*0.05;
if(abs((int)dx)>=3){
yaw_value+=dx;
if(yaw_value >= PwmMaxValue)
yaw_value=PwmMaxValue;
else if(yaw_value <= PwmMinValue)
yaw_value=PwmMinValue;
pwmWrite(yawPwmPin,(int)yaw_value);
}
if(abs((int)dy)>=2)
{
pitch_value+=dy;
if(pitch_value >= PwmMaxValue)
pitch_value=PwmMaxValue;
else if(pitch_value <= PwmMinValue)
pitch_value=PwmMinValue;
pwmWrite(pitchPwmPin,(int)pitch_value);
}
}
}
由于放不了视频只好将截图放上来了…
三、 项目总结
由于树莓派3B+有四路pwm分别是GPIO18和GPIO12,GPIO13和GPIO19,但是只有两路独立,因此我选择GPIO18和GPIO13,使用18控制云台的俯仰角,使用13控制云台的偏航角,之前不够了解树莓派的硬件导致浪费了很多时间。
整个项目的代码我打算放在Github上,有需要的同学可以去下载:
https://github.com/lvzhe-speed/program
下载的代码需要自己编译一下,就只有一个源文件,代码量很小,只是一个小的demo。由于我比较懒不太想为一个源文件创建一个Makefile,就采用命令的模式编译源码了。
1.编译的命令
g++ opencv_faceCheck.cpp -o faceCheck `pkg-config --libs --cflags opencv` -lwiringPi
2.执行的命令
sudo ./faceCheck
之后我还会继续深入opencv和ffmpeg的项目学习中。