nanopc-T4_RK3399 舵机与人脸检测跟踪相关程序
本人最近在 nanopc T4 开发板上做了一些小功能(完成导师安排的任务),初步实现了以下几个功能:
-
- 键盘控制 舵机上下左右旋转(两台舵机)
-
- nanopc-T4 开发板实现的人脸检测
-
- 根据人脸的位置控制舵机跟随人脸左右运动(人脸识别+人脸跟踪)
-
- nanopc-T4 开发板实现人脸识别
-
- 远程(http_get)控制舵机上下左右转动
-
- 舵机使用软件 PWM 驱动
-
- 解决舵机软件 PWM 驱动造成的抖动的一种方法
1. 键盘控制 舵机上下左右旋转(两台舵机)
#include <wiringPi.h>
# include <stdio.h>
# include <stdlib.h>
void foo(int pinN,int moveAngle);
// 接收一个输入度数
int main(void)
{
// pinN 为nanopc-T4 开发板上的引脚(以 Wpi脚 计数,通过 gpio readall 命令查看 wpi 引脚标号)
// 物理引脚为 7、11
int pinN_1=7;
int pinN_2=0;
int num;
wiringPiSetup() ;
//设置舵机控制引脚
pinMode (pinN_1, OUTPUT) ;
pinMode (pinN_2, OUTPUT) ;
//设置初始旋转到90度
// 330-2400 (10~170度)
int moveAngle_1=1365;
int moveAngle_2=1365;
foo(pinN_1,moveAngle_1);
foo(pinN_2,moveAngle_2);
for(;;){
printf("请输入移动方向,0表示左,1表示右,2表示上,3表示下\n");
scanf("%d",&num);
if(num==0||num==1){
if(num==0){
moveAngle_1=moveAngle_1-100;
}
if(num==1){
moveAngle_1=moveAngle_1+100;
}
foo(pinN_1,moveAngle_1);
}
if(num==2||num==3){
if(num==2){
moveAngle_2=moveAngle_2-100;
}
if(num==3){
moveAngle_2=moveAngle_2+100;
}
foo(pinN_2,moveAngle_2);
}
if(moveAngle_1>2400||moveAngle_1<320||moveAngle_2>2400||moveAngle_2<320){
printf("已到头!");
break;
}
}
printf("%d\n", num);
printf("%d\n", moveAngle_1);
printf("%d\n", moveAngle_2);
return 0;
}
void foo(int pinN,int moveAngle){
// 给舵机一段时间的反应,持续输出控制值
for (int i = 0; i < 200; i++)
{
digitalWrite(pinN,HIGH);
delayMicroseconds(moveAngle);
digitalWrite(pinN,LOW);
delayMicroseconds(2500-moveAngle);
}
printf("1");
}
2. nanopc-T4 开发板实现的人脸检测
import numpy as np
import cv2
# 获取opencv自带的用于人脸识别的级联分类器
faceCascade = cv2.CascadeClassifier('/media/pi/userdata/root/usr/local/share/OpenCV/haarcascades/haarcascade_frontalcatface.xml')
eye_cascade = cv2.CascadeClassifier('/media/pi/userdata/root/usr/local/share/OpenCV/haarcascades/haarcascade_eye.xml')
# 打开摄像头,本地自带的摄像头默认为0
cap = cv2.VideoCapture(10)
cap.set(3,640) # set Width
cap.set(4,480) # set Height
i=0
range1=80
range2=60
# 设置舵机旋转90度时对应值
moveAngle_1=1435
while True:
ret, img = cap.read()
# 控制图像翻转给,1,左右翻转,-1,上下翻转
img = cv2.flip(img, 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(
gray,
# 表示每次图像检测的比例
scaleFactor=1.1,
# 表示每一个目标至少要被检测到3次才算是真的目标
minNeighbors=3,
minSize=(20, 20)
)
for (x,y,w,h) in faces:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
print("%d,x = %d,x+w = %d,y = %d,y+h = %d" %(i,x,x+w,y,y+h))
i+=1
# 框选出人脸区域,在人脸区域而不是全图中进行人眼检测,节省计算资源
face_area = img[y:y+h, x:x+w] # 人脸区域
eyes = eye_cascade.detectMultiScale(face_area,1.1,5)
for (ex,ey,ew,eh) in eyes:
#画出人眼框,绿色,画笔宽度为1
cv2.rectangle(face_area,(ex,ey),(ex+ew,ey+eh),(0,255,0),1)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
cv2.namedWindow("enhanced",0);
cv2.resizeWindow("enhanced", 640, 640);
cv2.imshow('video',img)
k = cv2.waitKey(30) & 0xff
if k == 27: # press 'ESC' to quit
break
cap.release()
cv2.destroyAllWindows()
3. 根据人脸的位置控制舵机跟随人脸左右运动(人脸检测+人脸跟踪)
通过 1 与 2 的结合,调整部分程序代码实现。
舵机控制程序 duoji.c
#include <wiringPi.h>
# include <stdio.h>
# include <stdlib.h>
// gcc -Wall -o duoji duoji.c -lwiringPi -lwiringPiDev -lpthread -lrt -lm -lcrypt -shared
void foo(int pinN,int moveAngle);
// 接收一个输入度数
int fmain(int num,int moveAngle_1)
{
// pinN 为nanopc-T4 开发板上的引脚(以 Wpi脚 计数,通过 gpio readall 命令查看 wpi 引脚标号)
int pinN_1=7;
int pinN_2=0;
wiringPiSetup() ;
// for(;;){
// moveAngle_1=1365;
// moveAngle_2=1365;
// break;
// }
//设置舵机控制引脚
pinMode (pinN_1, OUTPUT) ;
pinMode (pinN_2, OUTPUT) ;
//旋转到90度
// foo(pinN_1,moveAngle_1);
// foo(pinN_2,moveAngle_2);
// printf("请输入移动方向,0表示左,1表示右,2表示上,3表示下\n");
// scanf("%d",&num);
if(num==0||num==1){
if(num==0){
moveAngle_1=moveAngle_1-50;
}
if(num==1){
moveAngle_1=moveAngle_1+50;
}
foo(pinN_1,moveAngle_1);
}
if(moveAngle_1>2400||moveAngle_1<320){
printf("已到头!");
}
printf("%d\n", num);
printf("%d\n", moveAngle_1);
return moveAngle_1;
}
void foo(int pinN,int moveAngle){
// 给舵机一段时间的反应,持续输出控制值
for (int i = 0; i < 200; i++)
{
digitalWrite(pinN,HIGH);
delayMicroseconds(moveAngle);
digitalWrite(pinN,LOW);
delayMicroseconds(2500-moveAngle);
}
printf("1");
}
通过编译命令将其进行编译
gcc -Wall -o duoji duoji.c -lwiringPi -lwiringPiDev -lpthread -lrt -lm -lcrypt -shared
通过人脸检测程序
face.py
判断人脸的位置,并调用舵机控制程序实现人脸的跟踪
import numpy as np
import cv2
# python 调用 C 所必须的操作
import ctypes
ll = ctypes.cdll.LoadLibrary
lib = ll("./duoji/duoji")
faceCascade = cv2.CascadeClassifier('/media/pi/userdata/root/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('/media/pi/userdata/root/usr/local/share/OpenCV/haarcascades/haarcascade_eye.xml')
cap = cv2.VideoCapture(10)
cap.set(3,640) # set Width
cap.set(4,480) # set Height
i=0
moveAngle_1=1435
while True:
ret, img = cap.read()
img = cv2.flip(img, 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = faceCascade.detectMultiScale(
gray,
# 表示每次图像尺寸减小的比例
scaleFactor=1.1,
# 表示每一个目标至少要被检测到3次才算是真的目标
minNeighbors=10,
minSize=(20, 20)
)
for (x,y,w,h) in faces:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
print("%d,x = %d,x+w = %d,y = %d,y+h = %d" %(i,x,x+w,y,y+h))
i+=1
# 框选出人脸区域,在人脸区域而不是全图中进行人眼检测,节省计算资源
face_area = img[y:y+h, x:x+w] # 人脸区域
eyes = eye_cascade.detectMultiScale(face_area,1.1,5)
for (ex,ey,ew,eh) in eyes:
#画出人眼框,绿色,画笔宽度为1
cv2.rectangle(face_area,(ex,ey),(ex+ew,ey+eh),(0,255,0),1)
# x 是人脸识别框中的左上角横坐标,根据 x 坐标在相机中的位置,判断相机(舵机)的移动方向
if(x < 320-0.7*w):
print(x)
# 0 或 1 表示转动方向,moveAngle_1 为转动角度系数,调用 duoji.c 中函数
moveAngle_1=lib.fmain(0,moveAngle_1)
# print("lib.fmain(0)="+str(lib.fmain(0)))
if(x+w > 320+0.7*w):
moveAngle_1=lib.fmain(1,moveAngle_1)
roi_gray = gray[y:y+h, x:x+w]
roi_color = img[y:y+h, x:x+w]
cv2.namedWindow("enhanced",0);
cv2.resizeWindow("enhanced", 640, 640);
cv2.imshow('video',img)
k = cv2.waitKey(30) & 0xff
if k == 27: # press 'ESC' to quit
break
cap.release()
cv2.destroyAllWindows()
4. nanopc-T4 开发板实现人脸识别
本次使用3个Python程序,分别为人脸数据采集程序,人脸数据训练程序以及人脸数据检测程序来实现此人脸识别程序。本程序经测试在本地电脑中运行结果良好,能正常识别出已训练好的人脸数据,但在 nanopc-T4 开发板中运行结果较不理想,初步判断为 nanopc-T4 开发板性能不足导致,后期仔细分析病假一改善。
在OpenCV中,可以用函数cv2.face.LBPHFaceRecognizer_create()生成LBPH识别器实例模型,然后应用cv2.face_FaceRecognizer.train()函数完成训练,最后用cv2.face_FaceRecognizer.predict()函数完成人脸识别。
- 个人人脸图片采集程序,将采集的人脸图片数据存放到 ./userImg 目录下
import cv2
import os
cam = cv2.VideoCapture(0) # 获取摄像头
cam.set(3, 640) # set video width # 设置视频的高度和宽度
cam.set(4, 480) # set video height
# 获取本地安装的cv2目录下的级联分类器
face_detector = cv2.CascadeClassifier(r'C:\Users\hotpotman\.conda\envs\face\Lib\site-packages\cv2\data\haarcascade_frontalface_default.xml')
# 对于每个人,输入一个编号用以区分
face_id = input('\n enter user id end press <return> ==> ')
#print("\n [INFO] Initializing face capture. Look the camera and wait ...")
# Initialize individual sampling face count
count = 0
while(True):
ret, img = cam.read() # 从摄像头读取图像
# 从文件中读取图像
# img = cv2.imread("/home/pi/Desktop/uerImg/" + str(count) + '.jpg')
#print("/home/pi/Desktop/兔子牙/" + str(count) + '.jpg')
# 翻转图像 1 水平翻转 0 垂直翻转 -1 水平垂直翻转
#img = cv2.flip(img, -1) # flip video image vertically
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转化为灰度图
# 使用级联分类器进行图片人脸检测
faces = face_detector.detectMultiScale(gray, 1.2, 5)
count += 1
# 在人脸上画矩形
for (x,y,w,h) in faces:
cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)
# 将图片保存到 userImg目录下
cv2.imwrite("./userImg/" + str(face_id) + '.' + str(count) + ".jpg", gray[y:y+h,x:x+w])
cv2.imshow('image', img) # 显示
cv2.imshow('image', img) # 显示
# 当按下 ESC 或者采集到 10 张图片时 退出
k = cv2.waitKey(100) & 0xff
if k == 27:
break
elif count >= 10:
break
# 关闭所有窗口,并释放资源
print("\n [INFO] Exiting Program and cleanup stuff")
cam.release()
cv2.destroyAllWindows()
- 根据采集到的人脸数据进行训练,提取其特征,并将训练好的数据存放到 ./trainer/trainer.yml 文件中
import numpy as np
from PIL import Image # python里面的图像库
import os
import cv2
# Path for face image database
path = './userImg' # 数据集的路径
# 在OpenCV中,可以用函数cv2.face.LBPHFaceRecognizer_create()生成LBPH识别器实例模型,然后应用cv2.face_FaceRecognizer.train()函数完成训练,最后用cv2.face_FaceRecognizer.predict()函数完成人脸识别。
# https://blog.csdn.net/qq_43069920/article/details/103673125
# LBPH识别器实例模型
recognizer = cv2.face.LBPHFaceRecognizer_create()
# 获取本地安装的cv2目录下的人脸检测级联分类器
detector = cv2.CascadeClassifier(r'C:\Users\hotpotman\.conda\envs\face\Lib\site-packages\cv2\data\haarcascade_frontalface_default.xml');
# 定义一个函数 用来获取 LBPH识别器 训练的图像和标签
def getImagesAndLabels(path):
imagePaths = [os.path.join(path,f) for f in os.listdir(path)]
# print(imagePaths) # 输出文件夹所有的文件路径
faceSamples=[] # 存放人脸
ids = [] # 存放人脸的ID
for imagePath in imagePaths: # 遍历路径
PIL_img = Image.open(imagePath).convert('L') # convert it to grayscale
img_numpy = np.array(PIL_img,'uint8')
print(os.path.split(imagePath)[-1])
# 获取图片中包含的人脸id
id = int(os.path.split(imagePath)[-1].split(".")[0]) # 获取ID
print(id)
#print(" " + str(id)) # 输入ID
faces = detector.detectMultiScale(img_numpy)
for (x,y,w,h) in faces:
faceSamples.append(img_numpy[y:y+h,x:x+w])
ids.append(id)
return faceSamples,ids
print ("\n [INFO] Training faces. It will take a few seconds. Wait ...")
faces,ids = getImagesAndLabels(path)
# 传入图像和标签 ,训练 LBPH识别器
recognizer.train(faces, np.array(ids))
# 将训练好的识别器 导出为 .yml 文件
recognizer.write('./trainer/trainer.yml') # recognizer.save() worked on Mac, but not on Pi
# Print the numer of faces trained and end program
print("\n [INFO] {0} faces trained. Exiting Program".format(len(np.unique(ids))))
- 根据训练好的数据,调用摄像头实时进行识别
import cv2
import numpy as np
import os
recognizer = cv2.face.LBPHFaceRecognizer_create() # 识别器
recognizer.read('./trainer/trainer.yml') # 加载训练集
# 获取本地安装的cv2目录下的人脸识别级联分类器
faceCascade = cv2.CascadeClassifier(r"C:\Users\hotpotman\.conda\envs\face\Lib\site-packages\cv2\data\haarcascade_frontalface_default.xml");
font = cv2.FONT_HERSHEY_SIMPLEX # 字体
#iniciate id counter
id = 0
# 定义图片中的人物名称,根据输入的 id 对应填写
names = ['None', 'zhaww', 'liuss','ligb']
# 获取摄像头 本地摄像头默认为 0
cam = cv2.VideoCapture(0)
cam.set(3, 640) # set video widht
cam.set(4, 480) # set video height
# 在AGX上不要去直接设置帧率,不好用,主要是opencv默认为YUY2格式的视频流,最高帧率只有40,而这个MJPG格式可以很高
# cam.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
# 定义最小识别范围
minW = 0.1*cam.get(3)
minH = 0.1*cam.get(4)
while True:
ret, img =cam.read() # 读取一帧图像
# 设置图像对称旋转,-1或1
img = cv2.flip(img, 1)
#img = cv2.flip(img, -1) # Flip vertically
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 转化为灰度图
# 使用级联分类器进行图片人脸检测
faces = faceCascade.detectMultiScale(
gray,
scaleFactor = 1.2,
# 图片重复检车次数
minNeighbors = 5,
minSize = (int(minW), int(minH)),
)
for(x,y,w,h) in faces:
# 根据坐标画出人脸框,蓝色,画笔宽度为2
cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
# 根据 LBPH识别器训练好的数据,与当前画面中的人脸进行匹配,返回其 ID 与 置信区间值
id, confidence = recognizer.predict(gray[y:y+h,x:x+w])
print("id=%d"%(id))
# Check if confidence is less them 100 ==> "0" is perfect match
if (confidence < 100):
# print("%id="%(id))
id = names[id]
confidence = " {0}%".format(round(100 - confidence))
else:
id = "unknown"
confidence = " {0}%".format(round(100 - confidence))
# 将 id 与 置信区间值 写到图片帧中
cv2.putText(img, str(id), (x+5,y-5), font, 1, (255,255,255), 2)
cv2.putText(img, str(confidence), (x+5,y+h-5), font, 1, (255,255,0), 1)
cv2.imshow('camera',img)
k = cv2.waitKey(10) & 0xff # Press 'ESC' for exiting video
if k == 27:
break
# Do a bit of cleanup
print("\n [INFO] Exiting Program and cleanup stuff")
cam.release()
cv2.destroyAllWindows()
5. 远程(http_get)控制舵机上下左右转动
在 nanopc-T4 上使用 web.py 部署 http_server ,用来解析 http_get 请求发送过来的数据,并根据传过来的数据调用舵机控制程序控制舵机上下左右转动。
- 使用 natfrp 服务在 nanopc-T4(ubuntu18.04)中搭建内网穿透服务,注意使用 http 协议。
- 在 nanopc-T4 上使用 web.py 搭建一个 web 服务器,接受通过内网穿透服务所传递过来的 http_get 请求,请解析出其携带的参数。
- 根据解析出的参数,调用舵机控制程序控制舵机上下左右转动。
# python_web 程序
# 接受 http_get 请求并解析出参数
import web
# python 调用 c 所必须
import ctypes
ll = ctypes.cdll.LoadLibrary
lib = ll("./duoji")
# 设置路由及其对应的处理类
urls = (
'/', 'index'
)
# 舵机旋转90度对应值
moveAngle_1=1435
class index():
def GET(self):
print(web.input().num)
# moveAngle_1=lib.fmain(web.input().num,moveAngle_1)
return moveDUoji(int(web.input().num))
def moveDUoji(num):
# 必须使用 global 声明 moveAngle_1 为全局变量,否则函数内尝试改变全局就会报错
# https://blog.csdn.net/sinat_40304087/article/details/115701595
global moveAngle_1
moveAngle_1=lib.fmain(num,moveAngle_1)
print(num)
print(moveAngle_1)
if __name__== "__main__":
app= web.application(urls,globals())
app.run()
# 命令行带端口号
6. 舵机使用软件 PWM 驱动
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <wiringPi.h>
#include <softPwm.h>
// gcc -Wall -o demo2 demo2.c -lwiringPi -lwiringPiDev -lpthread -lrt -lm -lcrypt -shared
#define RANGE 200 /* 1 means 100 us , 200 means 20 ms 1等于100微妙,200等于20毫秒 */
void foo(int pinN,int moveAngle);
// 接收一个输入度数
int main(void)
{
// pinN 为nanopc-T4 开发板上的引脚(以 Wpi脚 计数,通过 gpio readall 命令查看 wpi 引脚标号)
int pinN_1=7;
int pinN_2=0;
int num=-1;
wiringPiSetup() ;
//设置舵机控制引脚
int moveAngle_1 = 90;
int moveAngle_2 = 90;
wiringPiSetup(); /* wiringpi初始化 */
softPwmCreate( pinN_1, 0, RANGE ); /* 创建一个使舵机转到90的pwm输出信号 */
softPwmCreate( pinN_2, 0, RANGE ); /* 创建一个使舵机转到90的pwm输出信号 */
for(;;){
num=-1;
printf("请输入移动方向,0表示左,1表示右,2表示上,3表示下\n");
scanf("%d",&num);
if(num==0||num==1){
if(num==0){
// 每次调节+-5度
moveAngle_1=moveAngle_1-5;
}
if(num==1){
moveAngle_1=moveAngle_1+5;
}
foo(pinN_1,moveAngle_1);
}
if(num==2||num==3){
if(num==2){
moveAngle_2=moveAngle_2-5;
}
if(num==3){
moveAngle_2=moveAngle_2+5;
}
foo(pinN_2,moveAngle_2);
}
if(moveAngle_1>180||moveAngle_1<0||moveAngle_2>180||moveAngle_2<0){
printf("已到头!");
break;
}
printf("%d\n", num);
printf("%d\n", moveAngle_1);
printf("%d\n", moveAngle_2);
}
return 0;
}
void foo(int pinN,int moveAngle){
int degree;
// 将角度转化为pwm值(0-180:5ms-25ms)
degree = 5 + moveAngle / 180.0 * 20.0;
softPwmWrite( pinN, degree ); /* pwm输出脉冲控制舵机旋转 */
delay(500);
printf("函数执行完了");
}
7. 解决舵机软件 PWM 驱动造成的抖动的一种方法
在每次引脚输出 pwm 之后,给引脚一个 pwm=0 的值,经测试可以有效的解决舵机抖动的问题。
舵机旋转角度与 pwm 值之间的关系:
/* file name=sg90.c */
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <wiringPi.h>
#include <softPwm.h>
#define RANGE 200 /* 1 means 100 us , 200 means 20 ms 1等于100微妙,200等于20毫秒 */
int move( int pin, int moveAngle )
{
int degree;
degree = 5 + moveAngle / 180.0 * 20.0;
softPwmWrite( pin, degree ); /* 再次复写pwm输出 */
delay( 200 );
printf("%d\n",degree);
printf("%d\n",moveAngle);
// softPwmStop(pin);
// 输入 pwm 后,给 pwm 为 0,防抖
softPwmWrite( pin, 0 );
return 0;
}
int main( void )
{
int num;
int pinN_1 = 7;
int pinN_2 = 0;
int moveAngle_1 = 90;
int moveAngle_2 = 90;
wiringPiSetup(); /* wiringpi初始化 */
softPwmCreate( pinN_1, 15, RANGE ); /* 创建一个使舵机转到90的pwm输出信号 */
delay(500);
softPwmWrite( pinN_1, 0 );
softPwmCreate( pinN_2, 15, RANGE ); /* 创建一个使舵机转到90的pwm输出信号 */
delay(500);
softPwmWrite( pinN_2, 0 );
for (;; )
{
printf( "请输入移动方向,0表示左,1表示右,2表示上,3表示下\n" );
scanf( "%d", &num );
if ( !(( num ) >= 0 && ( num ) <= 180) ){
printf( "degree is between 0 and 180\n" );
exit( 0 );
}
if ( num == 0 || num == 1 ){
if ( num == 0 ){
moveAngle_1 = moveAngle_1 - 5;
}
if ( num == 1 ){
moveAngle_1 = moveAngle_1 + 5;
}
move( pinN_1, moveAngle_1 );
}
if ( num == 2 || num == 3 ){
if ( num == 2 ){
moveAngle_2 = moveAngle_2 - 5;
}
if ( num == 3 ){
moveAngle_2 = moveAngle_2 + 5;
}
move( pinN_2, moveAngle_2 );
}
}
return 0;
}