WriteableBitmap で、ラプラシアン フィルターを並列実行


WriteableBitmapで、画像処理をする場合の例。ここでは、ラプラシアンフィルタによる8近傍空間2次微分を計算し、輪郭を検出している。

image

GetBitmap, SetBitmap を使用した例では、GUIへの書き込み処理をメインスレッドで行う必要があり、マルチスレッド、マルチコアでの並列処理が難しい。そこで、WriteableBitmapを使用して、画像データをbyte列として抽出し、そのデータに対して、並列に画像処理を行い、Bitmapに書き戻すように処理をすることにより、マルチコアによる並列処理の効果も確認してみた。

Core i7 860 の4Core 8Thread で、SIDBA GIRL 256×256 Color  が0.043秒(シリアル)が0.014秒(パラレル)で約3倍高速。

image

Core i7 860 の4Core 8Thread で、SIDBA Mandrill 512×512 Colorで、0.17秒(シリアル)が0.05秒(パラレル)で、約3倍高速。

image

もう少し大きめの画像で、Core i7 860 の4Core 8Thread で、3072×2304 Colorで、4.66秒(シリアル)が1.11秒(パラレル)で、約4倍高速。

シリアル実行時のCPU使用状況

image

Parallel.For 実行時のCPU使用状況

image

フィルター処理自体は、高速化を意識せずに素直に処理している。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;
                }
            }
        }
    }
}

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中


%d人のブロガーが「いいね」をつけました。