65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 根据颜色和大小检测和跟踪实时摄像头视频中的对象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (42投票s)

2011年1月6日

CPOL

4分钟阅读

viewsIcon

249417

downloadIcon

22873

使用 C# 检测和跟踪实时网络摄像头视频中的对象。

引言

您可以实时选择一种颜色,它会跟踪该颜色的对象并为您提供其位置。我为此使用了 Aforge 库。我还使用了 .NET Framework 4.0。这是一个 C# 桌面应用程序,它可以达到每秒 25 帧。您可以随时更改颜色大小,绘制点的颜色也会随之改变。

背景 

我在 CodeProject 上看到了一个非常有趣的 项目,名为制作 乐高倾斜摄像头。借助这个项目,我想到可以制作一个实时跟踪器,其中颜色和对象的大小也可以实时更改。并可以用该颜色在位图中绘制该对象的移动轨迹。我使用了他们代码的一部分,但为了提高准确性,我使用了单独的颜色过滤器。

使用代码 

步骤非常简单

  1. 从网络摄像头捕获视频帧
  2. 按给定颜色进行过滤(此处使用欧几里得过滤) 
  3. 转换为灰度
  4. 按给定大小查找对象
  5. 查找最大的对象
  6. 在位图中绘制对象位置

首先,我将解释我的软件是如何工作的。它可以以默认颜色黑色和 120 的颜色范围开始跟踪,但您可以实时更改它。要更改颜色,请使用颜色对话框选择一种颜色,请记住按“确定”,并在实心颜色框中检查您的所需颜色是否存在。

要选择范围并定义对象的最小尺寸,请参见下图,这里有三个数字上调/下调列表。objectsize 以像素为单位计算。现在关于视图

有四个视图,其中三个是 Aforge 视频源控件,另一个是 PictureBox。第一个显示正常视频,第二个显示所有检测到的对象,第三个框仅显示最大的对象,第四个框仅在最大对象位置绘制一个 0 符号。请看下图以获得更清晰的视图

我还添加了一个 RichTextBox,用于显示 biggestrectangle 位置的像素值。现在关于连接代码,我使用普通的 Aforge 连接代码。

为此,您需要下载 Aforge DLL,并添加一个非常好的视频控件来显示视频。您可以将其添加到工具箱中。只需单击工具箱上的空白区域,然后选择“项”->“浏览 DLL”->“添加控件”。

using AForge;
using AForge.Video.DirectShow;

然后初始化

videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
if (videoDevices.Count == 0)
    throw new ApplicationException();
foreach (FilterInfo device in videoDevices)
{
    VideoCaptureDevice videoSource = new VideoCaptureDevice(device.MonikerString);
    videoSource.DesiredFrameSize = new Size(320, 240);
    videoSource.DesiredFrameRate = 15;
    videoSourcePlayer1.VideoSource = videoSource;
    videoSourcePlayer1.Start();
}

对于 videoplayercontrol,在其 misc 中添加一个名为的事件

 private void videoSourcePlayer1_NewFrame(object sender, ref Bitmap image)
{
//work with image
}

为了理解过滤是如何工作的,我将通过一张水母的图片来解释。这里,我以红色中心颜色为例。在我提供的软件中,用户可以选择他所需的颜色和大小。

在该函数中,我通过编写欧几里得过滤来应用我的颜色检测代码,然后使用 blob counter 来提取数据。我将在下面详细解释。

要查看 EuclideanColorFiltering 的工作原理,请先查看

undefined

我们将应用一个颜色过滤器。使用 euclideanfiltering 是一个非常简单的代码

// create filter
EuclideanColorFiltering filter = new EuclideanColorFiltering( );
// set center colol and radius
filter.CenterColor = Color.FromArgb( 215, 30, 30 );
filter.Radius = 100;
// apply the filter
filter.ApplyInPlace( image ); 

现在看看效果

嗯,为了理解它是如何工作的,请仔细查看代码:

filter.CenterColor = Color.FromArgb( 215, 30, 30 );
filter.Radius = 100; 

第一行选择选定的颜色值。大家都知道颜色值的范围是 0 到 255。使用 filter.CenterColor = Color.FromArgb( 215, 30, 30 ); 我指定我的中心颜色将是一种受红色影响的颜色,因为这里的红色值为 215,绿色和蓝色值为 30,而 filter.Radius = 100 意味着所有接近 100 的颜色值都在我指定的颜色范围内。现在我的过滤器会过滤像素,并决定哪些颜色在指定中心和半径的 RGB 球体内部/外部——它会保留球体内部/外部的颜色像素,其余填充为指定颜色。

