温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

一个基于Emgu的运动检测实例

发布时间:2020-07-06 18:16:51 来源:网络 阅读:3454 作者:shengqin105 栏目:编程语言

最近在做一个威视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_DateTimem_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出一个会动的物体,原图跟结果图像都放在附件里,大家可以自己下载下来玩一下。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI