画像処理の例

黒丸画像の抽出

 ここでは、画像の中から、一定の大きさの黒丸を抽出、黒丸の特性を計算するプログラムを紹介します。
一定の大きさの黒丸を抽出しますが、特性の計算については、一個だけ抽出された時に、正しい結果を出します。

 

サンプル処理後->処理後
 上図の様な画像から、指定された大きさの黒丸を残し、それから外れた画像を全て消去します。
 ここでのサンプル画像のサイズは、1392 × 1040 で 8 ビットのグレースケール画像です。
サイズの変更には、プログラムの変更が必要です。
抽出する黒丸のサイズは、画像1ドットあたりの長さ0.02ミリとして、5 ~ 7 ミリの直径になるものです、それ以外は消去されます。
 サンプル画像は、ダウンロードした bitmapフォルダーの中に入っています、Windowsのペイントで開くことは出来ますが、8ビットのグレースケールでの保存は出来ません。
 本来の目的は、デジタルカメラから取り込んだ画像を使用するのですが、デジタルカメラは、多種多様で、それにあわせたインターフェースの用意が出来ないので、ファイルから読み込んで画像処理を行います。
データーの1ドットのビット数が、Windowの標準と合わない場合、変換が必要です。
カメラから取り込む場合は、ドライバーにより指定されたポインターから読み込みますが、その時、ソフトで処理できる形にデーターを変換して読み込みます。
次の例は、このようなもであるという例で実際に動作するものではありません。

 

var
  Data2dByte: array[0..XimgWidth - 1,0..YimgHeight - 1] of byte;	// 2値化画像データー。
  
const
 threshold = 128;
var
  Pimage: Pointer; 					        	// 画像用メモリーポインタ
  cameraByte: array[0..XimgWidth - 1,0..YimgHeight - 1] of byte;        // カメラ用画像データー。
  LL : Byte;
  XX, YY: integer;
begin
  Pimage := DepictCreateImageDestinationEx(MaxRoiX, MaxRoiY, eFormat);  	// カメラのドライバーに必要なメモリー領域を確保させ、ポインターを取得する
  DepictSingleAcquireEx(hcamera, TRIG_IMMEDIATE, Pimage, nil , nil, 0); 	// カメラからポインターにデーターを読み込む
  DepictCovertBayerToGrayscale(Pimage, @cameraByte, 0, 0, XimgWidth, YimgHeight); 	// クレースケールデーターに変更する
  
  for YY := 0 to YimgHeight - 1 do                                 // 2値化処理
    for XX := 0 to XimgWidth - 1 do
      begin
        LL := cameraByte[XX, YY];                                        // 8ビットグレースケールの場合は1バイトごと
        if LL > threshold then Data2dByte[XX, YY] := $FF                	// 2値化
                          else Data2dByte[XX, YY] := 0;
   end;
 DepictFreeImageDestination(Pimage);                                    // バッファメモリーの解放
end;

 カメラのデーターは二次元の配列ですが、画面に表示するのには、表示用のデーターに変更する必要があります。
Bitmapのフォーマットからポインターの計算は厄介なので、Delphiには、1ライン毎に先頭のポインターを読み出すコマンドが用意されています。(ピクセルの各行へ、インデックスによるアクセス)

Type
  TImbyteArry = array[0..XimgWidth - 1] of byte;                        // 1ラインの配列宣言 この場合は。[0..0]でも可 必要なのbyteは配列のポインタ
  PImbyteArry = ^TImbyteArry;                                            // ポインター宣言
var
  Image1: Tbitmap;
  P: PImbyteArry;
  XX, YY: integer;
begin
  Bitmap1 := TBitmap.Create;                                             // ビットマップイメージ作成
  Bitmap1.PixelFormat := pf8bit;                                         // 8ビットモードに設定 データは$FFと$00だけなのでグレースケールの設定必要なし
  Bitmap1,Width       := XimgWidth;
  Bitmap1.Height      := YimgHeight;
  for YY := 0 to YimgHeight - 1 do
    begin
      P := Bitmap1.Scanline[YY];                                         // ラインの先頭にセット
      for XX : 0 to XimgWidth - 1 do                                // 1ライン分コピー
                 P[XX] := Data2dByte[XX, YY];
    end;
  Image1.Canvas.StretchDraw(rect(0, 0, Image1.width - 1, Image1.height - 1), bitmap1); // サイズストレッチ表示

  Bitmap1.free;                                                          // Bitmapの解放
end;

ダウンロードに用意しているサンプルプログラムでは、カメラが無いので、ファイルからロードします。

  Bitmap1.LoadFromFile(bitmapFile);                                      // ビットマップファイルの読み込み
  Image1.Width := 696;                                                   // 表示画面高さ設定
  Image1.Height := 520;                                                  // 表示画面幅設定
  ImageForm.Image1.Canvas.StretchDraw(rect(0, 0,
  		Image1.width - 1,Image1.height - 1), bitmap1);         // ストレッチ表示

 画像処理の手順
フロー
 画像の取り込みは、カメラか、ファイルから取り込むことになります。
 2値化の結果は、ビット単位にして  か  にしても良いのですが、ビット単位で取り扱うと、使用するメモリーは少なくてすむのですが、取り扱いが面倒なので、バイト単位で、$FF と $00 とし、二次元配列にして取り扱います。
