[BlueLeaf1336]> PROBLEMS> パターン認識への長い道のり>
history | TOP |
2006/04/21:作成
2006/04/21 | TOP |
次は、点を回転・移動します。また、点の配列をかたまりとして回転・移動したり拡大縮小したりします。まず最初に回転やら何やらの処理を集めたユニットを作成して、その中に簡単に点の回転を扱うためのクラスを定義します。
それから、テストとしてフォームにマウスを使って書いた四角形をぐるぐると回転させたり平行移動したり拡大・縮小したりするプログラムを作ってみます。
点の回転などをやるクラス | TOP |
//============================================================================= // Visual Basic によるはじめてのアルゴリズム入門(河西朝雄/技術評論社) // コンピュータ画像処理(田村秀行/オーム社) //============================================================================= unit Affine; interface uses Windows, SysUtils, Graphics, Types, Math; //----------------------------------------------------------------------------- // TPoint Double版 type TPointEx = record X: Double; Y: Double; end; PPointEx = ^TPointEx; TPointExArray = array of TPointEx; TPointArray = array of TPoint; //----------------------------------------------------------------------------- // 構造体初期化1 function PointEx(const AX, AY: Double): TPointEx; overload; // 構造体初期化2 function PointEx(): TPointEx; overload; // 構造体初期化3 function PointEx(const Pt: TPointEx): TPointEx; overload; // 構造体初期化4 function PointEx(const Pt: TPoint): TPointEx; overload; //----------------------------------------------------------------------------- // TPointとの相互変換 function PointExToPoint(const Pt: TPointEx): TPoint; overload; //----------------------------------------------------------------------------- // 符号反転 function InversePoint(const Pt: TPointEx): TPointEx; // 符号反転(引数変更版) function InversePoint2(var Pt: TPointEx): TPointEx; // 加算 function PlusPoint(const LeftPt, RightPt: TPointEx): TPointEx; // 減算 function MinusPoint(const LeftPt, RightPt: TPointEx): TPointEx; //----------------------------------------------------------------------------- // 平行移動 function Translate(const Pt: TPointEx; const X, Y: Double): TPointEx; overload; // 平行移動(引数変更版) function Translate2(var Pt: TPointEx; const X, Y: Double): TPointEx; overload; // 指定された点中心の回転移動 function Rotate(const Pt, Center: TPointEx; const Radian: Double): TPointEx; // 指定された点中心の回転移動(引数変更版) function Rotate2(var Pt: TPointEx; const Center: TPointEx; const Radian: Double): TPointEx; // 原点中心の回転移動 function RotateZ(const Pt: TPointEx; const Radian: Double): TPointEx; // 原点中心の回転移動(引数変更版) function RotateZ2(var Pt: TPointEx; const Radian: Double): TPointEx; // 指定された点中心の拡大・縮小 function Magnify(const Pt, Center: TPointEx; const Rate: Double): TPointEx; overload; // 指定された点中心の拡大・縮小(引数変更版) function Magnify2(var Pt: TPointEx; const Center: TPointEx; const Rate: Double): TPointEx; overload; // 原点中心の拡大・縮小 function MagnifyZ(const Pt: TPointEx; const Rate: Double): TPointEx; // 原点中心の拡大・縮小(引数変更版) function MagnifyZ2(var Pt: TPointEx; const Rate: Double): TPointEx; //----------------------------------------------------------------------------- // 中心点を求める function CenterPoint(const Pts: TPointExArray): TPointEx; // TRect を点の配列に変換 procedure Rect2PointEx(const Rt: TRect; var Pts: TPointExArray); //----------------------------------------------------------------------------- // 点の配列を描画 procedure DrawPoints(Canvas: TCanvas; const Pts: TPointArray; const Fill: Boolean); //============================================================================= // 回転する点を表すクラス //============================================================================= type TRotatePoints = class private FCount: Integer; FOriginalEx: TPointExArray; FOriginal: TPointArray; FResultEx: TPointExArray; FResult: TPointArray; FCenter: TPointEx; FTranslateX: Double; FTranslateY: Double; FRadian: Double; FRate: Double; // 回転の中心を設定する procedure SetCenter(const Pt: TPoint); // 回転の中心を取得する function GetCenter(): TPoint; public // コンストラクタ constructor Create(); // デストラクタ destructor Destroy(); override; public // 点の配列をクリア procedure Clear(); // 回転したい点の配列を設定する procedure SetPoint(const Pts: TPointExArray); overload; // 回転したい点の配列を設定する(TPoint版) procedure SetPoint(const Pts: TPointArray); overload; // 回転したい点の配列を設定する(TRect版) procedure SetPoint(const Rt: TRect); overload; // 回転角を「度」で設定する procedure SetDegree(const Degree: Double); // 点の配列の中心を回転の中心に設定する procedure SetCenterOfPts(); // プロパティ property CenterEx: TPointEx read FCenter write FCenter; property Center: TPoint read GetCenter write SetCenter; property TranslateX: Double read FTranslateX write FTranslateX; property TranslateY: Double read FTranslateY write FTranslateY; property Radian: Double read FRadian write FRadian; property Rate: Double read FRate write FRate; property OriginalEx: TPointExArray read FOriginalEx; property Original: TPointArray read FOriginal; property ResultEx: TPointExArray read FResultEx; property Result: TPointArray read FResult; public // 変換実行 procedure Execute(); // 変換前の点を描画を描画 procedure DrawOriginal(Canvas: TCanvas; const Fill: Boolean); // 変換後の点の配列を描画 procedure DrawResult(Canvas: TCanvas; const Fill: Boolean); end; implementation //============================================================================= // UTILITY //============================================================================= //----------------------------------------------------------------------------- // 構造体初期化1 function PointEx(const AX, AY: Double): TPointEx; begin Result.X := AX; Result.Y := AY; end; //----------------------------------------------------------------------------- // 構造体初期化2 function PointEx(): TPointEx; begin Result := PointEx(0, 0); end; //----------------------------------------------------------------------------- // 構造体初期化3 function PointEx(const Pt: TPointEx): TPointEx; begin Result := PointEx(Pt.X, Pt.Y); end; //----------------------------------------------------------------------------- // 構造体初期化4 function PointEx(const Pt: TPoint): TPointEx; begin Result := PointEx(Pt.X, Pt.Y); end; //----------------------------------------------------------------------------- // TPointとの相互変換2 function PointExToPoint(const Pt: TPointEx): TPoint; begin Result := Types.Point(Round(Pt.X), Round(Pt.Y)); end; //----------------------------------------------------------------------------- // 符号反転 function InversePoint(const Pt: TPointEx): TPointEx; begin Result := PointEx(-Pt.X, -Pt.Y); end; //----------------------------------------------------------------------------- // 符号反転(引数変更版) function InversePoint2(var Pt: TPointEx): TPointEx; begin Pt := InversePoint(Pt); Result := Pt; end; //----------------------------------------------------------------------------- // 加算 function PlusPoint(const LeftPt, RightPt: TPointEx): TPointEx; begin Result := PointEx(LeftPt.X + RightPt.X, LeftPt.Y + RightPt.Y); end; //----------------------------------------------------------------------------- // 減算 function MinusPoint(const LeftPt, RightPt: TPointEx): TPointEx; begin Result := PlusPoint(LeftPt, InversePoint(RightPt)); end; //----------------------------------------------------------------------------- // 平行移動 function Translate(const Pt: TPointEx; const X, Y: Double): TPointEx; begin Result := PlusPoint(Pt, PointEx(X, Y)); end; //----------------------------------------------------------------------------- // 平行移動(引数変更版) function Translate2(var Pt: TPointEx; const X, Y: Double): TPointEx; begin Pt := Translate(Pt, X, Y); Result := Pt; end; //----------------------------------------------------------------------------- // 指定された点中心の回転移動 function Rotate(const Pt, Center: TPointEx; const Radian: Double): TPointEx; var Sin, Cos: Extended; Pt1: TPointEx; begin Math.SinCos(Radian, Sin, Cos); Pt1 := MinusPoint(Pt, Center); Result.X := Pt1.X * Cos - Pt1.Y * Sin; Result.Y := Pt1.X * Sin + Pt1.Y * Cos; Result := PlusPoint(Result, Center); end; //----------------------------------------------------------------------------- // 指定された点中心の回転移動(引数変更版) function Rotate2(var Pt: TPointEx; const Center: TPointEx; const Radian: Double): TPointEx; begin Pt := Rotate(Pt, Center, Radian); Result := Pt; end; //----------------------------------------------------------------------------- // 原点中心の回転移動 function RotateZ(const Pt: TPointEx; const Radian: Double): TPointEx; begin Result := Rotate(Pt, PointEx(), Radian); end; //----------------------------------------------------------------------------- // 原点中心の回転移動(引数変更版) function RotateZ2(var Pt: TPointEx; const Radian: Double): TPointEx; begin Pt := RotateZ(Pt, Radian); Result := Pt; end; //----------------------------------------------------------------------------- // 指定された点中心の拡大・縮小 function Magnify(const Pt, Center: TPointEx; const Rate: Double): TPointEx; var Pt1: TPointEx; begin Pt1 := MinusPoint(Pt, Center); Result := PointEx(Pt1.X * Rate, Pt1.Y * Rate); Result := PlusPoint(Result, Center); end; //----------------------------------------------------------------------------- // 指定された点中心の拡大・縮小(引数変更版) function Magnify2(var Pt: TPointEx; const Center: TPointEx; const Rate: Double): TPointEx; begin Pt := Magnify(Pt, Center, Rate); Result := Pt; end; //----------------------------------------------------------------------------- // 原点中心の拡大・縮小 function MagnifyZ(const Pt: TPointEx; const Rate: Double): TPointEx; begin Result := Magnify(Pt, PointEx(), Rate); end; //----------------------------------------------------------------------------- // 原点中心の拡大・縮小(引数変更版) function MagnifyZ2(var Pt: TPointEx; const Rate: Double): TPointEx; begin Pt := MagnifyZ(Pt, Rate); Result := Pt; end; //----------------------------------------------------------------------------- // 中心点を求める function CenterPoint(const Pts: TPointExArray): TPointEx; var i, Len: Integer; X, Y: array of Double; begin Len := Length(Pts); SetLength(X, Len); SetLength(Y, Len); for i := 0 to Len - 1 do begin X[i] := Pts[i].X; Y[i] := Pts[i].Y; end; if (Len > 0) then begin Result := PointEx(Math.Mean(X), Math.Mean(Y)); end else begin Result := PointEx(); end; end; //----------------------------------------------------------------------------- // TRect を点の配列に変換 procedure Rect2PointEx(const Rt: TRect; var Pts: TPointExArray); begin SetLength(Pts, 4); Pts[0] := PointEx(Rt.Left, Rt.Top); Pts[1] := PointEx(Rt.Left, Rt.Bottom); Pts[2] := PointEx(Rt.Right, Rt.Bottom); Pts[3] := PointEx(Rt.Right, Rt.Top); end; //----------------------------------------------------------------------------- // 点の配列を描画 procedure DrawPoints(Canvas: TCanvas; const Pts: TPointArray; const Fill: Boolean); var i, Len: integer; Pts1: TPointArray; OldStyle: TBrushStyle; begin // 先頭点と末尾点をつなげる Len := Length(Pts); SetLength(Pts1, Len + 1); for i := 0 to Len - 1 do Pts1[i] := Pts[i]; Pts1[Len] := Pts1[0]; // 描画 OldStyle := Canvas.Brush.Style; if (Fill) then begin Canvas.Brush.Style := bsSolid; Canvas.Polyline(Pts); end else begin Canvas.Brush.Style := bsClear; Canvas.Polygon(Pts); end; Canvas.Brush.Style := OldStyle; end; //============================================================================= // 回転する点を表すクラス //============================================================================= //----------------------------------------------------------------------------- // 回転の中心を設定する procedure TRotatePoints.SetCenter(const Pt: TPoint); begin FCenter := PointEx(Pt); end; //----------------------------------------------------------------------------- // 回転の中心を取得する function TRotatePoints.GetCenter(): TPoint; begin Result := PointExToPoint(FCenter); end; //----------------------------------------------------------------------------- // コンストラクタ constructor TRotatePoints.Create(); begin inherited; FCount := 0; FCenter := PointEx(); FTranslateX := 0; FTranslateY := 0; FRadian := 0; FRate := 1; end; //----------------------------------------------------------------------------- // デストラクタ destructor TRotatePoints.Destroy(); begin inherited; end; //----------------------------------------------------------------------------- // 点の配列をクリア procedure TRotatePoints.Clear(); begin // メンバ初期化 Create(); // 配列初期化 SetLength(FOriginalEx, 0); SetLength(FOriginal, 0); SetLength(FResultEx, 0); SetLength(FResult, 0); end; //----------------------------------------------------------------------------- // 回転したい点の配列を設定する procedure TRotatePoints.SetPoint(const Pts: TPointExArray); var i: Integer; begin // 点の個数を決定 FCount := Length(Pts); // 配列のサイズを決定 SetLength(FOriginalEx, FCount); SetLength(FOriginal, FCount); SetLength(FResultEx, FCount); SetLength(FResult, FCount); // 点をコピー for i := 0 to FCount - 1 do begin FOriginalEx[i] := Pts[i]; FOriginal[i] := PointExToPoint(Pts[i]); end; end; //----------------------------------------------------------------------------- // 回転したい点の配列を設定する(TPoint版) procedure TRotatePoints.SetPoint(const Pts: TPointArray); var i: Integer; begin // 点の個数を決定 FCount := Length(Pts); // 配列のサイズを決定 SetLength(FOriginalEx, FCount); SetLength(FOriginal, FCount); SetLength(FResultEx, FCount); SetLength(FResult, FCount); // 点をコピー for i := 0 to FCount - 1 do begin FOriginalEx[i] := PointEx(Pts[i]); FOriginal[i] := Pts[i]; end; end; //----------------------------------------------------------------------------- // 回転したい点の配列を設定する(TRect版) procedure TRotatePoints.SetPoint(const Rt: TRect); var Pts: TPointExArray; begin Rect2PointEx(Rt, Pts); SetPoint(Pts); end; //----------------------------------------------------------------------------- // 回転角を「度」で設定する procedure TRotatePoints.SetDegree(const Degree: Double); begin FRadian := DegToRad(Degree); end; //----------------------------------------------------------------------------- // 点の配列の中心を回転の中心に設定する procedure TRotatePoints.SetCenterOfPts(); begin FCenter := CenterPoint(FOriginalEx); end; //----------------------------------------------------------------------------- // 変換実行 procedure TRotatePoints.Execute(); var i: Integer; Pt: TPointEx; begin for i := 0 to FCount - 1 do begin Pt := FOriginalEx[i]; // 回転移動 Rotate2(Pt, FCenter, FRadian); // 拡大 Magnify2(Pt, FCenter, FRate); // 平行移動 Translate2(Pt, FTranslateX, FTranslateY); // 保持 FResultEx[i] := Pt; FResult[i] := PointExToPoint(Pt); end; end; //----------------------------------------------------------------------------- // 変換前の点を描画を描画 procedure TRotatePoints.DrawOriginal(Canvas: TCanvas; const Fill: Boolean); begin DrawPoints(Canvas, FOriginal, Fill); end; //----------------------------------------------------------------------------- // 変換後の点の配列を描画 procedure TRotatePoints.DrawResult(Canvas: TCanvas; const Fill: Boolean); begin DrawPoints(Canvas, FResult, Fill); end; end.
テストプログラム | TOP |
ここにソースコードを乗せる意味はほとんどないし、関係ないところのコードが本来の部分を押しつぶしてますが、勢いで。
//----------------------------------------------------------------------------- // コンストラクタ procedure TForm1.FormCreate(Sender: TObject); begin // 回転処理クラス FRotater := TRotatePoints.Create(); // マウス操作中かどうか FDragging := False; // ダブルバッファ用ビットマップ FBitmap := TBitmap.Create(); FBitmap.Width := PaintBox1.Width; FBitmap.Height := PaintBox1.Height; PaintBox1.ControlStyle := PaintBox1.ControlStyle + [csOpaque]; FBitmap.Canvas.Font.Assign(PaintBox1.Font); end; //----------------------------------------------------------------------------- // デストラクタ procedure TForm1.FormDestroy(Sender: TObject); begin FBitmap.Free(); FRotater.Free(); end; //----------------------------------------------------------------------------- // 描画イベント procedure TForm1.PaintBox1Paint(Sender: TObject); var OldBkMode: Integer; begin // 背景塗りつぶし FBitmap.Canvas.FillRect(PaintBox1.ClientRect); // 回転の中心を四角形の中心に持ってくる FRotater.SetCenterOfPts(); // 回転・平行移動・拡大縮小実行 FRotater.Execute(); // 処理前の四角形を青で描画 FBitmap.Canvas.Pen.Color := clBlue; FRotater.DrawOriginal(FBitmap.Canvas, False); // 処理後の四角形を赤で描画 FBitmap.Canvas.Pen.Color := clRed; FRotater.DrawResult(FBitmap.Canvas, False); FBitmap.Canvas.Pen.Color := clBlack; // パラメータ出力 OldBkMode := SetBkMode(FBitmap.Canvas.Handle, TRANSPARENT); FBitmap.Canvas.TextOut(0, 0, FormatFloat('X = 0.00', FRotater.TranslateX)); FBitmap.Canvas.TextOut(0, 12, FormatFloat('Y = 0.00', FRotater.TranslateY)); FBitmap.Canvas.TextOut(0, 24, FormatFloat('Radian = 0.00', FRotater.Radian)); FBitmap.Canvas.TextOut(0, 36, FormatFloat('Rate = 0.00', FRotater.Rate)); SetBkMode(FBitmap.Canvas.Handle, OldBkMode); // ダブルバッファから画面に転送 BitBlt(PaintBox1.Canvas.Handle, 0, 0, PaintBox1.Width, PaintBox1.Height, FBitmap.Canvas.Handle, 0, 0, SRCCOPY); end; //----------------------------------------------------------------------------- // 操作開始 procedure TForm1.PaintBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin // 操作中フラグON FDragging := True; // 操作開始点を記録 FStartPos := Point(X, Y); // 描画モード if (SpeedButton1.Down) then begin // いちからやり直し FRotater.Clear(); end; PaintBox1.Repaint(); end; //----------------------------------------------------------------------------- // 操作中 procedure TForm1.PaintBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var Pt: TPoint; begin // この点を記録 Pt := Point(X, Y); // 操作中フラグがONなら if (FDragging) then begin // 描画モード if (SpeedButton1.Down) then begin // 操作対象となる点の配列を設定する FRotater.SetPoint(Rect(FStartPos, Pt)); end // 平行移動モード else if (SpeedButton2.Down) then begin // 横方向に移動 FRotater.TranslateX := FRotater.TranslateX + (Pt.X - FStartPos.X); // 縦方向に移動 FRotater.TranslateY:= FRotater.TranslateY + (Pt.Y - FStartPos.Y); // 次の操作に備えて処理開始点を更新 FStartPos := Pt; end // 回転移動モード else if (SpeedButton3.Down) then begin // 回転 FRotater.Radian := FRotater.Radian + DegToRad(Pt.X - FStartPos.X); // 回転(こっちはなくてもテストにはなるけれどもついでに) FRotater.Radian := FRotater.Radian + DegToRad(Pt.Y - FStartPos.Y); // 次の操作に備えて処理開始点を更新 FStartPos := Pt; end // 拡大モード else if (SpeedButton4.Down) then begin // 拡大あるいは縮小 FRotater.Rate := FRotater.Rate + DegToRad(Pt.X - FStartPos.X); // 拡大あるいは縮小(ついで) FRotater.Rate := FRotater.Rate + DegToRad(Pt.Y - FStartPos.Y); // 次の操作に備えて処理開始点を更新 FStartPos := Pt; end; PaintBox1.Repaint(); end; end; //----------------------------------------------------------------------------- // 操作確定 procedure TForm1.PaintBox1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Pt: TPoint; begin Pt := Point(X, Y); if (FDragging) then begin FDragging := False; // 描画モード if (SpeedButton1.Down) then begin // 最終的な四角形を設定 FRotater.SetPoint(Rect(FStartPos, Pt)); end // 平行移動モード else if (SpeedButton2.Down) then begin FRotater.TranslateX := FRotater.TranslateX + (Pt.X - FStartPos.X); FRotater.TranslateY:= FRotater.TranslateY + (Pt.Y - FStartPos.Y); FStartPos := Pt; end // 回転移動モード else if (SpeedButton3.Down) then begin FRotater.Radian := FRotater.Radian + DegToRad(Pt.X - FStartPos.X); FRotater.Radian := FRotater.Radian + DegToRad(Pt.Y - FStartPos.Y); FStartPos := Pt; end // 拡大モード else if (SpeedButton4.Down) then begin FRotater.Rate := FRotater.Rate + DegToRad(Pt.X - FStartPos.X); FRotater.Rate := FRotater.Rate + DegToRad(Pt.Y - FStartPos.Y); FStartPos := Pt; end; PaintBox1.Repaint(); end; end;
実行結果 | TOP |
まったく面白くないプログラムですが、白い部分をマウスでドラッグすることでいろいろ(?)できます。事前に、「四角形を定義する」「定義した四角形を平行移動する」「回転する」「拡大縮小する」のいずれを行うかを指定します。
TestAffine.zip(173,851Bytes) ソースコードと実行ファイルです。
ここでは、単に枠だけを扱っているので、四隅の点だけですんでいますが、実際には中身のぎっしり詰まった画像をいろいろとやることになります。図では「青」が処理前、「赤」が処理後をあらわしています。イメージ的には、「青」にあたるのがパターン認識のパターン画像のつもりです。
で、そのパターン画像を移動したり回転したりすると、認識したい原画像の特定の場所に対応することになります。そして対応した場所の画像を、「青」と比較できるようにどうにかして(今のところは、青→赤の変換の逆変換)、マッチしてるかどうかを比較する感じで考えています。
多分、重要な仮定となるのが、「元の四角形の中心に対して回転する」というところだと思います。平行移動や拡大縮小(実際には拡大縮小も元の四角形の中心に対して行いますが)は適用順序に意味はありませんが、この仮定がない場合回転移動が厄介なものになってきます。少し平行移動して特定の点に対して回転してまた平行移動してまた別の点に対して回転して...なんてことを考え出すと日が暮れます。すべての操作を記録しておいてその順に「再生」しないといけなくなります。
逆に、この仮定を持ち込むことで、平行移動・回転移動・拡大縮小 をどの順序で適用しても同じ結果にもっていけます。
それから、今のところ、パターンとなる画像は小さめにする予定なので、パターン画像を拡大して原画像と比較するのか、原画像の対応する場所を縮小してパターン画像と比較するのかを決める必要はありますが、いずれにしても、パターン画像を縮小して原画像と比較することは考えないようにしようと思っています。というのも今回のテキストにしている本(C言語による画像処理入門 昭晃堂)がそうしているからですが。
テキストでは、パターン画像(顔っぽい感じのもの)を、32x32 としています。なので、それにあわせようかと。
EOF | TOP |