WriteableBitmapで、画像処理をする場合の例。ここでは、ラプラシアンフィルタによる8近傍空間2次微分を計算し、輪郭を検出している。
GetBitmap, SetBitmap を使用した例では、GUIへの書き込み処理をメインスレッドで行う必要があり、マルチスレッド、マルチコアでの並列処理が難しい。そこで、WriteableBitmapを使用して、画像データをbyte列として抽出し、そのデータに対して、並列に画像処理を行い、Bitmapに書き戻すように処理をすることにより、マルチコアによる並列処理の効果も確認してみた。
Core i7 860 の4Core 8Thread で、SIDBA GIRL 256×256 Color が0.043秒(シリアル)が0.014秒(パラレル)で約3倍高速。
Core i7 860 の4Core 8Thread で、SIDBA Mandrill 512×512 Colorで、0.17秒(シリアル)が0.05秒(パラレル)で、約3倍高速。
もう少し大きめの画像で、Core i7 860 の4Core 8Thread で、3072×2304 Colorで、4.66秒(シリアル)が1.11秒(パラレル)で、約4倍高速。
シリアル実行時のCPU使用状況
Parallel.For 実行時のCPU使用状況
フィルター処理自体は、高速化を意識せずに素直に処理している。512×512程度のカラー画像に対して、3×3程度のフィルターは、ほぼリアルタイム処理が可能と思われる。
C# Parallel を使ってみたのは2度目だが、言語レベルでマルチコアがサポートされているのは、素晴らしすぎる。
さらに、マトリックス演算(積和)を行わず、加算だけで処理するような高速化や、フィルターの展開によるループの削減などの、コード上のチューニングだけでも、かなりの高速化が期待できると思う。また、Intel SIMD(MMX) を適用できるとおもわれるが、どの程度チューニングできるのか、機会があったら試してみたい。
MainWindow.Xaml
<Window x:Class=”WritableBitmap.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
Title=”MainWindow” Height=”406″ Width=”525″>
<Grid>
<Viewbox Margin=”0,64,0,0″>
<StackPanel Orientation=”Horizontal” Margin=”58,79,91,58″>
<Viewbox >
<Image x:Name=”SourceImage” VerticalAlignment=”Stretch” HorizontalAlignment=”Stretch” Stretch=”UniformToFill” />
</Viewbox>
<Viewbox >
<Image x:Name=”ResultImage” VerticalAlignment=”Stretch” HorizontalAlignment=”Stretch” Stretch=”UniformToFill” />
</Viewbox>
</StackPanel>
</Viewbox>
<Button Content=”Load Image” Height=”30″ HorizontalAlignment=”Left” Margin=”58,13,0,0″ Name=”button1″ VerticalAlignment=”Top” Width=”95″ Click=”button1_Click” />
<Button Content=”Do in Serial” Height=”30″ HorizontalAlignment=”Left” Margin=”231,13,0,0″ Name=”button2″ VerticalAlignment=”Top” Width=”95″ Click=”button2_Click” />
<Button Content=”Do in Parallel” Height=”30″ HorizontalAlignment=”Left” Margin=”347,13,0,0″ Name=”button3″ VerticalAlignment=”Top” Width=”95″ Click=”button3_Click” />
</Grid>
</Window>
MainWindow.Xaml.cs
using System;
using System.Windows;
using System.Windows.Media.Imaging;
using Microsoft.Win32;
namespace WritableBitmap
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
OnFileOpen();
}
WriteableBitmap srcWritableBitmap;
void OnFileOpen()
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = “Bitmap files|*.bmp;*.png;*.jpeg;*.jpg;*.gif;*.tiff;*.tif;*.wdp|” +
“BMP files (*.bmp)|*.bmp|” +
“PNG files (*.png)|*.png|” +
“JPEG files (*.jpeg, *.jpg)|*.jpeg;*.jpg|” +
“GIF files (*.gif)|*.gif|” +
“TIFF files (*.tiff, *.tif)|*.tiff;*.tif|” +
“Windows Media Player files (*.wdp)|*.wdp|” +
“All files (*.*)|*.*”;
if ((bool)dlg.ShowDialog())
{
SourceImage.Source = null;
srcWritableBitmap = null;
BitmapFrame frame = null;
try
{
frame = BitmapFrame.Create(new Uri(dlg.FileName), BitmapCreateOptions.None,
BitmapCacheOption.None);
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, Title);
return;
}
srcWritableBitmap = new WriteableBitmap(frame);
if (srcWritableBitmap.Format.BitsPerPixel != 8 &&
srcWritableBitmap.Format.BitsPerPixel != 24 &&
srcWritableBitmap.Format.BitsPerPixel != 32)
{
MessageBox.Show(“Bitmap must have 8 or 24 bits or 32 bits per pixel”, Title);
srcWritableBitmap = null;
return;
}
SourceImage.Source = srcWritableBitmap;
}
}
private void buttonExecInSerial_Click(object sender, RoutedEventArgs e)
{
Filter filter = new Filter();
filter.SetParameters(
srcWritableBitmap.PixelWidth,
srcWritableBitmap.PixelHeight,
srcWritableBitmap.Format.BitsPerPixel / 8);
srcWritableBitmap.CopyPixels(filter.Source, GetStride(), 0);
filter.ExecuteInSerial();
WriteableBitmap resWritableBitmap = srcWritableBitmap.Clone();
ResultImage.Source = resWritableBitmap;
resWritableBitmap.WritePixels(filter.Rect, filter.Result, filter.Stride, 0);
}
private void buttonExecInParallel_Click(object sender, RoutedEventArgs e)
{
Filter filter = new Filter();
filter.SetParameters(
srcWritableBitmap.PixelWidth,
srcWritableBitmap.PixelHeight,
srcWritableBitmap.Format.BitsPerPixel / 8);
srcWritableBitmap.CopyPixels(filter.Source, GetStride(), 0);
filter.ExecuteInParallel();
WriteableBitmap resWritableBitmap = srcWritableBitmap.Clone();
ResultImage.Source = resWritableBitmap;
resWritableBitmap.WritePixels(filter.Rect, filter.Result, filter.Stride, 0);
}
private int GetStride()
{
int stride = srcWritableBitmap.PixelWidth * srcWritableBitmap.Format.BitsPerPixel / 8;
return stride;
}
}
}
Filter.cs
#define STOPWATCH
using System;
using System.Windows;
using System.Threading.Tasks;
namespace WritableBitmap
{
class Filter
{
float[,] filter = {
{ -1 / 8f, -1 / 8f, -1 / 8f },
{ -1 / 8f, 8 / 8f, -1 / 8f },
{ -1 / 8f, -1 / 8f, -1 / 8f }
};
private int _width, _height, _bytesPerPixel;
public int Width
{
get { return this._width; }
private set { this._width = value; }
}
public int Height
{
get { return this._height; }
private set { this._height = value; }
}
public int BytesPerPixel
{
get { return this._bytesPerPixel; }
private set { this._bytesPerPixel = value; }
}
public Int32Rect Rect { get; set; }
public int Stride { get; set; }
public byte[] Source { get; set; }
public byte[] Result { get; set; }
public void SetParameters(int width, int height, int bytesPerPixel)
{
Width = width;
Height = height;
BytesPerPixel = bytesPerPixel;
Rect = new Int32Rect(0, 0, Width, Height);
Stride = Width * BytesPerPixel;
Source = new byte[Width * BytesPerPixel * Height];
Result = new byte[Width * BytesPerPixel * Height];
}
public void ExecuteInSerial()
{
#if STOPWATCH
DateTime dtStart = DateTime.Now;
#endif
for(int y = 1; y < Height – 1; y++)
FilterOneLine(y);
#if STOPWATCH
DateTime dtEnd = DateTime.Now;
Console.WriteLine((dtEnd – dtStart).ToString());
#endif
}
public void ExecuteInParallel()
{
#if STOPWATCH
DateTime dtStart = DateTime.Now;
#endif
Parallel.For(1, Height-1, FilterOneLine);
#if STOPWATCH
DateTime dtEnd = DateTime.Now;
Console.WriteLine((dtEnd-dtStart).ToString());
#endif
}
private void FilterOneLine(int y)
{
int str = Width * BytesPerPixel;
for (int x = 1; x < Width – 1; x += 1)
{
// 各色ごとに
for (int col = 0; col < BytesPerPixel; col++)
{
int index = str * y + BytesPerPixel * x + col;
if (col == 3)
{
Result[index] = 255;
continue;
}
float v = 0f;
for (int yy = -1; yy <= 1; yy++)
for (int xx = -1; xx <= 1; xx++)
{
int position = str * (y + yy) + BytesPerPixel * (x + xx) + col;
float filterValue = filter[yy + 1, xx + 1];
v = v + Source[position] * filterValue;
}
v = Math.Abs(v);
v = (v > 10) ? 255 : 0;
Result[index] = (byte)v;
}
}
}
}
}
コメントを残す