この記事は、Dr.Dobb’s Go Parallel に掲載されている「Calling IPP Functions from C# Code」 (http://drdobbs.com/go-parallel/article/232300486?pgno=1) の日本語参考訳です。
インテル® インテグレーテッド・パフォーマンス・プリミティブ (インテル® IPP) は、デジタルメディアやデータ処理アプリケーション向けに高度に最適化された算術ソフトウェア関数群を含むライブラリーです。インテル® IPP 関数は、複数のスレッドと最適な SIMD 命令を使用して、ハードウェアで利用可能な命令セット・アーキテクチャーに応じて最高のパフォーマンスを実現します。C# コードからは、Platform Invoke (P/Invoke) を介してインテル® IPP 関数を呼び出すことができます。
インテル® IPP 8.0 以降には、どの C# プロジェクトからでもインテル® IPP 関数を簡単に呼び出せるように、必要なラッパーコードがすべて含まれています。ただし、このラッパーコードは言語関連のサンプルの zip ファイルに含まれているため、zip ファイルを新しいフォルダーに展開して、各インテル® IPP 関数の呼び出しに必要な C# ラッパーファイルを探す必要があります。 このサンプルは、通常 “ipp-samples-language.zip” という名前の zip ファイルに圧縮されており、インテル® IPP 製品を含むインテル製品のインストール・ディレクトリーの Samples\en_US\IPP または ja_JP\IPP フォルダー以下にあります。このファイルが見つからない場合は、インテル製品のインストール・ディレクトリー以下を検索してみてください。例えば、64 ビットの Windows* システムにインストールされたインテル® Parallel Composer 2011 の場合、デフォルトでは Program Files (x86)\Intel\Parallel Studio 2011\Composer\Samples\en_US\IPP 以下にあります (図 1 を参照)。32 ビットの Windows* システムでは、Program Files (x86) ではなく、Program Files になります。
図 1. インテル® Parallel Composer で提供される IPP サンプルの zip ファイル
ipp-samples-language.zip ファイルを展開すると、メインのフォルダー以下に次のサブフォルダーがあります。
ipp-samples |-> language-interface |-> cpp |-> doc |-> src |-> csharp |-> application |-> doc |-> interface |-> src |-> tools |-> env
C# からインテル® IPP 関数を呼び出すのに必要なすべてのラッパーを含めるため、ipp-samples-language\ipp-samples\language-interface\csharp フォルダー以下にある .cs ファイルすべてを C# プロジェクトに追加します。(このフォルダーは、ipp-samples-language.zip ファイルを展開すると作成されます。) つまり、プロジェクトに次の C# ファイルをインクルードします。
- ippcc.cs
- ippch.cs
- ippcore.cs
- ippcp.cs
- ippcv.cs
- ippdc.cs
- ippdefs.cs
- ippdi.cs
- ippgen.cs
- ippi.cs
- ippj.cs
- ippm.cs
- ippr.cs
- ipps.cs
- ippsc.cs
- ippvc.cs
- ippvm.cs
インテル® IPP のイメージング・フィルターを使用する場合は、必要なデータを準備するため、アンセーフなポインターの使用を有効にする必要があります。そのためには、[ソリューション エクスプローラー] でプロジェクト名を右クリックして [プロパティ] を選択し、 [ビルド] ページで [アンセーフ コードの許可] チェックボックスをオンにします。図 2 を参照してください。
図 2. Visual Studio* のソリューションのプロパティページと [Allow Unsafe Code (アンセーフ コードの許可)] チェックボックス
以下の C# コードは、WPF アプリケーションの ProcessImage
ボタンの Click イベントハンドラーを定義します。WPF プロジェクトは、コードで System.Drawing.Bitmap
クラスを使用してビットマップ・イメージのピクセルを操作するため、System.Drawing
への参照が必要です。コードに using ipp
行があるのが分かります。
using System; using System.Drawing; using System.Drawing.Imaging; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Interop; using ipp; namespace IppCSharpExample { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private BitmapData GetBitmapData(Bitmap bitmap, ImageLockMode lockMode) { return bitmap.LockBits( new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), lockMode, System.Drawing.Imaging.PixelFormat.Format24bppRgb); } unsafe private Bitmap ApplyFilterSobelHoriz(string fileName) { var originalImage = new Bitmap(fileName); var sourceBitmapData = GetBitmapData(originalImage, ImageLockMode.ReadOnly); var destinationImage = new Bitmap(originalImage.Width, originalImage.Height); var destinationBitmapData = GetBitmapData(destinationImage, ImageLockMode.ReadWrite); IppiSize roi = new IppiSize(originalImage.Width - 3, originalImage.Height - 3); const int ksize = 5; const int half = ksize / 2; byte* pSrc = (byte*)sourceBitmapData.Scan0 + (sourceBitmapData.Stride + 3) * half; byte* pDst = (byte*)destinationBitmapData.Scan0 + (destinationBitmapData.Stride + 3) * half; IppStatus status = ipp.ip.ippiFilterSobelHoriz_8u_C3R( pSrc, sourceBitmapData.Stride, pDst, destinationBitmapData.Stride, roi); // ソースとデスティネーションの両方のビットをアンロック originalImage.UnlockBits(sourceBitmapData); destinationImage.UnlockBits(destinationBitmapData); return destinationImage; } private void ProcessImage_Click(object sender, RoutedEventArgs e) { ProcessImage.IsEnabled = false; // このスケジューラーは現在の SynchronizationContext に対してタスクを実行する var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); var filterTask = Task.Factory.StartNew( () => { return ApplyFilterSobelHoriz(@"C:\YOURPICTUREFOLDER\YOURPICTURE.JPG");} ); filterTask.ContinueWith( (antecedentTask) => { // このコードは UI スレッドにより実行される try { var outBitmap = antecedentTask.Result; // イメージ・コントロールに // 結果を表示 ProcessedImage.Source = Imaging.CreateBitmapSourceFromHBitmap( outBitmap.GetHbitmap(), IntPtr.Zero, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight( outBitmap.Width, outBitmap.Height)); } catch (AggregateException ex) { foreach (Exception innerEx in ex.InnerExceptions) { //// 例外処理をまとめる //// .... //// 例外処理をまとめる } } ProcessImage.IsEnabled = true; }, uiScheduler); } } }
この例の ProcessImage_Click
イベントハンドラーは filterTask
を開始します。これは、Task
で、ApplyFilterSobelHoriz
メソッドを呼び出します。そして、filterTask
の連続処理を行い、ProcessedImage
イメージ・コントロールにある JPEG イメージに水平方向のソーベルフィルターを適用した結果を表示します。この例では、コードを簡潔にするため、エラーをチェックしていません。代わりに、タスクと連続処理を使用して水平方向のソーベルフィルターを非同期的に実行し、結果を表示しなければならないときまで UI スレッドをブロックしません。このコード例から分かるように、.NET Framework 4 のタスクベースのプログラミングとインテル® IPP 呼び出しを組み合わせることができます。
安全でない ApplyFilterSobelHoriz
メソッドは、C:\YOURPICTUREFOLDER\YOURPICTURE.JPG ビットマップに対して水平方向のソーベルフィルターを実行します。このメソッドは、ソースおよびデスティネーションのデータとポインターを準備し、ipp.ip.ippiFilterSobelHoriz_8u_C3R
フィルターを適用するインテル® IPP のイメージ処理メソッドを呼び出します。インテル® IPP は、イメージサイズとハードウェアを考慮して、最適な SIMD 命令とスレッド数を利用し、イメージ処理アルゴリズムの実行を最適化します。
最初に、ApplyFilterSobelHoriz
メソッドは、filename
引数に指定されたパスを読み取って新しい Bitmap (originalImage
) を作成し、GetBitmapData
メソッドを呼び出してビットをシステムメモリーにロックします。これらのビットは読み取り専用として sourceBitmapData
に格納されます。次に、デスティネーション・ビットマップが必要なため、オリジナルのビットマップと幅と高さが同じ destinationImage
と呼ばれる新しい Bitmap を作成します。そして、GetBitmapData
メソッドを呼び出し、ビットをシステムメモリーにロックします。これらのビットは読み書き可能として destinationBitmapData
に格納されます。
次に、イメージの対象領域 (ROI) を指定する IppiSize roi
構造体を作成し、ソースとデスティネーションのビットマップのピクセルデータにアクセスする byte 型のポインター pSrc
と pDst
を定義します。これでソーベルフィルターに必要な引数が揃ったので、ipp.ip.ippiFilterSobelHoriz_8u_C3R
メソッドを呼び出します。このメソッドは、3 つのチャネル (赤、緑、青) によりイメージを処理します。そのため、デジタルカメラで撮影し、JPEG 形式で保存された写真に使用すると最適です。
ipp.ip.ippiFilterSobelHoriz_8u_C3R メソッドは、フィルターを適用し、処理済みのピクセルをデスティネーション・ビットマップに格納します。そして、インテル® IPP フィルターの結果に関係なく、ソースおよびデスティネーション・ビットマップのビットをアンロックして、destinationImage
を返します。処理が正しく行われなかった場合、メソッドは status 変数の値を確認し、必要な例外をスローする必要があります。
Task
は ApplyFilterSobelHoriz
メソッドの呼び出しの結果を filterTask
タスクに格納します。filterTask
タスクに関連付けられている連続処理タスクは、このタスクを antecedentTask
として受け取り、例外が伝えられるのを待機します。例外が発生すると、このコードはそれをキャッチするだけで、処理は行っていません。イメージ処理フィルターが正常に実行されると、UI コントロールを作成したスレッドによって実行されるコードで、ProcessedImage
コントロールにある結果のビットマップが表示されます。WPF アプリケーションでこのコードを使用する場合は、System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap
メソッドを使用して、GDI+ の System.Drawing.Bitmap
インスタンスから BitmapSource
を作成する必要があります。
もちろん、P/Invoke を介したインテル® IPP 関数の呼び出しにはペナルティーが伴います。しかし、インテル® IPP 関数は高度に最適化されているため、インテル® IPP 関数を呼び出すことで、インテル® プロセッサーの最新の命令セットを利用することができます。また、タスクベースのプログラミングと組み合わせて、応答性に優れた UI を簡単に記述することもできます。
インテル® IPP のメインの DLL (ippi-x.x.dll。x.x はバージョン番号。) には、その他の DLL への参照が含まれているため、PATH 環境変数にメインの DLL とその関連 DLL を含むフォルダーを設定する必要があります。あるいは別の方法として、すべての DLL をプロジェクトの bin 出力フォルダーにコピーすることもできます。インテル® Parallel Composer 2011 の “redist” フォルダーには、2 つのサブフォルダー (“ia32” および “intel64”) があります。どちらのサブフォルダーにも “ipp” サブフォルダーがあり、命令セット (IA-32 またはインテル® 64) に応じて再配布可能なすべての DLL が配置されています。C# ラッパーがインテル® IPP のメインの DLL をロード中にエラーが発生した場合は、C# プロジェクトの bin 出力フォルダーに適切な DLL をコピーしてみると良いでしょう。
インテル® IPP は商用製品ですが、こちらから無料体験版をダウンロードすることができます。
パフォーマンス・ライブラリーを使用する利点に関する詳細は、Lori Matassa と Max Domeica の「パフォーマンス・ライブラリーの基本」 (英語) が参考になるでしょう。