gpt4 book ai didi

c# - 在 Picturebox 上显示从相机检索到的位图的实验

转载 作者:太空狗 更新时间:2023-10-29 19:45:16 31 4
gpt4 key购买 nike

在我的代码中,我使用指向非托管对象的指针从相机中检索帧,对其进行一些计算,然后在图片框控件上将其可视化。
在我进一步了解这个应用程序的所有细节之前,我想确保这个过程的基本代码是好的。我特别想:
- 保持执行时间最短并避免不必要的操作,例如 复制不必要的图像。我只想保留必要的 操作
- 了解每帧计算过程中的延迟是否会对图像的显示方式产生不利影响(即如果它没有按照我的预期打印)或跳过某些图像
- 防止更严重的错误,例如由于内存或线程管理或图像显示引起的错误。
为此,我设置了几行实验代码(如下),但我无法解释我发现的结果。如果你有 OpenCv 的可执行文件,你可以自己尝试一下。

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 Duo T6600 2.2 GHz]:

一个。缓冲区复制=假;复制位图=假;刷新=假;
这是更简单的配置。依次检索每一帧,进行运算(实际是基于同一帧,这里只是计算),然后将计算结果打印在图像上,最后显示在图片框上。< br/>OpenCv 文档说:

OpenCV 1.x functions cvRetrieveFrame and cv.RetrieveFrame return image stored inside the video capturing structure. It is not allowed to modify or release the image! You can copy the frame using cvCloneImage() and then do whatever you want with the copy.

但这并不妨碍我们做实验。
如果计算强度不高(迭代次数少,N),一切都很好,我们操纵非托管帧检索器拥有的图像缓冲区这一事实在这里不会造成问题。
原因可能是他们建议不要触及缓冲区,以防人们修改它的结构(而不​​是它的值)或在没有意识到的情况下异步执行操作。现在我们依次检索帧并修改它们的内容。
如果增加 N(N=1000000 或更多),当每秒帧数不高时,例如在人造光和低曝光下,一切似乎都正常,但过一会儿视频就会滞后并且图形会在上面留下深刻印象正在闪烁。对于更高的帧速率,即使视频仍然流畅,闪烁也会从一开始就出现。
这是因为在控件上显示图像(或刷新或其他)的机制在某种程度上是异步的,当图片框正在获取其数据缓冲区时,它同时被相机修改,删除图形?
还是有其他原因?
为什么图像会以这种方式滞后,即我预计由于计算而导致的延迟只会在计算尚未完成时跳过相机接收到的帧,并且实际上只会降低帧速率;或者,所有帧都已接收,并且由于计算导致的延迟使系统处理几分钟前获得的图像,因为要处理的图像队列会随着时间的推移而增加。
相反,观察到的行为似乎是两者的混合体:有几秒钟的延迟,但随着捕获过程的进行,延迟似乎并没有增加多少。

B。缓冲区复制=真;复制位图=假;刷新=假;
在这里,我按照 OpenCv 文档的建议将缓冲区的深度复制到第二个缓冲区中。
没有什么改变。第二个缓冲区在运行期间不会更改其在内存中的地址。

C。缓冲区复制=假;复制位图=真;刷新=假;
现在,每次在内存中分配一个新空间时,位图的(深)副本就会被分配。
闪烁效果消失了,但在一定时间后仍会出现滞后现象。

D。缓冲区复制=假;复制位图=假;刷新=真;
像以前一样。

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

最佳答案

坦率地说,了解您问题的所有细节有点乏味,但让我提出几点来帮助您分析结果。

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

你应该做的是在做任何事情之前复制缓冲区。这会给你一段内存,你可以随心所欲地做任何事。您可以创建一个引用此内存的位图,并在不再需要时手动释放内存。

如果您的处理速率(每秒处理的帧数)小于相机每秒捕获的帧数,则您必须预料到会丢失一些帧。如果您想显示处理过的图像的实时 View ,它会滞后并且没有简单的解决方法。如果您的应用程序处理流畅的视频至关重要(例如,如果您正在跟踪一个对象,这可能是必要的),那么请考虑将视频存储到磁盘,这样您就不必实时处理。您也可以考虑使用多线程同时处理多个帧,但实时取景会有延迟。

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

关于c# - 在 Picturebox 上显示从相机检索到的位图的实验,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29881151/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com