圖像獲取: 重複播放捕捉到的圖像序列

這個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 事件中將一個按鈕設置為可用狀態。

程序窗口如圖所示:

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.