数字图像处理之基于opencv的人腿跟踪检测

前言

本项目是数字图像处理课程的大作业,选题为人腿跟踪的机器人。与之前实验不同的是本次实验将应用到机器人平台进行验证,所以使用了ROS系统控制。

开发环境

  1. Ubuntu18.04
  2. ROS melodic
  3. Opencv3

研究目标

1、实现机器小车识别并跟踪人腿的功能

研究内容

1、采用颜色分割算法,实现对黑色的提取功能。
2、采用特征提取算法,实现对已分割图像的有效轮廓提取。
3、采用运动决策算法,实现依据有效轮廓对小车的运动控制。

算法实现

针对颜色分离功能

1.1 先对图像进行伽马变换,使从摄像头获得的图片变亮,利于后续的颜色选择
1.2 对图片进行高斯模糊,消去噪声,
1.3 将图片从RGB色度空间转换到HSV色度空间,利于图像的颜色分割。
1.4 对提取的图片进行灰度变换并进行开运算,减少其他区域干扰。
源程序:
伽马校正(网上复制粘贴的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void GammaCorrection(Mat src, Mat &dst, float fGamma)
{
// build look up table
unsigned char lut[256];
for( int i = 0; i < 256; i++ )
{
lut[i] = saturate_cast<uchar>(pow((float)(i/255.0), fGamma) * 255.0f);//防止颜色溢出操作
}
dst = src.clone();
const int channels = dst.channels();
switch(channels)
{
case 1:
{
MatIterator_<uchar> it, end;
for( it = dst.begin<uchar>(), end = dst.end<uchar>(); it != end; it++ )
*it = lut[(*it)];
break;
}
case 3:
{

MatIterator_<Vec3b> it, end;
for( it = dst.begin<Vec3b>(), end = dst.end<Vec3b>(); it != end; it++ )
{
(*it)[0] = lut[((*it)[0])];
(*it)[1] = lut[((*it)[1])];
(*it)[2] = lut[((*it)[2])];
}
break;
}
}
}

颜色分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Separate(Mat src,Mat &dst)
{
GammaCorrection(src,src,1/1.6);//伽马变换
imshow("src",src);
GaussianBlur(src,src,Size(7,7),7,7);//高斯模糊

//转换到HSV色度空间
Mat hsv;
cvtColor(src,hsv,COLOR_BGR2HSV);//
//提取黑色
//thre.convertTo(thre,CV_8UC1,-1,255);
inRange(hsv, Scalar(0,0, 0),Scalar(180,255, 46), dst);//颜色分割
//开运算
Mat element=getStructuringElement(MORPH_RECT, Size(15,15));
Mat opened;
morphologyEx(dst, dst, MORPH_OPEN, element);;
imshow("black",dst);

}

针对特征识别功能

2.1 对处理后的灰度图进行寻找轮廓,绘画轮廓边框和轮廓中心点,判断边框是否是所需要的轮廓边框。
2.2 判断依据有矩形的长宽比是否为长边形,边框是否是大致竖直状态,边框大小是否处于面积阈值范围。
2.3 得到人腿的准确边框。
源程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//两点间距离
double SideCalc(Point a,Point b){
return sqrtf(powf(a.x-b.x,2)+powf(a.y-b.y,2));
}
//直线的斜率
double Grad(Point a,Point b){
if(a.x==b.x)return oo;
return fabs(a.y-b.y)/fabs(a.x-b.x);
}
void Command(Mat src,geometry_msgs::Twist &cmd_red)//根据分离出来的图像决定dashgo的行动
{
//寻找轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(src, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
//对每个找到的轮廓创建可倾斜的边界框
vector<RotatedRect> minRect(contours.size());
for (int i = 0; i < contours.size(); i++)
{
minRect[i] = minAreaRect(Mat(contours[i]));
}


bool flag=false;//是否有且仅有一个符合条件的边框
Point center(0,0);//轮廓中心点
double Area=-1;
//筛选边框并且根据有效轮廓决定运动参数(如果有多个,则参数无意义)
//绘出有效轮廓及其可倾斜的边界框
Mat drawing = Mat::zeros(src.size(), CV_8UC3);
for (int i = 0; i< contours.size(); i++)
{
Point2f rect_points[4]; //轮廓四个拐点
minRect[i].points(rect_points);
//计算方框相邻边长和面积
double side1=SideCalc(rect_points[0],rect_points[1]);
double side2=SideCalc(rect_points[1],rect_points[2]);
double area=side1*side2;

//cout<<"area:"<<area<<endl;
//判断轮廓形状是否是要求长方形
//cout<<"WHratio:"<<side1/side2<<" "<<side2/side1<<endl;
if(side1/side2>WHRATIO&&side2/side1>WHRATIO)continue;
//判断长方形长边的斜率
double longGrad;
if(side1>side2)longGrad=Grad(rect_points[0],rect_points[1]);
else longGrad=Grad(rect_points[1],rect_points[2]);
//cout<<"longGrad:"<<longGrad<<endl;

if(longGrad<GRAD)continue;//如果过于平缓,则淘汰该矩形

//判断轮廓的大小,并且根据可能轮廓判断轮廓与机器人距离
if(area>=130000||area<15000)continue;//如果距离过远或者过近则不运动,同时排除掉过小的边框


//寻找轮廓中心点,并绘制边框
for (int j = 0; j < 4; j++)
{
line(drawing, rect_points[j], rect_points[(j + 1) % 4], Scalar(0, 0, 255), 1, 50);
center.x+=rect_points[j].x;
center.y+=rect_points[j].y;
}
center.x/=4;
center.y/=4;
circle(drawing,center,2,Scalar(0,0,255));
……
}
……
}

针对运动决策功能

3.1 对得到的边框进行筛选,并依据边框的大小进行判断目标的远近,边框中心点与图像中心点比较得到目标的左右偏离位置
3.2 依据具体大小,偏离距离进行角速度和线速度的规划,并设置阻尼函数,使小车运行,线速度随远近而变大或变小,角速度随偏离距离增加或减小。
3.3 最终实现机器人的人腿跟踪识别。
源程序:
运动规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
double vec=0;
double angle_vec=0;
void Command(Mat src,geometry_msgs::Twist &cmd_red)//根据分离出来的图像决定dashgo的行动
{
……
for (int i = 0; i< contours.size(); i++)
{
……
if(Area>-1)//已有有效边框
{
Area=-1;
break;
}
else Area=area;

}

imshow("Contours",drawing);
//制定机器人指令
if(Area==-1)//视野没有有效轮廓
{
return ;
}
//判断机器人速度
double ref_vec=1.0f*(130000-Area)/260000.0f;//目标速度
vec=Damp(ref_vec,vec);//算出阻尼后的速度
cmd_red.linear.x=vec;
//判断机器人前进的方向
double centerL=drawing.cols*5/11;
double centerR=drawing.rows*6/11;
double ref_angle_vec=0;
if(center.x<=centerL)ref_angle_vec=(centerL-center.x)/centerL;//左边
else if(center.x>centerR)ref_angle_vec=(centerR-center.x)/centerL;//右边
angle_vec=Damp(ref_angle_vec,angle_vec);//算出阻尼后的速度
cmd_red.angular.z=angle_vec;
}

阻尼函数

1
2
3
4
5
6
#define Kp 0.1
double Damp(double vec,double last_vec){
double error=vec-last_vec;
double output=last_vec+error*Kp;
return output;
}

实验结果

分别对颜色分割、特征提取、运动决策功能进行了实验验证,实验过程介绍
结果1:对色度空间的转换和伽马变换实现了对黑色较好的的颜色分割。
结果图:
颜色分离
结果2:对已分割完成后的图片进行轮廓绘画并标出其中心点,并依据判断条件去除不符合条件的边框。
结果图:
方框选取
结果3:根据轮廓中心点与图像中心点的坐标判断,得到机器人左右控制的速度指令。根据轮廓面积的大小控制机器人前进的速度大小。
结果图:
机器人控制

结论

本文设计了颜色分离,特征提取,运动决策算法,实现了对黑色的提取、对分离图片的边框选取、对机器人小车的运动控制功能,取得了有效提取并分离出黑色、对已分割图片轮廓的完好提取并绘画边框、较良好的控制小车的运动的效果。实验过程说明了颜色分割效果较好、轮廓边框判断选取准确、小车速度转速控制稳定。
本文设计的算法具有如下优缺点:
优点:使用最基础的算法和代码实现了较为良好的效果;算法条理清晰,并可以消除大量黑色干扰物体;消除了小车突然启动时的速度突变现象。
缺点:背景中不能有大块的黑色干扰,否则会与人腿形状融合导致无法成为有效边框;只能识别纯色裤子的人腿,非常花哨的裤子可能会干扰;多个轮廓直接停止运动,不会继续跟随
可以通过哪些方法进行改进:通过深度学习或者模式识别,直接针对人腿进行识别,可以较为良好的避免背景中的干扰,并且不再收到颜色的干扰;刚出现多个轮廓时,直接跟踪最大轮廓。