写真にぼかしを Part 3 ガウスぼかし


#wp7dev_jp

そういえば、この辺の話は自分で考えたり、ほかで紹介されている方法をつまみ食いしてます。いいサイトがあればぜひ教えてください。

さて、Photoshop のフィルタなどでもおなじみのガウスぼかし。多分こんな感じです。

1つの点をぼかすと 右のように変化します。そのとき、中心からの濃さはガウス分布によって広がります。これを実際にロジックで実行します。

image

1つの点をまずは横方向にぼかしたときの結果を考えます。そしてそれをもとに縦方向にぼかした時の影響を考えます。

image

これまで平均をとっていた時には、対象点から離れていても対象点の上でも一律で平均していましたが、今度は対称点からの距離に合わせてガウス分布の重み付けを行います。そんなわけであらかじめガウス分布の配列を作っておきます。このときの標準正規分布の式はこれ。σ で分布の扁平率が調整できます。(今回は対象半径x3にしています。)

image f(x)=\frac{1}{\sqrt{2\pi\sigma^{2}}} \exp\!\left(-\frac{(x-\mu)^2}{2\sigma^2} \right)

上の説明手順と、処理準がちょっと異なりますが、これまでのぼかしの処理に、このガウスぼかしを適応します。

手順は、

  1. ガウス分布の配列を作る
  2. 元画像から横方向だけのぼかし画像を作る
    この時に平均ではなく、ガウス分布の重み付けをしての平均をとる
  3. 横方向のぼかし画像から縦方向のぼかし画像を作る
    この時も平均ではなく、ガウス分布の重み付けをしての平均をとる

ではコード。

XAMLはいつもと同じ。

<Grid x:Name="LayoutRoot" Background="Transparent">
    <Grid x:Name="ContentPanel" >
        <Image Name="image1" Stretch="UniformToFill" Tap="image1_Tap" />
    </Grid>
</Grid>

<!--ApplicationBar の使用法を示すサンプル コード-->
<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
        <shell:ApplicationBar.MenuItems>
            <shell:ApplicationBarMenuItem Text="読み込み" 
            Click="ApplicationBarMenuItem_Click" />
        </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

 

ロジックはこんな感じになりました。 ピクセル生成ループが2つになりましたが、その中のぼかしのループはこれまで x、yを回していたところが、それぞれxだけyだけになっています。

private void image1_Tap(object sender, GestureEventArgs e)
{

    WriteableBitmap sourcewp = new WriteableBitmap(image1, null);

    int wpw = sourcewp.PixelWidth;
    int wph = sourcewp.PixelHeight;

    WriteableBitmap tempwp = new WriteableBitmap(wpw, wph);
    WriteableBitmap finalwp = new WriteableBitmap(wpw, wph);

    double blur = 5;
    double blurRange = blur * 3 ;
    Double[] gaussparam = new Double[(int)blurRange + 1];


    for( double i=0; i<blurRange; i++)
        gaussparam[(int)i] = Math.Exp(-i * i / (2 * blur * blur));

    DateTime start = DateTime.Now;

    for (int pixel = 0; pixel < sourcewp.Pixels.Length; pixel++)
    {
        int ox = pixel % wpw;
        int oy = pixel / wpw;

        double gauss = 0;
        double count = 0;
        double A = 0;
        double R = 0;
        double G = 0;
        double B = 0;

        for (int x = -1 * (int)blurRange; x <= blurRange; x++)
        {
            int tx = ox + x;

            if ((tx >= 0) && (tx < wpw))
            {
                gauss = gaussparam[Math.Abs(x)];
                int color = sourcewp.Pixels[tx + oy * wpw];
                A += (double)(color >> 24) * gauss;
                R += (double)((color >> 16) & 0x000000FF) * gauss;
                G += (double)((color >> 8) & 0x000000FF) * gauss;
                B += (double)((color) & 0x000000FF) * gauss;
                count += gauss;
            }
        }

        A /= count;
        R /= count;
        G /= count;
        B /= count;

        tempwp.Pixels[pixel] = ((int)A << 24) | ((int)R << 16) | ((int)G << 8) | (int)B;

    }

    for (int pixel = 0; pixel < tempwp.Pixels.Length; pixel++)
    {

        int ox = pixel % wpw;
        int oy = pixel / wpw;

        double gauss = 0;
        double count = 0;
        double A = 0;
        double R = 0;
        double G = 0;
        double B = 0;

        for (int y = -1 * (int)blurRange; y <= blurRange; y+=3)
        {
            int ty = oy + y;

            if ((ty >= 0) && (ty < wph))
            {
                gauss = gaussparam[Math.Abs(y)];
                int color = tempwp.Pixels[ox + ty * wpw];
                A += (double)(color >> 24) * gauss;
                R += (double)((color >> 16) & 0x000000FF) * gauss;
                G += (double)((color >> 8) & 0x000000FF) * gauss;
                B += (double)((color) & 0x000000FF) * gauss;
                count += gauss;
            }
        }

        A /= count;
        R /= count;
        G /= count;
        B /= count;

        finalwp.Pixels[pixel] = ((int)A << 24) | ((int)R << 16) | ((int)G << 8) | (int)B;

    }

    image1.Source = finalwp;
    MessageBox.Show((DateTime.Now - start).TotalSeconds.ToString() + " sec");
}

private void ApplicationBarMenuItem_Click(object sender, EventArgs e)
{
    PhotoChooserTask task = new PhotoChooserTask();
    task.Completed += new EventHandler<PhotoResult>(task_Completed);
    task.Show();
}

void task_Completed(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        BitmapImage bmp = new BitmapImage();
        bmp.SetSource(e.ChosenPhoto);
        image1.Source = bmp;
    }
}

 

結果

σ = 3x半径 としてガウス分布でとっているので、結果として前のBlur値でのガウスよりも強く出ています。また、ぼかしのかかり方が少し違うので比較が難しいですが、今回はBlur=6で間引きなしの結果を比較しています。(ボールの左下の五角形の文字のボケ辺りで比較)

元画像と、写真にぼかしを で紹介した、Blur = 10でのものと、今回のBlur =6での結果です。

image image image

素晴らしいことは、このぼかしを行うのに、平均で行っていた12秒に対して、今回の処理方法では、2秒強で終了しています。Blur=3くらいだと1秒強で終わります。

さて、次はこれにマスクを加える方法ですね。

Comments (0)

Skip to main content