[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 |