バイト単位にすれば、8ビットグレースケールの二次元配列データーをそのまま利用出来るし、画面への表示も簡単になります。
 ゴミ画像消去
一番問題になるのは、必要な黒丸画像の周囲に入ってしまった、ゴミ画像の処理です。
XYProjWM45ProjP45Proj


 ゴミ画像の消去は、X方向、Y方向、プラス45度、マイナス45度方向に対して、投影して幅が指定値より短い場合、その短い場所の幅分の画像を消去
1列の並びの長さが、指定の長さより長い場合は、その1列を消去します。
各方向の投影量を加算すれば、面積になるので、指定した面積より、小さい場合、その小さい面積の部分の画像全てを消去します。
黒丸の中に、白い部分があると、外径は指定の大きさでも、面積が不足してゴミとして消去されます場合があります。
正しくゴミを消去するのは難しく、出来る限り、ゴミが画像に入らないようにした方が良いでしょう。
本プログラムでは、必要とする黒丸に、繋がってしまったゴミ画像の処理は出来ません。

   ブラス45度                         マイナス45度
P45projHM45ProjH


 斜めに投影する場合は、三角部①③と、平行四辺形②の部分に分けて投影量を計算します。
45度斜めに、投影すると、幅は水平垂直にたいして√2(1.41)倍の幅になります。

投影量の計算
XYproje
 前にも説明しましだが、投影量は単に各方向のドットを数えたものです。

重心の計算

	X方向の数の計を Yproject Y軸投影量配列
	Y方向の数の計を Xproject X軸投影量配列
		とし
	CCDの	X方向の座標を、i
    	    	Y方向の座標を、jとした場合
	重心座標(mX,mY)は、次の計算で求められます。
		mX = Sum(i*Xproject(i)) / Sum(Xproject(i))
		mY = Sum(j*Yproject(j)) / Sum(Yproject(j))
SumはΣ(総和)を表します。 Sum(Xproject(i))、Sum(Yproject(j))は、同じ値で、画像のある場所の面積と成ります。

二次モーメントの計算

  	二次モーメントの計算は、穴の形状を楕円として取り扱い、その半径を計算するために行います。

  	完全な円の場合は、重心に対する、X方向の二次モーメントと、Y方向に対する二次モーメントは一致することになります。
	二次モーメントを、mX2,mY2とすると、
		mX2=Sum(Sqr(i-mX) * Xproject(i))
		mY2=Sum(Sqr(j-mY) * Yproject(j))		 
			Sqrは自乗を表します
	と成ります。

相乗モーメントの計算

相乗モーメントの計算は、楕円の方向を求めるのに使用します。

				Jxy=Sum((i-mX)*(j-mY)* P(i,j))

					P(i,j) は座標(i,j)の値です 1 or 0

軸角の計算

 	楕円の傾きを計算するため、二次モーメント、相乗モーメントから、軸角を計算します。
	計算式の証明については、省略します。

	二次モーメントが一番大きくなる場合の軸角の値は、次のようになります。
	軸角θは
        θ=ArcTan( 2*Jxy/(mX2-mY2))/2

 		で求める事が出来ますが、上記の計算では、45°~-45°の計算結果となります。

	求める角度は 90°~-90°が必要です。
		そこで、相乗モーメント(Jxy)と、分母の二次モーメント(mX2-mY2)の符号(±)で判定し、計算結果を修正します。

         軸角を
        θ=ArcTan2(- 2*Jxy, mY2-mX2)/2
		で求めれば簡単になります。
         

OVAL

   	上図がその関係を表しています。

	分母がプラスで分子がマイナスの場合、ArcTanの場合0°から-45°と、45°から90°の範囲と同じ計算結果を出します、
	又、分母、分子ともマイナスの場合は、0°から45°と、-45°から-90°の範囲にある場合も同じ結果となります。
	そこで、分母と分子の関係から、計算結果のθに、90°を加算するか、減算して、90°~-90°の軸角を求めます。
	(注)パソコン画面の座標は、Y方向が一般の方向とは、逆になるので、±の符号に注意が必要です。
     Arctan2を使用すれば、プログラムは簡単になります。

楕円半径の計算


	穴の形状が、楕円であると仮定して、二次モーメントと、面積から楕円の長aと、短円bを求めます。
	軸角θのある楕円の断面二次モーメントImax、Iminは、次の様になります。

		Imax=(mX2+mY2)/2+Sqrt(Sqr(mX2-mY2)+4*Sqr(Jxy))/2
		Imin=(mX2+mY2)/2-Sqrt(Sqr(mX2-mY2)+4*Sqr(Jxy))/2

	楕円の二次モーメントは、
   		I=π*a3*b/4

	楕円の面積は
   		S=π*a*b

	二次モーメントを面積で割り算すると、
      Sqr(a)/4=I/S   		Sqrは自乗を表します
      a=Sqrt(I/S*4)
      b=S/π/aで求める事が出来ます。

以上で黒丸画像の解析は完了です。
画像処理には、目的に応じて多種な解析方法がありますが、此処では単純な解析を取り上げました。
プログラムはダウンロードして確認して下さい。
サンプル画像ファイルも有ります。


download Imagefit.zip

 

画像処理一覧へ戻る

最初に戻る