首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在图片盒上显示从摄像机中检索到的位图的实验

在图片盒上显示从摄像机中检索到的位图的实验
EN

Stack Overflow用户
提问于 2015-04-26 17:45:04
回答 2查看 913关注 0票数 8

在我的代码中,我从带有指向非托管对象的指针的照相机中检索帧,对其进行一些计算,然后在picturebox控件上将其可视化。

在深入讨论这个应用程序的所有细节之前,我希望确保这个过程的基本代码是好的。我特别想:

  • 尽量减少执行时间,避免不必要的操作,例如复制超出需要的图像。我只想做必要的手术
  • 理解每一帧计算过程中的延迟是否会对图像的显示方式产生有害影响(例如,如果没有按我预期的那样打印),或者跳过一些图像。
  • 防止更严重的错误,例如由于内存或线程管理或图像显示引起的错误。 为此,我设置了几行实验代码(如下所示),但我无法解释我发现的结果。如果您拥有OpenCv的可执行文件,您可以自己尝试。
代码语言:javascript
复制
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;

public partial class FormX : Form
{
private delegate void setImageCallback();
Bitmap _bmp;
Bitmap _bmp_draw;
bool _exit;
double _x;
IntPtr _ImgBuffer;

bool buffercopy;
bool copyBitmap;
bool refresh;

public FormX()
{
    InitializeComponent();
    _x = 10.1;

    // set experimemental parameters
    buffercopy = false;
    copyBitmap = false;
    refresh = true;
}

private void buttonStart_Click(object sender, EventArgs e)
{
    Thread camThread = new Thread(new ThreadStart(Cycle));
    camThread.Start();
}

private void buttonStop_Click(object sender, EventArgs e)
{
    _exit = true;
}

private void Cycle()
{
    _ImgBuffer = IntPtr.Zero;
    _exit = false;

    IntPtr vcap = cvCreateCameraCapture(0);
    while (!_exit)
    {
        IntPtr frame = cvQueryFrame(vcap);

        if (buffercopy)
        {
            UnmanageCopy(frame);
            _bmp = SharedBitmap(_ImgBuffer);
        }
        else
        { _bmp = SharedBitmap(frame); }

        // make calculations
        int N = 1000000; /*1000000*/
        for (int i = 0; i < N; i++)
            _x = Math.Sin(0.999999 * _x);

        ShowFrame();
    }

    cvReleaseImage(ref _ImgBuffer);
    cvReleaseCapture(ref vcap);
}


private void ShowFrame()
{
    if (pbCam.InvokeRequired)
    {
        this.Invoke(new setImageCallback(ShowFrame));
    }
    else
    {
        Pen RectangleDtPen = new Pen(Color.Azure, 3);

        if (copyBitmap)
        {
            if (_bmp_draw != null) _bmp_draw.Dispose();
            //_bmp_draw = new Bitmap(_bmp); // deep copy
            _bmp_draw = _bmp.Clone(new Rectangle(0, 0, _bmp.Width, _bmp.Height), _bmp.PixelFormat);
        }
        else
        {
            _bmp_draw = _bmp;  // add reference to the same object
        }

        Graphics g = Graphics.FromImage(_bmp_draw);
        String drawString = _x.ToString();
        Font drawFont = new Font("Arial", 56);
        SolidBrush drawBrush = new SolidBrush(Color.Red);
        PointF drawPoint = new PointF(10.0F, 10.0F);
        g.DrawString(drawString, drawFont, drawBrush, drawPoint);
        drawPoint = new PointF(10.0F, 300.0F);
        g.DrawString(drawString, drawFont, drawBrush, drawPoint);
        g.DrawRectangle(RectangleDtPen, 12, 12, 200, 400);
        g.Dispose();

        pbCam.Image = _bmp_draw;
        if (refresh) pbCam.Refresh();
    }
}

public void UnmanageCopy(IntPtr f)
{
    if (_ImgBuffer == IntPtr.Zero)
        _ImgBuffer = cvCloneImage(f);
    else
        cvCopy(f, _ImgBuffer, IntPtr.Zero);
}

// only works with 3 channel images from camera! (to keep code minimal)
public Bitmap SharedBitmap(IntPtr ipl)
{
    // gets unmanaged data from pointer to IplImage:
    IntPtr scan0;
    int step;
    Size size;
    OpenCvCall.cvGetRawData(ipl, out scan0, out step, out size);
    return new Bitmap(size.Width, size.Height, step, PixelFormat.Format24bppRgb, scan0);
}

// based on older version of OpenCv. Change dll name if different
[DllImport( "opencv_highgui246", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvCreateCameraCapture(int index);

[DllImport("opencv_highgui246", CallingConvention = CallingConvention.Cdecl)]
public static extern void cvReleaseCapture(ref IntPtr capture);

[DllImport("opencv_highgui246", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvQueryFrame(IntPtr capture);

[DllImport("opencv_core246", CallingConvention = CallingConvention.Cdecl)]
public static extern void cvGetRawData(IntPtr arr, out IntPtr data, out int step, out Size roiSize);

[DllImport("opencv_core246", CallingConvention = CallingConvention.Cdecl)]
public static extern void cvCopy(IntPtr src, IntPtr dst, IntPtr mask);

[DllImport("opencv_core246", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cvCloneImage(IntPtr src);

[DllImport("opencv_core246", CallingConvention = CallingConvention.Cdecl)]
public static extern void cvReleaseImage(ref IntPtr image);
}

结果双核2双T6600 2.2 GHz

A.缓冲拷贝=假;copyBitmap =假;刷新=假;

这是更简单的配置。依次检索每个帧,进行操作(在现实中,它们基于同一帧,这里只是计算),然后将计算结果打印在图像的顶部,最后显示在picturebox上。

OpenCv文档说:

OpenCV 1.x函数cvRetrieveFrame和cv.RetrieveFrame返回存储在视频捕捉结构中的图像。不允许修改或释放图像!您可以使用cvCloneImage()复制框架,然后对该副本做您想做的任何事情。

但这并不妨碍我们做实验。

如果计算不密集(迭代次数少,N),那么一切都很好,我们操纵非托管帧检索器拥有的图像缓冲区这一事实在这里不会造成问题。

原因可能是,如果有人修改其结构(而不是它的值)或异步操作而没有意识到它,他们可能建议不动缓冲区。现在我们检索帧并依次修改它们的内容。

如果N增加(N=1000000或更多),当每秒的帧数不高时,例如人工光和低曝光时,一切似乎都正常,但过了一段时间,视频就会滞后,图像也会闪烁。随着更高的帧率,闪烁出现从一开始,即使是当视频仍然是流动的。

这是因为在控件上显示图像的机制(或刷新或其他任何东西)都是异步的,而且picturebox在获取其数据缓冲区时会同时被摄像机修改,删除图形吗?

还是有别的原因?

为什么图像会以这种方式延迟,也就是说,由于计算而造成的延迟只会在尚未完成计算时跳过摄像机接收到的帧,实际上只会降低帧速率;或者,所有帧都会被接收,而由于计算而导致的延迟会使系统处理几分钟前得到的图像,因为需要处理的图像队列随着时间的推移而增加。

相反,观察到的行为在两者之间似乎是混合的:有几秒钟的延迟,但随着捕获过程的进行,这种延迟似乎不会增加太多。

B。缓冲区拷贝=真;copyBitmap =假;刷新=假;

在这里,我按照OpenCv文档的建议,将缓冲区深拷贝到第二个缓冲区。

什么都没变。第二个缓冲区在运行期间不会更改内存中的地址。

C.缓冲区拷贝=假;copyBitmap =真;刷新=假;

现在,每次在内存中分配新空间时,都会生成位图的(深度)副本。

闪烁的效果已经消失了,但是在一段时间之后,滞后就会继续出现。

D缓冲器拷贝=假;copyBitmap =假;刷新=真;

和以前一样。

请帮我解释一下这些结果!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-05-08 11:48:31

请允许我坦率地说,理解你所有问题的细节有点乏味,但让我提出几点,以帮助你分析你的结果。

在A种情况下,您说您直接在缓冲区上执行计算。文档中说您不应该这样做,所以如果您这样做了,您可以期望得到未定义的结果。OpenCV假设你不会碰它,所以它可能会做一些事情,比如突然删除内存的那一部分,让其他应用程序来处理它等等。它看起来很正常,但你永远无法确定,所以不要这么做*拍你的手腕*特别是,如果你的处理需要很长时间,当你正在处理它的时候,相机可能会覆盖它的缓冲区。

您应该做的方法是在执行任何操作之前复制缓冲区。这将给你一段记忆,这是你想做的任何事。您可以创建引用此内存的位图,并在不再需要时手动释放内存。

如果您的处理速率(每秒处理的帧)小于摄像机每秒捕获的帧数,则必须期望一些帧将被删除。如果您想要显示处理过的图像的实时视图,它将滞后,没有简单的方法可以绕过它。如果您的应用程序处理一个流畅的视频(例如,如果您跟踪一个对象,这可能是必要的),那么请考虑将视频存储到磁盘,这样您就不必实时处理了。您也可以考虑多线程一次处理多个框架,但是活动视图会有延迟。

顺便问一下,您不使用EmguCV有什么特别的原因吗?它有相机的抽象和一个系统,只要摄像机捕捉到新的帧,就会引发事件。这样,您就不需要在后台线程上连续调用cvQueryFrame

票数 2
EN

Stack Overflow用户

发布于 2015-05-06 07:16:18

我认为您的UnmanageCopy方法仍然有问题,因为您只在第一次调用映像时复制它,然后再复制它。我认为每次复制都需要执行cvCloneImage(f),因为复制只执行浅拷贝,而不是像您想象的那样深拷贝。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/29881151

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档