如何使用 Kinect 來錄影,錄製儲存彩色影片(For Windows SDK V1)
今天主題是「如何使用 Kinect 來錄影,錄製儲存彩色影片」,
這話題是很妙的,因為一旦打開這個"影像"潘多拉的盒子,
KT 就有說不完的故事,所以KT在 Kinect 教學目錄 未來章節裡,
加了很多有關影像處理的主題 ,如果時間允許的話,
KT 很願意跟大家分享,過去在影像處理,學到的一些小小心得,
如:圖片、圖形、文字和人臉等影像辨識,這些都是非常有趣的話題。
但坦白說這些影像處理概念,其實早在以前,
用一台小小的 Webcam就都可以辦到了,
所以你知道的,一台小小的Webcam 就可以讓KT 玩的很開心,哈哈哈~
而今天我們拿來跟 Kinect 做結合,看能不能擦出不一樣的火花,
而談到影像處理,當然不能不去提到 OpenCV (開放式源碼電腦視覺),
但KT怕範圍太廣,發散掉,所以KT也只大概介紹一下OpenCV的學習資源,
所以對影像處理有興趣的玩家,
可以去「OpenCV 英文官網」 或 「OpenCV中文官網」 瞧一瞧,
超過500項的應用函數:
英文官網上推薦的 OpenCV 相關書籍
Learning OpenCV: Computer Vision with the OpenCV Library
OpenCV 2 Computer Vision Application Programming Cookbook
而我們今天則是要使用,特地為.NET平台量身訂做,
包裝OpenCV處理影像函數的 Emgu CV ,
細節部分可以連過去官網看詳細介紹。
Emgu CV 架構圖 (最少瞄一下橘色和藍色字):
所以我們今天處裡影像串流部分,
則是要將影像框架(Frame) 丟給 Emgu CV 幫我們處理,
將影像框架(Frame) 組合成一部影片,
這樣做法,好比像是拍照一樣,將每張圖片依照時間先後順序存起來,
然後快速每秒以30張(30 FPS,Frames per second) 或15張(15 FPS)連續撥放,
看起來就像動畫一樣。
所以我們會去引用到 Emgu CV裡的Emgu.CV、Emgu.UI、Emgu.Util等dll,
所以在專案裡,要記得加入參考:
而因為怕客戶端,電腦裡不見得有安裝 Emgu CV,
所以你可以將這些dll一起包到安裝檔裡或放在bin資料夾與執行檔在一起,
縮短減少環境造成的臭蟲(bug)率,
否則換了電腦,就像移植心臟一樣,不一定能動(執行)。
而我們需要Emgu CV裡面的 image 型別,所以要特別要將他給擴展出來,
否則會於內建C# image 型別衝突,這邊KT採用Jarrett Webb & James Ashley在
Beginning Kinect Programming with the Microsoft Kinect SDK ,
所附的EmguImageExtensions.cs來解決這件事。
這次程式設計的畫面:
右邊紅色圓圈錄影按鈕按下,會先詢問儲存位置
(當然你也可以改成固定儲存位置與自動命名影片檔名),
選擇確定儲放位置後,即開始錄影,
此時紅色圓圈錄影按鈕,會變成停止錄影按鈕,
所以按一下停止錄影按鈕,即會停止錄影。
而左邊調整Kinect上下角度設定部分,麻煩參考:
1.如何調整 Kinect 底座馬達上下旋轉角度 (For Windows SDK V1)
2.如何使用 Kinect 來拍照,儲存彩色影像圖片(For Windows SDK V1)
KT這邊就不在重覆贅述了。
而KT這邊採用Emgu CV 2.3.0 ,最新且是穩定版 (Stable Version),
詳細API 使用說明文件可以參考:Emgu CV 2.3.0 Online Documentation
右邊紅色圓圈錄影按鈕按下,會先詢問儲存位置
(當然你也可以改成固定儲存位置與自動命名影片檔名),
選擇確定儲放位置後,即開始錄影,
此時紅色圓圈錄影按鈕,會變成停止錄影按鈕,
所以按一下停止錄影按鈕,即會停止錄影。
而左邊調整Kinect上下角度設定部分,麻煩參考:
1.如何調整 Kinect 底座馬達上下旋轉角度 (For Windows SDK V1)
2.如何使用 Kinect 來拍照,儲存彩色影像圖片(For Windows SDK V1)
KT這邊就不在重覆贅述了。
而KT這邊採用Emgu CV 2.3.0 ,最新且是穩定版 (Stable Version),
詳細API 使用說明文件可以參考:Emgu CV 2.3.0 Online Documentation
- 錄影按鈕與停止按鈕:
//錄影按鈕 private void btn_Rec(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (!_isRecording) { //開啟儲存位置詢問視窗 Microsoft.Win32.SaveFileDialog openFileDialog = new Microsoft.Win32.SaveFileDialog(); openFileDialog.FileName = "Video"; openFileDialog.DefaultExt = ".avi"; openFileDialog.Filter = "AVI文件|*.avi|所有文件|*.*"; openFileDialog.ShowDialog(); _fileName = openFileDialog.FileName; _isRecording = true;//啟動錄影 SetShootImage(1);//設定錄影時,按鈕為停止圖案 } else { //呼叫停止錄影 StopRecording(); SetShootImage(0);////設定停止錄影時,按鈕為錄影圖案 } }
- 開始錄影與停止錄影函數
//開始錄影函數 void Record(ColorImageFrame image) { if (_isRecording) { _videoArray.Add(image.ToOpenCVImage()); } } //停止錄影函數 void StopRecording() { if (!_isRecording) return; //using (VideoWriter vw = new VideoWriter(_fileName, 0, 30, 640, 480, true)) using (VideoWriter vw = new VideoWriter(_fileName, 30, 640, 480, true)) { for (int i = 0; i < _videoArray.Count(); i++) vw.WriteFrame (_videoArray[i]); } _fileName = string.Empty; _videoArray.Clear(); _isRecording = false; }
其中當按下停止錄影按鈕時,會花一點處理等待時間,
此時你可以去呼叫忙碌指示(Busy Indicator),或自己製作,
KT這邊沒加入這個,建議加入,優化User Experience (使用者介面經驗)。
影片教學:
(KT下次補上)
C# 完整程式碼:
using System; using System.Linq; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using Microsoft.Kinect; using System.Collections.Generic; using Emgu.CV; using Emgu.CV.VideoSurveillance; using Emgu.CV.Structure; using System.Drawing.Imaging; using System.Drawing; using System.ComponentModel; using System.Runtime.InteropServices; using ImageManipulationExtensionMethods; namespace KinectRecordingVideo_Demo { public partial class MainWindow : Window { //宣告Kinect裝置變數名稱 KinectSensor sensor = null; byte[] pixelData; bool _isRecording = false; public MainWindow() { InitializeComponent(); //宣告視窗載入與卸載事件 this.Loaded += new RoutedEventHandler(MainWindow_Loaded); this.Unloaded += new RoutedEventHandler(MainWindow_Unloaded); } void MainWindow_Loaded(object sender, RoutedEventArgs e) { InitialKinect();//當視窗載入時,初始化 Kinect裝置 } void MainWindow_Unloaded(object sender, RoutedEventArgs e) { UninitiaKinect();//視窗關閉時,關閉 Kinect裝置 } //初始化 Kinect裝置 private void InitialKinect() { //Save.Visibility = Visibility.Hidden; if (sensor != null) { UninitiaKinect(); } sensor = KinectSensor.KinectSensors[0]; sensor.Start(); sensor.ColorFrameReady += runtime_VideoFrameReady; sensor.ColorStream.Enable(); } //關閉 Kinect裝置 private void UninitiaKinect() { if (sensor == null) { return; } sensor.ColorFrameReady -= runtime_VideoFrameReady; sensor.Stop(); sensor = null; } //彩色影像處理函數 void runtime_VideoFrameReady(object sender, ColorImageFrameReadyEventArgs e) { bool receivedData = false; using (ColorImageFrame CFrame = e.OpenColorImageFrame()) { if (CFrame == null) { // The image processing took too long. More than 2 frames behind. } else { pixelData = new byte[CFrame.PixelDataLength]; CFrame.CopyPixelDataTo(pixelData); receivedData = true; Record(CFrame); } } if (receivedData) { //將彩色影像資料,轉成點陣圖(Bitmap) BitmapSource source = BitmapSource.Create(640, 480, 96, 96, PixelFormats.Bgr32, null, pixelData, 640 * 4); //將Bitmap 影像秀到Image控制項上 videoImage.Source = source; } } string _fileName; List> _videoArray = new List >(); //開始錄影函數 void Record(ColorImageFrame image) { if (_isRecording) { _videoArray.Add(image.ToOpenCVImage ()); } } //停止錄影函數 void StopRecording() { if (!_isRecording) return; //using (VideoWriter vw = new VideoWriter(_fileName, 0, 30, 640, 480, true)) using (VideoWriter vw = new VideoWriter(_fileName, 30, 640, 480, true)) { for (int i = 0; i < _videoArray.Count(); i++) vw.WriteFrame (_videoArray[i]); } _fileName = string.Empty; _videoArray.Clear(); _isRecording = false; } //錄影按鈕 private void btn_Rec(object sender, System.Windows.Input.MouseButtonEventArgs e) { if (!_isRecording) { //開啟儲存位置詢問視窗 Microsoft.Win32.SaveFileDialog openFileDialog = new Microsoft.Win32.SaveFileDialog(); openFileDialog.FileName = "Video"; openFileDialog.DefaultExt = ".avi"; openFileDialog.Filter = "AVI文件|*.avi|所有文件|*.*"; openFileDialog.ShowDialog(); _fileName = openFileDialog.FileName; _isRecording = true;//啟動錄影 SetShootImage(1);//設定錄影時,按鈕為停止圖案 } else { //呼叫停止錄影 StopRecording(); SetShootImage(0);////設定停止錄影時,按鈕為錄影圖案 } } //===設定 Shoot 狀態圖片===Start=== void SetShootImage(int State) { // Create source. BitmapImage bi = new BitmapImage(); // BitmapImage.UriSource must be in a BeginInit/EndInit block. bi.BeginInit(); if (State == 0) { //未 Rec,秀shoot bi.UriSource = new Uri(@"/Image/Rec.png", UriKind.RelativeOrAbsolute); } else if (State == 1) { //已 Rec,秀Stop bi.UriSource = new Uri(@"/Image/Stop.png", UriKind.RelativeOrAbsolute); } bi.EndInit(); // Set the image source. Rec.Source = bi; } //調整Kinect上下角度 private void AdjustAngle(object sender, RoutedEventArgs e) { Btn_AdjustAngle.IsEnabled = false;//將按鈕設為失能(Disable),等角度整套設定完再致能(Enabled) //設定角度 if (sensor != null && sensor.IsRunning) { //將滑桿的值存到 ElevationAngle sensor.ElevationAngle = (int)slider1.Value; } Btn_AdjustAngle.IsEnabled = true;//恢復按鈕設定功能 } } }
範例程式碼下載:
相關文章參考:
HKT線上教學教室 - Kinect 教學目錄