Kinect的麥克風陣列內含了四組麥克風,分為兩組,一組來取得聲音並進行消除雜音等演算法處理之後,可提供另一組聲音辨識的功能。
最近因為上課需要,所以要在寫Kinect的相關程式,當然這篇就是拿來練功的,不過這次是透過『Kinect體感程式探索-使用C#』去學習關於Kinect的聲音處理部份。而不再透過官方的API
這樣學起來也比較快,省時間XDDD
照慣例,還是要分析一下程式碼,這樣才能有所學習到
主要宣告的有以下幾種,最關鍵在於imageRect和imageDataArray以及soundSample
imageRect是一個空矩形的結構,imageDataArray是用來記錄波形位置,soundSample則是用來記錄聲音
private KinectSensor sensor;
private KinectAudioSource audioSource;
private WriteableBitmap writeableBitmap;
private Int32Rect imageRect;
private short[] imageDataArray;
private byte[] soundSample;
int witdh;
int height;
來看一下程式載入點的程式碼!
public MainWindow()
{
InitializeComponent();
witdh = (int)DisplaySound.Width;
height = (int)DisplaySound.Height;
imageDataArray = new short[witdh * height];
for (int index = 0; index < imageDataArray.Length; index++)
{
imageDataArray[index] = 32767;
}
soundSample = new byte[witdh * 2];
writeableBitmap = new WriteableBitmap(witdh, height, 96, 96,
PixelFormats.Gray16, null);
imageRect = new Int32Rect(0, 0, witdh, height);
DisplaySound.Source = writeableBitmap;
KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
writeableBitmap.WritePixels(imageRect,
imageDataArray, 640 * 2, 0);
}
imageDataArray是用來記錄波形位置,所以他的陣列大小為『高乘上寬』,並且將值設為32767,32767為short最高的數值
imageDataArray = new short[witdh * height];
for (int index = 0; index < imageDataArray.Length; index++)
{
imageDataArray[index] = 32767;
}
並將soundSample陣列大小設為『寬度乘上2』
soundSample = new byte[witdh * 2];
透過writeableBitmap將背景設定為灰色,dpi為96,DisplaySound來源指定為writeableBitmap
writeableBitmap = new WriteableBitmap(witdh, height, 96, 96,
PixelFormats.Gray16, null);
DisplaySound.Source = writeableBitmap;
只要從Kinect取得的Sensor狀態有改變,則會觸發
KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
該方法主要偵測狀態如果連結了,但sensor本身為空,則會將KinectSensor指定給sensor,並且啟動Kinect硬體。如果為中斷連結,則將Kinect啟動的硬體都關閉
private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch (e.Status)
{
case KinectStatus.Connected:
if (this.sensor == null)
{
this.sensor = e.Sensor;
this.sensor.Start();
}
break;
case KinectStatus.Disconnected:
this.sensor.Stop();
this.sensor = null;
break;
}
}
在程式載入的那一刻會觸發『Window_Loaded』,這個事件會判斷電腦接上Kinect了嗎?接上了KinectSensor.KinectSensors.Count會大於0,如果沒有的話則會顯示出『請將Kinect接上電腦』,如果接上就將第一個接上Kinect的機器控制權交由sensor去處理,並且啟動硬體
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (KinectSensor.KinectSensors.Count == 0)
{
MessageBox.Show("請將Kinect接上電腦");
}
else if (KinectSensor.KinectSensors[0].Status == KinectStatus.Connected)
{
this.sensor = KinectSensor.KinectSensors[0];
this.sensor.Start();
}
}
在滑鼠按下左鍵時,會觸發『DisplaySound_MouseLeftButtonDown』事件,該事件會啟動執行緒,並且將執行緒的執行權限調為優先,並在背後執行
private void DisplaySound_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Thread thread = new Thread(new ThreadStart(ShowWave));
thread.Priority = ThreadPriority.Highest;
thread.IsBackground = true;
thread.Start();
}
執行緒主要執行的方法是ShowWave,所以我們來看看該方法吧!
private void ShowWave()
{
audioSource = sensor.AudioSource;
var audioStream = audioSource.Start();
short soundLevel;
int imageY;
int heightBias = height / 2;
int imagePosition;
while (audioStream.Read(soundSample, 0, soundSample.Length) > 0)
{
for (int index = 0; index < imageDataArray.Length; index++)
{
imageDataArray[index] = 32767;
}
int soundSampleIndex = 0;
for (int imageX = 0; imageX < witdh; imageX++)
{
soundLevel = (short)(soundSample[soundSampleIndex]
| (soundSample[soundSampleIndex + 1] << 8));
imageY = (soundLevel * height) /
65535 + heightBias;
imageY = height - imageY;
imageY = imageY * witdh;
imagePosition = imageX + imageY;
if (imagePosition > 307199)
imagePosition = 307199;
imageDataArray[imagePosition] = 0;
soundSampleIndex += 2;
}
Dispatcher.Invoke(new Action(() => UpdateDisplay()));
}
}
透過audioSource去取得目前Kinect的Audio來源,並啟動
audioSource = sensor.AudioSource;
var audioStream = audioSource.Start();
透過一個While不斷地從Kinect取得音源
while (audioStream.Read(soundSample, 0, soundSample.Length) > 0)
在將得的值去換算,得到高低差
for (int imageX = 0; imageX < witdh; imageX++)
{
soundLevel = (short)(soundSample[soundSampleIndex]
| (soundSample[soundSampleIndex + 1] << 8));
imageY = (soundLevel * height) /
65535 + heightBias;
imageY = height - imageY;
imageY = imageY * witdh;
imagePosition = imageX + imageY;
if (imagePosition > 307199)
imagePosition = 307199;
imageDataArray[imagePosition] = 0;
soundSampleIndex += 2;
}
為什麼imagePosition大於307199的數值就設定為307199,你可以去算『640乘上480』數值為多少?
並且不斷地去呼叫UpdateDisplay,更新DisplaySound的圖形內容,就會看到整個音波圖了
Dispatcher.Invoke(new Action(() => UpdateDisplay()));
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="480" Width="640" Loaded="Window_Loaded">
<Grid>
<Image x:Name="DisplaySound" Height="480" Width="640"
MouseLeftButtonDown="DisplaySound_MouseLeftButtonDown"/>
</Grid>
</Window>
程式碼:
using Microsoft.Kinect;
using System;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfApplication1
{
/// <summary>
/// MainWindow.xaml 的互動邏輯
/// </summary>
public partial class MainWindow : Window
{
private KinectSensor sensor;
private KinectAudioSource audioSource;
private WriteableBitmap writeableBitmap;
private Int32Rect imageRect;
private short[] imageDataArray;
private byte[] soundSample;
int witdh;
int height;
public MainWindow()
{
InitializeComponent();
witdh = (int)DisplaySound.Width;
height = (int)DisplaySound.Height;
imageDataArray = new short[witdh * height];
for (int index = 0; index < imageDataArray.Length; index++)
{
imageDataArray[index] = 32767;
}
soundSample = new byte[witdh * 2];
writeableBitmap = new WriteableBitmap(witdh, height, 96, 96,
PixelFormats.Gray16, null);
imageRect = new Int32Rect(0, 0, witdh, height);
DisplaySound.Source = writeableBitmap;
KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
writeableBitmap.WritePixels(imageRect,
imageDataArray, 640 * 2, 0);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (KinectSensor.KinectSensors.Count == 0)
{
MessageBox.Show("請將Kinect接上電腦");
}
else if (KinectSensor.KinectSensors[0].Status == KinectStatus.Connected)
{
this.sensor = KinectSensor.KinectSensors[0];
this.sensor.Start();
}
}
private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch (e.Status)
{
case KinectStatus.Connected:
if (this.sensor == null)
{
this.sensor = e.Sensor;
this.sensor.Start();
}
break;
case KinectStatus.Disconnected:
this.sensor.Stop();
this.sensor = null;
break;
}
}
private void DisplaySound_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Thread thread = new Thread(new ThreadStart(ShowWave));
thread.Priority = ThreadPriority.Highest;
thread.IsBackground = true;
thread.Start();
}
private void ShowWave()
{
audioSource = sensor.AudioSource;
var audioStream = audioSource.Start();
short soundLevel;
int imageY;
int heightBias = height / 2;
int imagePosition;
while (audioStream.Read(soundSample, 0, soundSample.Length) > 0)
{
for (int index = 0; index < imageDataArray.Length; index++)
{
imageDataArray[index] = 32767;
}
int soundSampleIndex = 0;
for (int imageX = 0; imageX < witdh; imageX++)
{
soundLevel = (short)(soundSample[soundSampleIndex]
| (soundSample[soundSampleIndex + 1] << 8));
imageY = (soundLevel * height) /
65535 + heightBias;
imageY = height - imageY;
imageY = imageY * witdh;
imagePosition = imageX + imageY;
if (imagePosition > 307199)
imagePosition = 307199;
imageDataArray[imagePosition] = 0;
soundSampleIndex += 2;
}
Dispatcher.Invoke(new Action(() => UpdateDisplay()));
}
}
private void UpdateDisplay()
{
writeableBitmap.WritePixels(imageRect,
imageDataArray, witdh * 2, 0);
}
}
}
這本書是跟學弟借的,非常感激^^
參考資料:
Kinect體感程式探索-使用C#
http://ashonehuang.pixnet.net/blog/post/20028926
http://zh.wikipedia.org/wiki/%E9%BA%A5%E5%85%8B%E9%A2%A8%E9%99%A3%E5%88%97
