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 ...
随机推荐
- onActivityResult不执行 或者 onActivityResult的解决方法
开发人员都知道,可以通过使用 startActivityForResult() 和 onActivityResult() 方法来传递或接收参数.然而在一次使用中,还没等到被调用的 Activity 返 ...
- [转]Android 使用Scroller实现绚丽的ListView左右滑动删除Item效果
转帖请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/17539199),请尊重他人的辛勤劳动成果,谢谢! 我在上一 ...
- 关于Javascript语言中this关键字(变量)的用法
最近很多 Javascript初学者朋友总在问: Javascript 的this 关键字的用法.我在这里索性总结一下 this关键字的用法. this 关键字是面向对象编程语言中的一个重要概念!在J ...
- OpenSuse下编译MonoDevelop
当访问Monodevelop.com官网下载的安装包,安装后,发现并不是最新版.在OpenSuse下载的是3.0版本.根据官网的指示,可以自己下载源码进行编译.按官网的指引: 1. $ git clo ...
- Android Intent到底能做些什么
Android Intent到底能做些什么 原文:http://www.toutiao.com/i6348296465147757058/?tt_from=mobile_qq&utm_camp ...
- 高仿QQ即时聊天软件开发系列之一开端
前段时间在园子里看到一个大神做了一个GG2014IM软件,仿QQ的,那感觉···,赶快下载源码过来试试,还真能直接跑起来,效果也不错.但一看源码,全都给封装到了ESFramework里面了,音视频那部 ...
- java新版中唤醒指定线层对象
import java.util.concurrent.locks.*; class Do9 { public static void main(String[] args) { Resource r ...
- POJ2739 Sum of Consecutive Prime Numbers(尺取法)
POJ2739 Sum of Consecutive Prime Numbers 题目大意:给出一个整数,如果有一段连续的素数之和等于该数,即满足要求,求出这种连续的素数的个数 水题:艾氏筛法打表+尺 ...
- centos 服务器装与python34源码安装
http://www.111cn.net/sys/CentOS/63645.htm 1.CentOS安装Python的依赖包(不安装依赖包,会导致python安装不完整) yum groupinsta ...
- php 之 数据访问 查询关键字 (0506)
根据数据库中的car表做一个汽车查询页面: 一.一个关键字查询: 主页面: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transiti ...