はじめに
10年くらいまえに、windowsで大量の画像データを処理する仕事があって、その時に、マネージドコード(VB)とC/C++のネイティブexeの速度比較を行ったことがあります。
その時に、並列化や一部コードのアセンブラ化、SIMDの採用など、実験的にどこまで高速化できるかやってみました。
その時は、100万画素程度の画像の処理に、マネージドコードで秒単位でかかっていた処理を、10msecのオーダーまで短縮することが出来ました(100倍程度の高速化)。
最近Windowsアプリを作る機会が減っているので、再勉強をかねて、その時の実験を思い出しながら、現在のPC、現在の開発環境で再現してみたいと思います。
開発環境は無料で使えるmicrosoftのvisual studio community editionを考えています。
https://visualstudio.microsoft.com/ja/downloads/
画像のフィルター処理の例として平滑化処理をやってみることにします。
平滑化処理とは注目する画素とその周辺の画素を足して平均をとるものです。C言語で表現すると、以下のようなかんじです。
void Average(unsigned char* src, unsigned char* dst, int xsize, int ysize)
{
int pix, i , j, offset;
for( j=1; j < ysize-1; j++) {
for( i=1; i < xsize-1; i++){
pos = j * xsize + i;
pix = (
*(src+pos-1-xsize)
+ *(src+pos-xsize)
+ *(src+pos+1-xsize)
+ *(src+pos-1)
+ *(src+pos)
+ *(src+pos+1)
+ *(src+pos-1+xsize)
+ *(src+pos+xsize)
+ *(src+pos+1+xsize) )
/ 9;
*(dst + pos) = (unsigned char) pix;
}
}
}
それでは、次回はマネージドコードで画像にフィルター処理をほどこし、速度を計測してみたいと思います。
その前に、いつの間にか手元のPCがMacだけになってしまった私は、WindowsPCを入手しなくてはなりません。ごく普通のスペックのノートPCになると思います。
Visual Studio Community 2022にハマった件
PCを借りることができました。Intel(R) Core(TM) i5-4210M CPU @2.60GHzでRAMが16GBと、今回の実験には十分すぎるほどのスペックです。
さっそくVisual Stuio Community 2022をインストールして、C#のコンソールプログラムで"Hello World!"をやってみます。ここまでは問題なし。
ところが、ビットマップを読み込もうとBitmapクラスの変数を宣言したところでエラーが出ました。エラーメッセージは"System.Drawingって何?そんなの知らんけど?"みたいなかんじ。
ソリューションの依存関係を見ると、ちゃんとSystem.Drawingはいます。そこで、ネットを検索してみましたが、このVisual Stuio Community 2022というのは最近リリースされたものらしく、的確な情報がみつけられません。あれこれ試行錯誤した結果、ターゲットframeworkをデフォルトの.NET6.0から.NET Framework4.8に落とし、言語バージョンを8.0に、ImplicitUsingsをdisableにしたところで、エラーが出なくなりました。以下が現在のプロジェクトの設定です。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net48</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AutoGenerateBindingRedirects>False</AutoGenerateBindingRedirects>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
</Project>
これでやっとスタート地点です。
*当初Visual Stuio Community 2022をVisual Stuio Community 2020と表記しておりました。(2020が今年だと信じて疑わなかった私。。。)
指摘していただいた@yumetodoさん、ありがとうございました。
次は読み込んだビットマップの画素を取得して、加工し、結果出力用ビットマップに格納しファイルに落とすところまでやります。
GetPixel SetPixelを使ったバージョン
using System;
using System.Drawing;
using System.Drawing.Imaging;
public class Hello
{
public static void Average(Bitmap src, Bitmap dst, int width, int height)
{
int i, j;
int pix;
for (j = 1; j < height-1; j++)
{
for (i = 1; i < width-1; i++)
{
pix = (
(int)src.GetPixel(i - 1, j - 1).R
+ (int)src.GetPixel(i, j - 1).R
+ (int)src.GetPixel(i + 1, j - 1).R
+ (int)src.GetPixel(i - 1, j).R
+ (int)src.GetPixel(i, j).R
+ (int)src.GetPixel(i + 1, j).R
+ (int)src.GetPixel(i - 1, j + 1).R
+ (int)src.GetPixel(i, j + 1).R
+ (int)src.GetPixel(i + 1, j + 1).R )
/ (int)9;
dst.SetPixel(i, j, Color.FromArgb(
(int)pix,
(int)pix,
(int)pix));
}
}
}
public static void Main()
{
Console.WriteLine(System.Environment.CurrentDirectory);
Bitmap bmporg = new Bitmap("test1.bmp"); // 元ファイル
Bitmap bmpresult = new Bitmap(bmporg.Size.Width, bmporg.Size.Height); // 結果ファイル
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
Average(bmporg, bmpresult, bmporg.Width, bmporg.Height);
Console.WriteLine(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff"));
bmpresult.Save("test1_result.bmp", ImageFormat.Bmp);
Console.ReadKey();
}
}
カレントディレクトリの"test1.bmp"を読み込み、平滑化処理を施し、カレントディレクトリの"test_result.bmp"に保存するコンソールプログラムです。例外やエラーのチェックはサボっています。
あと、グレースケールの画像を扱うのにうまいやりかたがみつけられなかったので、Rチャンネルだけ使っていますが、このあたりはもっとうまいやり方がみつかったら直したいと思います。
ビットマップは1920 x 1200(およそ230万画素)のものを使いました。
結果は以下のようになりました。ビットマップの読み込み時間や書き込み時間を除いて、画像処理の時間だけ計測しています。
2022/01/28 18:03:00.555
2022/01/28 18:03:13.030
12秒ちょいかかっていますね。GetPixel, SetPixelは「鬼のように遅い」ことで知られていますが、他の手法と比較するためにあえてやってみました。
計算結果の画像を拡大して表示したものです。
上が元画像、下が処理後です。下はぼんやりしていますよね?
次回は「鬼のように遅い」GetPixel, SetPixelをやめて、LockBitsを使った方法でやってみます。
↧