画像の座標抽出をするには?

解決


プラ  2012-04-21 07:12:05  No: 42028

Delphiでプログラムを勉強し始めたばかりの者です。
現在bmp画像上のドットの座標を抽出したいと考えています。

やりたいことのイメージですが
ある大きさを持ったほぼ円形の点が横にn個並んでいる画像があります。
その位置はきれいな横一直線ではなく、上下左右のずれや傾きをもっています。
1番目の点を原点とし、一番右のn番目の点に対して直線を引きx軸と定義します。
x軸に直行して原点を通る直線をy軸と定義します。
そのxy座標平面において軸の設定に使用した2点以外の点の座標を自動で求めたいのです。

処理を以下のように考えました。
1.bmp画像を表示し任意の閾値で二値化
2.原点と定義したい点をマウスのクリックで指定、その点の重心座標を求める。
3.x軸を設定するための右端の点をクリックで指定、その点の重心座標を求める。
4.2つの重心座標を通る直線をx軸と定義
5.y軸は原点を通りx軸に直行する直線を求める。
6.2点以外の点についても、すべて自動で抽出しその重心座標を求める。
7.定義したxy平面状での座標値に変換する。

分からないことは以下の2点です。
1.画像をマウスクリックで選択し、その画像の重心座標を求める方法
2.その他の点を自動で抽出する方法

色々と調べているのですがラベリング処理というものを使うのでしょうか?
参考となるコードやページ、参考書などありましたらご教授よろしくお願いいたします。


monaa  2012-04-21 22:11:52  No: 42029

サンプルを書く分には問題ないのですが、
それを読み解くのにも結構プログラミングを知ってる必要があると思います。
初心者には敷居が高すぎると思います。
Delphiの文法とVCLを大まかに理解しつつ、
Bitmapを配列として扱うプログラミングの基礎と
与えられたデータから目的の情報を抽出するアルゴリズムの知識が不可欠です。
ぱっと質問を読み返した限りではその両方のスキルが未習得と見受けられます。

とりあえず、私からおすすめするのは
1.DelphiでのBitmapの基礎、
2.Bitmapから計算しやすいように配列へ変換
3.データ処理
の3ステップを習得したほうが将来的には近道かと思います。

とにかく動くものがいますぐ欲しくて、理解は後回しというならそれはそれで良いと思いますが、私はおすすめしません。


プラ  2012-04-21 23:25:56  No: 42030

ご返答有難うございます。
質問時は説明不足にならないように冗長に書いたため
すぐに答えを教えてくれ、みたいな雰囲気になってしまいましたね。
申し訳ありません。ライフワークみたいな感じでやってます。

画処理はこちらのサイトで勉強しています。
http://www2s.biglobe.ne.jp/~aks-lab/delphi_lecture.htm
データ処理は今はもっぱらVBAでやっています。

確かにbmpデータの配列処理が自分でもよく理解できていないと
思っていますのでもう少し調べてみたいと思います。


monaa  2012-04-22 01:39:49  No: 42031

確かに、Delphiのグラフィック系は参考になるサイトが少ないかと思います。
なるべくわかりやすいようにコメント盛りだくさんでサンプルを書いて見たので参考にして下さい。
わからない箇所があればまた答えます。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus;

