opencv视频跟踪2
在前面的报告中我们实现了用SURF算法计算目标在移动摄像机拍摄到的视频中的位置。由于摄像机本身像素的限制,加之算法处理时间会随着图像质量的提高而提高,实际实验发现在背景复杂的情况下,结果偏差可能会很大。
本次改进是预备在原先检测到的特征点上加上某种限制条件,以提高准确率。
问题:如何判定检测到的特征点是否是我们需要的点(也就是目标区域上的点)?
可行方案:用形态学找出目标的大致区域,然后对特征点判定。
特征点(SURF算法或者其他的算法)已有,我们来一步步实现找到目标大致区域。
下图假设为视频中的某一帧

我们要在这一帧中找出“停”字的大致区域(“停”的颜色和背景颜色可以酌情设置,并且和少量代码相关,可以修改)。
目标被设置成了两种颜色(后面的操作也是基于两个通道R和B),原因是一种颜色太简单以致不好分离通道然后计算,三种颜色往上也没有必要,因为我们只计算两通道,多了会增加计算时间。(其他情况留作进一步的讨论)
1、分离通道
Mat img_scene = imread("image1.jpg"); //读取图像
resize(img_scene,img_scene,cvSize(,),0.2,0.2); //把图像调整到合适的大小
vector<Mat> channels;
split(img_scene, channels); // 分离色彩通道, 把一个3通道图像转换成3个单通道图像
Mat img_scene_BlueChannel = channels.at(); // 红通道
Mat img_scene_GreenChannel = channels.at(); // 绿通道
Mat img_scene_RedChannel = channels.at(); // 蓝通道
结果如下图:
红色通道

蓝色通道

我们看到原图的红色部分在red channel中位深色,蓝色部分在blue channel中为深色,这一步我们分离了目标的两个部分,两幅图得背景看起来差别不大。
2、对图像进行二值化
首先定义阈值
#define threshold_value_red 150 // 红色通道阈值
#define threshold_value_blue 160 // 蓝色通道阈值
二值化处理
Mat Seg_img_red; // 红色通道阈值分割结果图
Mat Seg_img_blue; // 蓝色通道阈值分割结果图 threshold(img_scene_RedChannel, Seg_img_red, threshold_value_red, , THRESH_BINARY); // 红色通道进行阈值分割(大于阈值时候取255)
threshold(img_scene_BlueChannel, Seg_img_blue, threshold_value_blue, , THRESH_BINARY); // 蓝色通道进行阈值分割(小于阈值时候取255) imshow("Seg_img_red",Seg_img_red);
imshow("Seg_img_blue",Seg_img_blue);
处理结果


二值化后图像简单化了,去除了冗余,值得高兴的是目标看起来更突出了,目标区域似乎能够检测出来,但是背景也有大片干扰,下面就是用两个通道的好处了。
3、合并图像
这里我们注意到red channel中“停”字为黑色,背景是白色,而blue channel中刚好相反,于是想到试着对两幅图进行异或运算
Mat Object_img;
bitwise_xor(Seg_img_red, Seg_img_blue, Object_img); // 求两个图像的交集获取目标的潜在颜色区域
imshow("Object_img",Object_img);
结果

好的,看起来更好了,虽然背景还是有干扰(这也是无法避免的),目前我们已经使用上了两个通道的信息,只能继续往下寻找其他方法。
4、腐蚀图像
图像中有不少像胡椒粉一样的噪声,先用腐蚀去掉
erode(Object_img,Object_img,cv::Mat());
imshow("Object_img_erode",Object_img);
结果

