图像获取: 重复播放捕捉到的图象序列

这个 C# 示例程序将介绍如何把某个时刻之前的两秒钟捕捉至图象序列中去。
语言:.NET C#/Visual Basic
版本:3.0.3
发布于:2007年6月8日
作者:IC Imaging Control 技术支持部
系统要求:IC Imaging Control >3.0.3, Visual Studio™ 2005
由WDM数据流类驱动程序驱动的相机、视频转换器或图像采集卡

前言

这个示例程序可以把视频中某个事件之前的两秒钟捕捉至一个图象序列中。 用户可以通过点击按钮触发事件, 之后用户可以手动或自动回放这个图象序列。

类似的程序可被用于各种体育赛事中回放运动员的动作。 例如跳远运动员在跳入沙坑后,教练点击 Capture 按钮,这样程序就可以回放运动员落地前的几秒钟,尤其是起跳的时刻, 从而教练和运动员就可以研习起跳动作技巧等。 同样地,高尔夫和网球选手的动作也可通过这个程序回放。

这个示例程序中包含了以下几点技术:

  • 如何快速地从ImageBuffers中获取数据。
  • 如何使用ImageBuffer.SampleStartTime在环形缓存中找到最早的一帧图像。
  • 如何通过一个 DelegateImageAvailable 事件中将一个按钮设置为可用状态event.

程序窗口如图所示:

Image Sequence

程序开始后,用户可通过点击Device 按钮选择一台视频捕捉设备, 通过点击Start Live按钮开始播放实时影像。 两秒钟后,视频的第一帧图像就已被存入环形缓存了。 下一步,程序将Capture按钮设为可用,这样用户就可以在需要的时候,通过点击这个按钮触发捕捉事件了。 点击 Capture 按钮后,程序停止现场视频流,把捕捉图像中的最早一帧显示在IC Imaging Control窗口中。 这张最早的帧就是所要显示的第一帧图像, IC Imaging Control 下方的时间刻度是用来在捕捉到的图象序列中移动播放点的(即前进或后退)。 用户可以点击Auto Repeat按钮使程序自动反复播放图象序列。

程序主类所需变量

点击按钮 Capture 后,程序将把环形缓存中最早一帧图像的索引号保存至变量 StartIndex 中, 而后首先显示最早一帧图像,或把IC Imaging Control 窗口下方的时间条拖动至0也可显示最早一帧图像。

变量 Image 存储的是环形缓存的引用,图像序列都被保存在环形缓存中,这样就可使得程序直接并快速地访问环形缓存。

变量FrameCount用于记录开始播放实时影像之后所捕捉到的帧数目。 当图像环形缓存开始存入图像后(可由FrameCount表明),按钮 Capture 就处于可用状态。 这就意味着程序已经准备就绪,可以捕捉并显示一个完整序列了。

自动反复显示捕捉到的图象序列时,TimerCurrentImage 的作用相当于帧索引号。 timer 事件自动显示这些帧, 在这个事件中,TimerCurrentImage 将自加,用以遍历环形缓存中需要被显示的图像。

变量Seconds决定需要被捕捉的图象序列的长度, 在本例中,它被设为 2。 当然也可根据需要将其设为其它值。 变量Seconds的最大值需根据内存大小和视频设备的帧速率决定。

[VB.NET]
' The variable "StartImage" is used to save the index of the oldest image in the image ring buffer.
Dim StartImage As Integer

' The variable "TimerCurrentImage" is used for the index of the currently displayed image,
' due auto repeat of the image sequence is running.
Dim TimerCurrentImage As Integer

' "Images" will contain a copy of the image ring buffer, in order to get faster acces to the images.
Dim Images() As TIS.Imaging.ImageBuffer


' The variable "FrameCount" is used to count the capture frames. If "FrameCount"
' is greater than the image ring buffer size, the "btnCapture" will be enabled and
' the user knows the image sequence in the ring buffer is now filled up completely.
' FrameCount will be set to 0 in btnLiveVideo_Click after the live video has been started.
' It is incremented in the ImageAvailable event handler.
Dim FrameCount As Integer