现在对于对象检测,我使用位图数据并使用 lockbits 方法。要清楚地理解此方法,请在此 查看。然后,我们将其转换为灰度算法,然后解锁。

BitmapData objectsData = image.LockBits(new Rectangle(0, 0, 
   image.Width, image.Height),ImageLockMode.ReadOnly, image.PixelFormat);
// grayscaling
UnmanagedImage grayImage = 	grayscaleFilter.Apply(new UnmanagedImage(objectsData));
// unlock image
image.UnlockBits(objectsData); 

现在对于对象,我们使用 blobcounter。这是 Aforge 提供的一个非常强大的类。

blobCounter.MinWidth = 5;
blobCounter.MinHeight = 5;
blobCounter.FilterBlobs = true;
blobCounter.ProcessImage(grayImage);
Rectangle[] rects = blobCounter.GetObjectRectangles();
foreach(Rectangle recs in rects)
if (rects.Length > 0)
{
    foreach (Rectangle objectRect in rects)
    {
        Graphics g = Graphics.FromImage(image);
        using (Pen pen = new Pen(Color.FromArgb(160, 255, 160), 5))
        {
            g.DrawRectangle(pen, objectRect);
        }

        g.Dispose();
        }
    }

blobCounter.MinWidthblobCounter.MinHeight 定义了对象的最小像素尺寸,而 blobCounter.GetObjectRectangles() 返回所有对象的矩形位置,并使用 graphics 类,我在图像上绘制一个矩形。

现在,如果您只想获取最大的对象,有一个方法可以做到这一点

blobCounter.MinWidth = 5;
blobCounter.MinHeight = 5;
blobCounter.FilterBlobs = true;
blobCounter.ObjectsOrder = ObjectsOrder.Size;
blobCounter.ProcessImage(grayImage);
Rectangle[] rects = blobCounter.GetObjectRectangles();
foreach(Rectangle recs in rects)
    if (rects.Length > 0)
    {
       Rectangle objectRect = rects[0];
       Graphics g = Graphics.FromImage(image);
       using (Pen pen = new Pen(Color.FromArgb(160, 255, 160), 5))
       {
           g.DrawRectangle(pen, objectRect);
       }
       g.Dispose();
    }

现在图像看起来是这样的

但是,如果您想提取它,可以使用以下代码

Bitmap bmp = new Bitmap(rects[0].Width, rects[0].Height);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(c, 0, 0, rects[0], GraphicsUnit.Pixel);

这样您就可以获得您的对象了

undefined

为了绘制位图,我不得不使用多线程。在 videosurecplayer 事件中调用了该线程。

void p(object r)
{
   try
   {//create image
   Bitmap b = new Bitmap(pictureBox1.Image);
   Rectangle a = (Rectangle)r;
   Pen pen1 = new Pen(Color.FromArgb(160, 255, 160), 3);
   Graphics g2 = Graphics.FromImage(b);
   pen1 = new Pen(color, 3);
   SolidBrush b5 = new SolidBrush(color);
    Font f = new Font(Font, FontStyle.Bold);
   g2.DrawString("o", f, b5, a.Location);//draw the position
   g2.Dispose();
   pictureBox1.Image = (System.Drawing.Image)b;
   this.Invoke((MethodInvoker)delegate
       {
           richTextBox1.Text = a.Location.ToString() + 
			"\n" + richTextBox1.Text + "\n"; ;
       });
   }
   catch (Exception faa)
   {
       Thread.CurrentThread.Abort();
   }
  Thread.CurrentThread.Abort();
}

由于跨线程,我必须使用 invoke 来写入 textbox。为了发送矩形对象,我使用了一个参数化线程。线程 aa 调用函数 p() 来在位图上绘制并写入文本框。

if (rects.Length > 0)
{
    Rectangle objectRect = rects[0];

    // draw rectangle around derected object
    Graphics g = Graphics.FromImage(image);

    using (Pen pen = new Pen(Color.FromArgb(160, 255, 160), 5))
    {
        g.DrawRectangle(pen, objectRect);
    }
    g.Dispose();
    int objectX = objectRect.X + objectRect.Width / 2 - image.Width / 2;
    int objectY = image.Height / 2 - (objectRect.Y + objectRect.Height / 2);
    ParameterizedThreadStart t = new ParameterizedThreadStart(p);
    Thread aa = new Thread(t);
    aa.Start(rects[0]);
}

关注点

这里必须实现参数化线程和跨线程,还需要应用 invoke 方法进行写入,通过良好的多线程,我们可以提高 framerate。关于过滤,欧几里得过滤比 HSLfilteringColorfilteringyrbrcr 过滤更准确。

感谢 Andrew Kirillov 的帮助。

© . All rights reserved.