清爽不少
5、计算连通域
vector<vector<Point> > contours;
findContours(Object_img, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
Mat result(Object_img.size(), CV_8U, Scalar());
drawContours(result, contours, //画出轮廓
-, // 画出所有的轮廓
Scalar(), // 用白线画出
); // 轮廓线的粗细为2 namedWindow("Contours");
imshow("Contours", result); // 显示图像中所有的连通域轮廓
处理结果

有很多小的连通域,可以放心的先去掉
6、连通域去噪
方法一:
// 去除图像中的连通域噪声
int cmin = ; // 最小的轮廓长度
int cmax = ; // 最大的轮廓长度
vector<vector<Point>>::const_iterator itc = contours.begin();
while (itc != contours.end())
{
if (itc->size() < cmin || itc->size() > cmax)
itc = contours.erase(itc); // 删除当前连通域轮廓
else
++itc;
} // 画出去掉连通域噪声后的连通域
Mat original(Object_img.size(), CV_8U, Scalar());
Mat result_hull(Object_img.size(), CV_8U, Scalar());
Mat threshold_output(Object_img.size(), CV_8U, Scalar());
drawContours(original, contours,
-, // 画出所有的轮廓
Scalar(), // 用白线画出
); // 轮廓线的粗度为2
namedWindow("Contours noise reduced");
imshow("Contours noise reduce", original); // 画出去掉连通域噪声后的连通域
处理结果(轮廓长度参数表示我们要选取适当的连通域,将范围缩小可以在单张图片中取得更好的结果,例如将cmin=400直接就有

但是为了在视频中处理要将范围适当放宽),结果如下:

看起来离目标不远了。
方法二:
方法一使用了人为设置的参数,但在视频处理中要尽量避免这种做法,方法二将得到的连通域按边界像素数量进行排序,然后选取边界像素数量最大的contours.size()*1/n数量的连通域(n按实际帧大小和摄像机距离目标距离等因素选取适当的值)。
int *ca = new int[contours.size()]; //定义连通域边界像素排序数组
itContours = contours.begin();
for (int i = ;i < contours.size(), itContours != contours.end();++i, ++itContours)
{ ca[i] = itContours->size();
}
sort(ca, ca + contours.size()); //按连通域边界像素的多少排序
int s = contours.size()/;
int threshhold_con = ca[contours.size() - s];
vector<vector<Point>>::const_iterator itc = contours.begin();
while (itc != contours.end())
{
if (itc->size() < threshhold_con)
itc = contours.erase(itc); // 删除当前连通域轮廓
else
++itc;
}
方法二结果

看起来似乎没什么差别,算法复杂了一点,但为了最后在视频中处理,我们选用了第二种方法。
7、计算连通域的凸包络、填充包络
Mat result_hull(Object_img.size(), CV_8U, Scalar());
Mat threshold_output(Object_img.size(), CV_8U, Scalar());
/// 对每个轮廓计算其凸包
vector<vector<Point> >hull( contours.size() );
for( int i = ; i < contours.size(); i++ )
{ convexHull( Mat(contours[i]), hull[i], false ); } /// 绘出轮廓及其凸包
Mat drawing = Mat::zeros( threshold_output.size(), CV_8UC3 );
for( int i = ; i< contours.size(); i++ )
{
Scalar color = Scalar( rng.uniform(, ), rng.uniform(,), rng.uniform(,) );
drawContours( drawing, contours, i, color, , , vector<Vec4i>(), , Point() );
drawContours( drawing, hull, i, color, , , vector<Vec4i>(), , Point() );
fillConvexPoly(result_hull,&hull[i][],hull[i].size(),Scalar(,,));
}
处理结果


实际上到这里,如果我们的把落入上图白色区域的特征点认为是正确的点已经能够减少一部分误差了,但本着精益求精的精神,还是应该再好一点。
8、计算连通域的外接矩形
这一步为可选项,可以看到前面一张图中的目标是近似正方形的,实际我们所用的目标就是正方形的,那么在摄像机离目标中心法线偏离角度不是太离谱的情况,得到的目标图像的外接矩形的长宽比的变化可以表示为一个固定的范围,这里思路是,计算所有连通域外接矩形长宽比(用短的比长的),得到一组0~1的浮点数,排序后和第七步一样选取适当的百分比,筛选长宽比靠近1的外接矩形和对应的连通域,代码如下:
Rect *r = new Rect[contours.size()];//定义外接矩形数组
double *ra = new double[contours.size()]; //定义外接矩形长宽比数组
double *rb = new double[contours.size()];
Mat obj_rec = Mat::zeros( threshold_output.size(), CV_8UC3 );
for( int i = ; i < contours.size(); i++ )
{
convexHull( Mat(contours[i]), hull[i], false );
r[i] = boundingRect(Mat(contours[i]));//boundingRect获取这个外接矩形;
rb[i] = ra[i] = rate(r[i].width,r[i].height); //计算长宽比
rectangle(obj_rec, r[i], Scalar(), );
}
sort(ra,ra+contours.size()); //将外接矩形长宽比排序
int k = contours.size()/;
double threshhold_rate = ra[contours.size() - k]; //定义外接矩形长宽比阈值
//绘制通过长宽比阈值限制后的外接矩形
Mat obj_rec_thr = Mat::zeros( threshold_output.size(), CV_8UC3 );
itContours = contours.begin();
for( int i = ; i < contours.size(), itContours != contours.end(); ++i)
{
if(rb[i]>threshhold_rate)
{
rectangle(obj_rec_thr, r[i], Scalar(,,), );
++itContours;
}
else
itContours = contours.erase(itContours);
}
筛选前外接矩形:

筛选后连通域外接矩形:

相应的连通域图:

只剩下一点噪声了。
9、去掉最后的噪声
第三步中合并图像我们使用的是异或(xor),也就是同一个位置像素在两个通道中值相同和不同这两种结果在目标图像中以黑和白区分开来了,我们这个例子中目标图像白色区域是为两个通道相同像素点值不同的结果。也就是说,前一张图中的两块连通域中的像素在第二步阈值处理后的两张图中相应位置的像素值刚好不同。于是我们将得到的两个连通域的外接矩形设置为兴趣区,我们得到两个兴趣区,以第一个兴趣区的位置和大小在阈值处理后的图像上分别截取对应的区域(做这步我们要将
threshold(img_scene_RedChannel, Seg_img_red, threshold_value_red, , THRESH_BINARY); // 红色通道进行阈值分割(大于阈值时候取255)
threshold(img_scene_BlueChannel, Seg_img_blue, threshold_value_blue, , THRESH_BINARY); // 蓝色通道进行阈值分割(小于阈值时候取255)
中的255改为1,方便累加处理),又得到两块区域,分别求像素和sum_red,sum_blue,求比值(小的比大的),我们得目标区域是既有蓝色的又有红色的(比值可通过自己设计目标图形和颜色修改),噪声区域是不一定的,我们只保留比值大于0.2的,太小的就不要了。
实现代码
for( int i = ; i < contours.size(), itContours != contours.end(); ++i)
{
if(rb[i]>threshhold_rate)
{
rectangle(obj_rec_thr, r[i], Scalar(,,), );
// ++itContours;
Mat imageROI_red = Seg_img_red(cv::Rect(r[i].x, r[i].y, r[i].width, r[i].height));
Mat imageROI_blue = Seg_img_blue(cv::Rect(r[i].x, r[i].y, r[i].width, r[i].height)); long long int sum_red = , sum_blue = ;
int nr=imageROI_red.rows;
int nc=imageROI_red.cols;
// outImage.create(image.size(),image.type());
if(imageROI_red.isContinuous())
{
nr=;
nc=nc*imageROI_red.rows*imageROI_red.channels();
}
for(int i=;i<nr;i++)
{
const uchar* Data_red=imageROI_red.ptr<uchar>(i);
const uchar* Data_blue=imageROI_blue.ptr<uchar>(i);
// uchar* outData=outImage.ptr<uchar>(i);
for(int j=;j<nc;j++)
{
sum_red += *Data_red;
sum_blue += *Data_blue;
// *outData++=*inData++/div*div+div/2;
}
}
double pixel_sum_rate = rate((double)sum_red, (double)sum_blue);
cout << sum_red << "," << sum_blue << endl;
cout << pixel_sum_rate << endl; if(pixel_sum_rate < 0.2)
itContours = contours.erase(itContours);
else
++itContours; imshow("imageROI_red", imageROI_red);
imshow("imageROI_blue", imageROI_blue);
}
else
itContours = contours.erase(itContours);
}
代码是直接在第八步中插入的,结果如下:

通过输出中间值可看到

第一个外接矩形区域sum_red=0,sum_blue=420,比值为0;
第二个外接矩形区域sum_red=12317,sum_blue=9153,比值为0.743119;
10、结论
我们已经成功找到目标所在区域,实现了预期的效果,用这个区域去限制SURF特征点预计可以得到更加精确的结果。
opencv视频跟踪2的更多相关文章
- python + opencv: kalman 跟踪
之前博文中讲解过kalman滤波的原理和应用,这里用一个跟踪鼠标的例程来演示怎么在opencv里用自带的kalman函数进行目标跟踪,文章的内容对做图像跟踪有借鉴意义.文章主要是网络资源进行整理和简单 ...
- OpenCV视频读取播放,视频转换为图片
转载请注明出处!!! http://blog.csdn.net/zhonghuan1992 OpenCV视频读取播放,视频转换为图片 介绍几个有关视频读取的函数: VideoCapture::Vide ...
- opencv 视频处理相关
包含视频格式知识(编解码和封装格式):如何获取视频信息及视频编解码格式:opencv读取及保存视频,及opencv fourcc编码格式 一.基础知识 视频的编解码格式和封装格式 参考如山似水 视频编 ...
- OpenCV 视频监控(Video Surveilance)的算法体系
如前面说到的,OpenCV VS提供了6组算法的接口,分别是:前景检测.新目标检测.目标跟踪.轨迹生成.跟踪后处理.轨迹分析,除了轨迹生成用于轨迹数据的保存以外,其他5个部分都是标准的视频监控算法体系 ...
- Opencv目标跟踪—CamShift算法
CamShift算法全称是"Continuously Adaptive Mean-Shift"(连续的自适应MeanShift算法),是对MeanShift算法的改进算法,可以在跟 ...
- 庞锋 OpenCV 视频 学习进度备忘
书签:另外跳过的内容有待跟进 学习资源: opencv视频教程目录(初级) 主讲:庞锋,毕业于电子科技大学 知识基础支持: 线性代数 应用数学 跳过的内容: 1.第1~6集跳过,简单.(2014- ...
- 比微软kinect更强的视频跟踪算法--TLD跟踪算法介绍
转自:http://blog.csdn.net/carson2005/article/details/7647500 TLD(Tracking-Learning-Detection)是英国萨里大学的一 ...
- ffmpeg的安装--opencv视频处理必备
安装yasm(ffmpeg必备)wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gztar xzvf yasm-1. ...
- OpenCV——视频操作基础
读入视频: VideoCapture 类 //方法一 VideoCapture capture; capture.open("test.avi"); //方法二 VideoCapt ...
随机推荐
- 经常使用ARM汇编指令
一面学习,一面总结,一面记录. 以下是整理在网上找到的一些资料,简单整理记录一下,方便以后查阅. ARM处理器的指令集能够分为跳转指令.数据处理指令.程序状态寄存器(PSR)处理指令.载入/存储指令. ...
- 七、Solr服务部署和安全
概念: 我们知道,Solr是以webapp的形式运行的,那么我们只需要把Solr.war文件部署到web容器中,便可以运行了,但是因为需要连接数据库做索引并且提供线上的服务调用query接口,那么So ...
- ExtJS4.2学习(11)——高级组件之Grid
大纲: 1.首先,搭建起来一个最基础的Grid组件: 2.其次,利用前边MVC架构将代码重构: 3.再者,介绍下Grid的一些特性. 一.搭建基础的Grid组件 在文章的开始,我们首先简单的搭建一个G ...
- 杭州电 1372 Knight Moves(全站搜索模板称号)
http://acm.hdu.edu.cn/showproblem.php?pid=1372 Knight Moves Time Limit: 2000/1000 MS (Java/Others) ...
- ADT下载地址整理
參考以下文章 http://developer.android.com/tools/sdk/eclipse-adt.html 整理了官网的下载地址 http://dl.google.com/andro ...
- Java基础知识强化50:运行javac 报告javac不是内部或外部命令(已解决)
1. 问题:运行javac 报告javac不是内部或外部命令,但是运行java.java-version正常 ? 看看下面三个环境变量是否设置正确: (1)环境变量 JAVA_HOME 设置JAVA ...
- Java基础知识强化38:StringBuffer类之StringBuffer的添加功能
1. StringBuffer的添加功能: public StringBuffer append(String str):可以把任意类型数据添加到字符串缓冲区里面,并返回字符串缓冲区本身. publ ...
- Mysqldb连接Mysql数据库(转)
python操作mysql数据库 Python 标准数据库接口为 Python DB-API,Python DB-API为开发人员提供了数据库应用编程接口. Python 数据库接口支持非常多的数据库 ...
- Python购物车的实现课程
需求: 1.用户输入工资收入 2.打印商品列表 3.用户选择商品,不断的加入购物车 4.检测用户余额,直接捐款,不足提示余额不足 5.允许主动退出,退出时,打印已购商品列表 重点方法: 打印列表下标的 ...
- div+css不间断滚动字幕
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...