' "Seconds" determins the length of the image ring buffer in seconds.
Dim Seconds As Integer
[C#]
// The variable "StartImage" is used to save the index of the oldest image in the image ring buffer.
int StartImage;

// The variable "TimerCurrentImage" is used for the index of the currently displayed image,
// due auto repeat of the image sequence is running.
int TimerCurrentImage;

// "Images" will contain a copy of the image ring buffer, in order to get faster acces to the images.
TIS.Imaging.ImageBuffer[] Images;

// The variable "FrameCount" is used to count the capture frames. If "FrameCount"
// is greater than the image ring buffer size, the "btnCapture" will be enabled and
// the user knows the image sequence in the ring buffer is now filled up completely.
// FrameCount will be set to 0 in btnLiveVideo_Click after the live video has been started.
// It is incremented in the ImageAvailable event handler.
int FrameCount = 0;

// "Seconds" determins the length of the image ring buffer in seconds. If the image sequence should
// contain more or less seconds, only the value of "Seconds" need to be changed here.
int Seconds = 10;

计算图像环形缓存的大小

环形缓存的大小由视频设备的帧速率(例如30帧每秒)及所需捕捉序列的长度(例如2秒)决定, 即用帧速率乘以时间长度。将所得值赋予icImagingControl1.ImageRingBufferSize。 这项工作可在选择视频设备后完成, 在本例中,这项工作是由 btnDevice_Click 按钮处理程序完成的。 下面是如何选择设备及计算环形缓存大小的代码:

[VB.NET]
' Show device settings dialog.
IcImagingControl1.ShowDeviceSettingsDialog()

If IcImagingControl1.DeviceValid Then
    ' The last seconds before the capture button has been
    ' clicked should be saved in the image ring buffer. Thus
    ' the currently set frame rate multiplied by the value of "Seconds".
    IcImagingControl1.ImageRingBufferSize = Convert.ToInt32(IcImagingControl1.DeviceFrameRate * Seconds)
[C#]
icImagingControl1.ShowDeviceSettingsDialog();

if (icImagingControl1.DeviceValid)
{
    // The last seconds before the capture button has been
    // clicked should be saved in the image ring buffer. Thus
    // the currently set frame rate multiplied by the value of "Seconds".
    icImagingControl1.ImageRingBufferSize = Convert.ToInt32(icImagingControl1.DeviceFrameRate
                                                            * Seconds);

处理“捕捉”事件

用户点击Capture按钮后程序停止播放现场视频。 如需在点击按钮后某个时刻继续捕捉,可在调用icImagingControl1.LiveStop之前调用Sleep。 每一帧都被自动存入环形缓存,因此,在过去的两秒钟内捕捉到的图像都可被用于图象处理了。 为了能够快速访问环形缓存中的图像,环形缓存的引用被拷贝至变量Images:

[VB.NET]
' If the image sequence should contain images captures after the "Capture"
' button has been clicked, a call to "Sleep()" should be inserted before
' "LiveStop()" is called, e.g.:
'  System.Threading.Thread.Sleep(1000)
' This would the application continue capture one second after the "Capture"
' button has been clicked.

' Now stop the live video stream.
IcImagingControl1.LiveStop()
btnLiveVideo.Text = "Start Live"
' Create a copy of the Imagesbuffers to get a faster access to the images.
Images = IcImagingControl1.ImageBuffers
[C#]
// If the image sequence should contain images captures after the "Capture"
// button has been clicked, a call to "Sleep()" should be inserted before
// "LiveStop()" is called, e.g.:
//  System.Threading.Thread.Sleep(1000);
// This would the application continue capture one second after the "Capture"
// button has been clicked.

// Now stop the live video stream.
icImagingControl1.LiveStop();
btnLiveVideo.Text = "Start Live";
// Copy the reference of the ImageBuffers to get a faster access to the images.
Images = icImagingControl1.ImageBuffers;

环形缓存实际上是一个图象缓存数组,IC Imaging Control 将从视频设备接收到的第一帧图像存入缓存中的第一个单位,将第二帧存入第二个单元,一次类推,当所有单元都被存入图像帧后,下一帧图像将被存入第一个单元并覆盖其中之前的图像帧。 因此需要搞清楚环形缓存中最早的一帧图像在哪。 这项工作可通过环形缓存中任一图像帧的属性SampleStartTime实现, 该属性记录的是这帧图像到达计算机的时刻。 这样,程序就需要遍历环形缓存,找出具有最小SampleStartTime值的图像帧, 而后将这个缓存单元的索引号存入变量 StartIndex, 这个变量将指示第一帧被显示的图像所在的位置。 上述过程的具体计算方法:

[VB.NET]
' Find the oldest image in the ring buffer. Its index is saved in
' the variable "StartImage".
Dim i As Integer
Dim MinStartTime As Double
StartImage = 0
MinStartTime = IcImagingControl1.ImageBuffers(StartImage).SampleStartTime
For i = 1 To IcImagingControl1.ImageRingBufferSize - 1
    If Images(i).SampleStartTime < MinStartTime Then
        StartImage = i
        MinStartTime = Images(StartImage).SampleStartTime
    End If
Next
[C#]
// Find the oldest image in the ring buffer. Its index is saved in
// the variable "StartImage".
double MinStartTime;

StartImage = 0;
MinStartTime = icImagingControl1.ImageBuffers[StartImage].SampleStartTime;

for (int i = 1; i < (icImagingControl1.ImageRingBufferSize - 1); i++)
{
    if (Images[i].SampleStartTime < MinStartTime)
    {
        StartImage = i;
        MinStartTime = Images[StartImage].SampleStartTime;
        break;
    }
}

为了在遍历环形缓存时正确显示图像,程序需要根据StartIndex计算索引号。 例如,图像环形缓存有30个单元,最早的一帧图像在第25个单元。如果程序从序号0开始显示图象序列,将首先显示25号图像,而后显示序号"1"图像,就是第26个缓存单元中的图像帧。因此 StartIndex 的值 25 就被加入所需显示图像的序列号。

当序号大于 5 时,缓存单元序号就会大于缓存的大小(这里是30)。 (环形缓存单元序列号从0开始,即 0-29)。 这时,程序就必须处理溢出, 即计算出的缓存单元序列号减去环形缓存大小。 例如,图象序列中应被显示的图像是第11帧,StartIndex 为 25,环形缓存大小为30。 计算出的缓存单元序列号就是 11 + 25 = 36, 36 大于 30,因此用36减去30, 所得即为图象序列中第11帧图像所在单元6。

上述过程的源代码:

[VB.NET]
Private Sub DisplayTheImage(ByVal Index As Integer)
    Dim i As Integer
    With IcImagingControl1
        If Not .LiveVideoRunning Then
            i = StartImage + Index
            ' Handling a possible overflow, in case the i is greater then the ring buffer size.
            If i >= .ImageRingBufferSize Then
                i = i - .ImageRingBufferSize
            End If
            .DisplayImageBuffer(Images(i))
        End If
    End With
End Sub
[C#]
private void DisplayTheImage(int Index)
{
    int i;
    if (!icImagingControl1.LiveVideoRunning)
    {
        i = StartImage + Index;
        // Handling a possible overflow, in case the i is greater then the ring buffer size.
        if (i >= icImagingControl1.ImageRingBufferSize)
        {
            i = i - icImagingControl1.ImageRingBufferSize;
        }
        icImagingControl1.DisplayImageBuffer(Images[i]);
    }
}

将按钮 "Capture" 设为可用

当捕捉到足够多的图像帧后,程序将把按钮Capture设为可用状态。 在现场视频开始时,FrameCount 被赋值为0。 在IC Imaging Control的 ImageAvailable 事件中,FrameCount 自加1,直至它的值大于环形缓存的大小, 此时,环形缓存中所有单元都已存入图像帧,程序可以开始捕捉图象序列了, Capture 按钮被激活。

由于使用不同的线程,所以不推荐在ImageAvailable事件中直接改变按钮状态,这里需要使用一个DelegateImageAvailable事件是在 IC Imaging Control 的抓取线程中被调用的, 而按钮状态的改变则是在程序线程中完成的,跨线程调用控件可导致程序停止。

首先创建用于激活按钮Capture的函数:

[VB.NET]
Private Sub EnableCaptureButton()
    btnCapture.Enabled = True
End Sub
[C#]
private void EnableCaptureButton()
{
    btnCapture.Enabled = true;
}

然后声明 Delegate :

[VB.NET]
Private Delegate Sub EnableCaptureDelegate()
[C#]
public delegate void EnableCaptureDelegate();

ImageAvailable 事件中,变量 FrameCount 自加1,直至其值大于环形缓存容量。 而后使用BeginInvoke调用Delegate, 它以参数的形式接收所调用函数: EnableCaptureButton

[VB.NET]
Private Sub IcImagingControl1_ImageAvailable(ByVal sender As System.Object, ByVal e As TIS.Imaging.ICImagingControl.ImageAvailableEventArgs) Handles IcImagingControl1.ImageAvailable
    If btnCapture.Enabled = False Then
        If FrameCount > IcImagingControl1.ImageRingBufferSize Then
            ' Now it is time to enable the capture button.
            BeginInvoke(New EnableCaptureDelegate(AddressOf EnableCaptureButton))
        Else
            FrameCount = FrameCount + 1
        End If
    End If
End Sub
[C#]
private void icImagingControl1_ImageAvailable(object sender, TIS.Imaging.ICImagingControl.ImageAvailableEventArgs e)
{
    if (btnCapture.Enabled == false)
    {
        if (FrameCount > icImagingControl1.ImageRingBufferSize)
        {
            // Now it is time to enable the capture button.
            BeginInvoke(new EnableCaptureDelegate(EnableCaptureButton));
        }
        else
        {
            FrameCount++;
        }
    }
}

相关源代码示例

责任声明
IC Imaging Control 源代码库中的所有代码均只用于教学目的,The Imaging Source Europe GmbH 作为IC Imaging Control的开发制造商,不对任何由于使用本文或其中源代码所产生的后果承担责任。

该网站为The Imaging Source网络的一部分。其它的站点包括 公司, Imaging, 天文相机, Astronomy Cameras Blog, Blog caméras d'astronomie, 天文相机有奖竞答, TX Text Control, LiveDocx, phpLiveDocxForum.