您好,登录后才能下订单哦!
最近在做一个威视IPC的视觉跟踪项目,因为实际操作跟本人无关,只是因为兴趣做点小研究而已,因为平台主要是用C#的,那视觉处理库无疑选择Emgu会比较理想一点,Emgu是OpenCV的一个C#封装,网上放出来的资料并不多见,搜索耗费不少的时间,Emgu的入门好像网上有些挺好的文章,在此不赘述。
本来项目的要求应该是要实时的,但使用Emgu好像挺难实时的,且不说实时视频帧很难保证,就Emgu的一句图像比较函数在我i5的机器下就花掉了100多ms,然而接近实时也并非不可能的,例如使用更好的CPU或使用显卡运算,也许存在更好的视觉处理库,方法应该不少的。我的项目实际要求是统计物体运动轨迹再作一些简单的判断而已,所以我采取一种恶心的方式,将视频数据流存放到一个Queue中,再开一条线程慢慢处理这此数据,反正我只需要事后得知结果而已,保证原始数据的实时显得更重要一点。
事先声明一点,因为开发的原因,没法在办公室里调试摄像头,我建了一个ImageStream的类,用于封装采集到的视频数据,因为Emgu中使用的是Image<TColor>的类型,这里边会有一些图像格式转换的工作需要注意。将视频数据Byte[]转成为Image<TColor>不算太难吧,也就是一句话的事而已,可能需要注意JPEG跟BMP格式的。因为是模拟,我将电脑上JPG图片转成为Byte[]数据流,放到ImageStream里,开一个线程塞图像数据,一个线程处理图像,大概流程就是这样。
先上ImageStream类的代码,如下:
using Emgu.CV; using Emgu.CV.Structure; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace QImageClass { /// <summary> /// 用于保存Image数据流的类 /// </summary> public class ImageStream { /// <summary> /// 时间值 /// </summary> public DateTime m_DateTime; /// <summary> /// 源文件名 /// </summary> public string m_SrcFileName; /// <summary> /// 图像流 /// </summary> public MemoryStream m_ImageStream = null; /// <summary> /// 构造函数 /// </summary> /// <param name="dt"></param> /// <param name="pBuf"></param> public ImageStream(DateTime dt, byte[] pBuf, string srcFileName = null) { this.m_DateTime = dt; this.m_ImageStream = new MemoryStream(pBuf); this.m_SrcFileName = srcFileName; } /// <summary> /// 转换成为Emgu的图像 /// </summary> /// <returns></returns> public Image<Bgr, Byte> ToEmguImage() { Image img = Image.FromStream(this.m_ImageStream); return new Image<Bgr, Byte>((Bitmap)(img)); } /// <summary> /// 根据时间作为文件名 /// </summary> /// <returns></returns> public string ToFileName() { string file = this.m_DateTime.Year.ToString("D4") + "-" + this.m_DateTime.Month.ToString("D2") + "-" + this.m_DateTime.Day.ToString("D2") + "-" + this.m_DateTime.Hour.ToString("D2") + "-" + this.m_DateTime.Minute.ToString("D2") + "-" + this.m_DateTime.Second.ToString("D2") + "-" + this.m_DateTime.Millisecond.ToString("D3"); return file; } } }
类中的m_DateTime跟m_SrcFileName只是作一个数据源的识别参数而已,为的是调试上的方便。
图像运动检测我封装成为了一个Poser类,使用Add(ImageStream im)将图像数据加入到处理队列里,然后自行在ProcessThread的线程中处理,Poser的代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Collections; using System.Threading; using Emgu.CV; using Emgu.CV.Structure; using System.Diagnostics; using Emgu.CV.VideoSurveillance; using Emgu.CV.CvEnum; using System.Drawing; namespace QImageClass { /// <summary> /// Image序列处理类 /// </summary> public class ImagePoser { /// <summary> /// 图像流数据链表 /// </summary> private Queue<ImageStream> _ImageStreamList = new Queue<ImageStream>(); /// <summary> /// 退出线程的标志 /// </summary> private bool _QuitThreadFlag = true; /// <summary> /// 加入序列的总数量 /// </summary> private int _TotalImageCount = 0; /// <summary> /// 已经处理完毕的数量 /// </summary> private int _FinishedCount = 0; /// <summary> /// 互斥锁 /// </summary> private Mutex _WaitMutex = new Mutex(); /// <summary> /// 前景与背景检测器 /// </summary> private FGDetector<Bgr> _ForeGroundDetector = null; /// <summary> /// 默认构造函数 /// </summary> public ImagePoser() { } /// <summary> /// 开始进行图像处理 /// </summary> public void Start() { this._QuitThreadFlag = false; Thread thread = new Thread(new ThreadStart(this.ProcessThread)); thread.Name = "ImagePoserThread"; thread.Is true; thread.Start(); } /// <summary> /// 停止图像处理线程 /// </summary> public void Stop() { this._QuitThreadFlag = true; } /// <summary> /// 退出条件 /// </summary> /// <returns></returns> protected virtual bool StopCondition() { Queue<int> fifo = new Queue<int>(); return false; } /// <summary> /// 添加图像数据 /// </summary> /// <param name="p_w_picpathStream"></param> public void Add(ImageStream p_w_picpathStream) { this._WaitMutex.WaitOne(); this._ImageStreamList.Enqueue(p_w_picpathStream); this._TotalImageCount++; Debug.WriteLine("Poser Add : " + this._TotalImageCount.ToString()); this._WaitMutex.ReleaseMutex(); } /// <summary> /// 总共需要处理的数量 /// </summary> /// <returns></returns> public int GetTotalCount() { return this._TotalImageCount; } /// <summary> /// 已经处理完毕的数量 /// </summary> /// <returns></returns> public int GetBeFinishedCount() { return this._FinishedCount; } /// <summary> /// 获取当前未处理的数量 /// </summary> /// <returns></returns> public int GetUnFinishedCount() { this._WaitMutex.WaitOne(); int nListCount = this._ImageStreamList.Count; this._WaitMutex.ReleaseMutex(); return nListCount; } /// <summary> /// 图像处理线程 /// </summary> private void ProcessThread() { //前景检测器 if (this._ForeGroundDetector == null) { this._ForeGroundDetector = new FGDetector<Bgr>(FORGROUND_DETECTOR_TYPE.FGD); } while (!this._QuitThreadFlag) { ImageStream im = null; this._WaitMutex.WaitOne(); if (this._ImageStreamList.Count == 0) { this._WaitMutex.ReleaseMutex(); Thread.Sleep(1); continue; } Stopwatch st = new Stopwatch(); st.Start(); //抽取出一组ImageStream im = this._ImageStreamList.Dequeue(); this._FinishedCount++; this._WaitMutex.ReleaseMutex(); //转换成为OpenCV所使用的图片格式 Image<Bgr, Byte> tagImage = (im.ToEmguImage()).Resize(0.5, INTER.CV_INTER_LINEAR); //tagImage.Save("E:\\" + im.ToFileName() + ".bmp");//保存Bmp格式文件 //运动检测 ////////////////////////////////////////////////////////////////////////// //高斯处理 tagImage.SmoothGaussian(3); //获取前景,将其转成为灰度图 _ForeGroundDetector.Update(tagImage); Image<Gray, Byte> foreGroundMark = _ForeGroundDetector.ForegroundMask; //foreGroundMark.Save("E:\\" + im.ToFileName() + ".bmp");//保存Bmp格式文件 //连续区域的边缘点集 Contour<Point> contour = foreGroundMark.FindContours(); if (contour != null) { //Rectangle rect = contour.BoundingRectangle; //Image<Bgr, Byte> resImg = new Image<Bgr, Byte>(foreGroundMark.Size); //绘画边缘点集 foreach (Point p in contour) { tagImage.Draw(new CircleF(p, 2.0f), new Bgr(Color.Red), 1); } //绘画绑定矩形 tagImage.Draw(contour.BoundingRectangle, new Bgr(Color.Green), 1); } //保存处理后的图片 tagImage.Save("E:\\" + im.ToFileName() + ".bmp");//保存Bmp格式文件 //计算图像处理时间 st.Stop(); Debug.WriteLine("Poser处理耗时 : " + st.ElapsedMilliseconds.ToString() + "ms\r\n"); Thread.Sleep(1); } } } }
使用时,开启一个线程(应该没难度吧?),使用类似如下的代码
/// <summary> /// 填充数据流 /// </summary> private void Thread1() { //读取资源文件 EmunFileReader reader = new EmunFileReader("D:\\TEST_JPG - 副本", ".jpg"); string[] fileList = reader.GetFileList(); int nCount = reader.GetFileCount(); //图像处理器 ImagePoser poser = new ImagePoser(); poser.Start(); foreach (string s in fileList) { //将图片转成为ImageStream Debug.WriteLine(s); Image img = Image.FromFile(s); ImageStream ism = new ImageStream(DateTime.Now, ImageConvert.ImageToBytes(img, ImageFormat.Jpeg), s); poser.Add(ism); Thread.Sleep(1); } while (true) { if (poser.GetTotalCount() == nCount && poser.GetBeFinishedCount() == nCount) { poser.Stop(); break; } else { Thread.Sleep(1); } } }
至于原始图片,大家可以自行寻找,我是用PS来P出一个会动的物体,原图跟结果图像都放在附件里,大家可以自己下载下来玩一下。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。