type
  TPixcelData=record
    Black:Boolean;   //色が黒
    Checked:Boolean; //チェックしたか
  end;
  PPixcelDataArray = ^TPixcelDataArray;
  TPixcelDataArray = array of array of TPixcelData;

  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    OpenDialog1: TOpenDialog;
    N1: TMenuItem;
    N2: TMenuItem;
    procedure N2Click(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
    fBmp:TBitmap;
    fPoint:array of TPoint;
  public
    { Public 宣言 }
    procedure Analize(aBmp:TBitmap);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure PixcelSearch(var Pixcel:TPixcelDataArray; x,y:Integer; var vx,vy,vc:Integer);
begin
  if (Pixcel[x,y].Black=True) and     //黒
     (Pixcel[x,y].Checked=False) then //未チェック
  begin
    Pixcel[x,y].Checked:=True; //チェック
    vx := vx + x;
    vy := vy + y;
    vc := vc + 1;
    //4方向へ検索
    PixcelSearch(Pixcel,x+1,y  ,vx,vy,vc);
    PixcelSearch(Pixcel,x-1,y  ,vx,vy,vc);
    PixcelSearch(Pixcel,x  ,y+1,vx,vy,vc);
    PixcelSearch(Pixcel,x  ,y-1,vx,vy,vc);
  end;
end;

procedure TForm1.Analize(aBmp: TBitmap);
var
  i,x,y: Integer;
  //画像のピクセルデータを格納する
  //True=黒  False=白  の2値
  //座標にアクセスはPixcel[x,y]
  Pixcel:TPixcelDataArray;
  //ビットマップの画像データへのポインタ
  p:PInteger; //32bit値なのでPIntegerを使用
  //ビットマップのカラー値 32bitビットマップの色は
  //32bit = 8bit:A,R,G,B
  bR,bG,bB:Byte;

  //--------------------------------------------
  //黒い塊の重心を求めるアルゴリズム用
  vx,vy,vc:Integer;
const
  Threshold = 128; //白黒の閾値
begin
  //Pixcel配列の確保
  SetLength(Pixcel,aBmp.Width);
  for i := 0 to aBmp.Width-1 do
    SetLength(Pixcel[i],aBmp.Height);
  //その初期化
  for y := 0 to aBmp.Height-1 do
    for x := 0 to aBmp.Width-1 do
    begin
      Pixcel[x,y].Black:=False;
      Pixcel[x,y].Checked:=False;
    end;

  //Pixcel配列にビットマップのグレー値を格納
  //32bitビットマップを用いてピクセルデータを抽出しています。
  //この方法は32bitビットマップでのみ可能です
  aBmp.PixelFormat := pf32bit;
  //ビットマップのデータ座標は逆順に配列されている
  p:=aBmp.ScanLine[aBmp.Height-1];
  for y := aBmp.Height-1 downto 0 do
    for x := 0 to aBmp.Width-1 do
    begin
      bR := GetRValue(p^);
      bG := GetGValue(p^);
      bB := GetBValue(p^);
      inc(p); //ポインタを32bitシフト
                            //グレー化     //閾値で比較
      Pixcel[x,y].Black := (((bR+bG+bB)/3)<Threshold);
    end;

  //----------------------------------------------------------------------------
  //黒い塊の重心を求める
  //結果を格納する配列の初期化
  SetLength(fPoint,0);
  //順番に黒い点を探す
  for y := 0 to aBmp.Height-1 do
    for x := 0 to aBmp.Width-1 do
    begin
      //初期化
      vx := 0;
      vy := 0;
      vc := 0;
      //重心を求める
      PixcelSearch(Pixcel,x,y,vx,vy,vc);
      if vc > 0 then
      begin
        SetLength(fPoint,Length(fPoint)+1);
        fPoint[Length(fPoint)-1].X := Round(vx/vc);
        fPoint[Length(fPoint)-1].Y := Round(vy/vc);
      end;
    end;

  Repaint; //再描画
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  fBmp:=TBitmap.Create;
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  i: Integer;
begin
  Canvas.Draw(0,0,fBmp);
  Canvas.Brush.Color := clRed;
  Canvas.Pen.Color := clRed;
  for i := 0 to Length(fPoint)-1 do
    Canvas.Ellipse(fPoint[i].X - 2,
                   fPoint[i].Y - 2,
                   fPoint[i].X + 2,
                   fPoint[i].Y + 2
                   );
end;

//ファイルを開く
procedure TForm1.N2Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
  begin
    fBmp.LoadFromFile(OpenDialog1.FileName);
    Analize(fBmp);
  end;
end;

end.


プラ  2012-04-24 08:05:30  No: 42032

お時間を割いて大変丁寧にご回答いただき、ありがとうございます。

プログラミングの基礎力が足りていないので
画像処理以前の初歩的な質問となってしまうのですが

>begin
>  if (Pixcel[x,y].Black=True) and     //黒
>     (Pixcel[x,y].Checked=False) then //未チェック
>  begin
>    Pixcel[x,y].Checked:=True; //チェック
>    vx := vx + x;
>    vy := vy + y;
>    vc := vc + 1;
>    //4方向へ検索
>    PixcelSearch(Pixcel,x+1,y  ,vx,vy,vc);
>    PixcelSearch(Pixcel,x-1,y  ,vx,vy,vc);
>    PixcelSearch(Pixcel,x  ,y+1,vx,vy,vc);
>    PixcelSearch(Pixcel,x  ,y-1,vx,vy,vc);
>  end;
>end;

1番最初の注目画素がチェックされ、vc=1となった後に4方向を検索する場合
4方向の中に黒かつ未チェックと判定される場所があった場合、
そこもvc=1となるという意味でしょうか。


monaa  2012-04-24 18:49:03  No: 42033

アルゴリズムの基本である再帰処理というやつです。
ちなみに、引数にvarを入れると引数の値の変化を引き継ぐ事が出来ます。
詳しくはこの辺の単語でぐぐってみてください。

procedure Saiki(var i,p:Integer);
begin
  i:=i+1;
  p:=p+1;
  if i < 100 then
    Saiki(i,p);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i,p:Integer;
begin
  i:=30;
  p:=0;
  Saiki(i,p);
  ShowMessage('変数iが100になるまで' +
              IntToStr(p) +
              '回1を足しました。' );
end;

これらを踏まえた上で今回の関数を見ると
vcは注目座標のCheckedがFalseの時のみそれをTrueにして1増える事が分かります。
つまりTrueになった数[V]alue True [C]ountと言うことが分かります。
vx,vyはその座標を元のvx,vyに足した値です。[V]alue [X] axis Sum

0 1 2 3 4 5 6 7 8
□□□■■■■■□というピクセルがあった場合、重心は
(3+4+5+6+7)/5=5となりますよね。
vx = (3+4+5+6+7)
vc = 5
を意味します。


プラ  2012-04-30 07:38:01  No: 42034

お返事が遅くなり申し訳ありません。
色々と関係ありそうな事をネットで調べながら考えていました。
完全な独学なのでこのように分かりやすいように
例を示して導いていただいて大変ありがたく思います。

おかげ様で書いていただいたコードを何とか
理解することが出来、やりたいことの第一歩とすることができました。
示して下さったようなシンプルで美しいコードを
書くことはまだ当分出来なそうですが
少しづつトライして行こうと思います。
回答を頂けたおかげで前に進む原動力となりました。

まだ気になることは色々あるのですが
重心については解決出来ましたので
一旦クローズさせていただきたいと思います。
ありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加