肝心の正規化相関計算ルーチンの作成と検証
佳境に入ってきました。現時点で作成して基本的な動作確認をしておきましょう。正規化相関計算については、vmapの記事
を参考に願います。図を再掲すると、ncc(normalized cross correlation)は、

この図でfとgが画像の小領域(RectないしROI)として、nccを計算する具体的なルーチンは、
// NCC(正規化相互相関)を計算する関数
// TBitmapは24bit(pf24bit)または32bit(pf32bit)を想定
double CalculateNCC(TBitmap* target, TBitmap* templateImg, int startX, int startY)
{
// フォーマットを強制(ピクセルアクセスを単純化するためグレースケール化するか、輝度で計算)
target->PixelFormat = pf32bit;
templateImg->PixelFormat = pf32bit;
int tWidth = templateImg->Width;
int tHeight = templateImg->Height;
double sumT = 0, sumI = 0;
double sumTT = 0, sumII = 0;
double sumTI = 0;
double N = tWidth * tHeight;
for (int y = 0; y < tHeight; y++) {
// ScanLineによる高速ピクセルアクセス
RGBQUAD* rowT = (RGBQUAD*)templateImg->ScanLine[y];
RGBQUAD* rowI = (RGBQUAD*)target->ScanLine[startY + y];
for (int x = 0; x < tWidth; x++) {
// 輝度(Y)の計算 (簡易的にRGBの平均、または一般的RGB重み付け)
double valT = (rowT[x].rgbRed + rowT[x].rgbGreen + rowT[x].rgbBlue) / 3.0;
double valI = (rowI[startX + x].rgbRed + rowI[startX + x].rgbGreen + rowI[startX + x].rgbBlue) / 3.0;
sumT += valT;
sumI += valI;
sumTT += valT * valT;
sumII += valI * valI;
sumTI += valT * valI;
}
}
// NCC分子・分母の計算
double num = sumTI - (sumT * sumI) / N;
double den = std::sqrt((sumTT - (sumT * sumT) / N) * (sumII - (sumI * sumI) / N));
if (den == 0.0) return 0.0;
return num / den; // -1.0 から 1.0 の範囲。1.0に近いほど一致
}になります。引数並びをご覧になれば明白ですが、rawなimage bufferではなく、TBitmap同士の計算で行います。ただし内部ではグレイスケールで計算しています。つまり引数では32bitないし24bitのTBitmapが可です。これを使って、まずここまでで作成したtemplateなBitmapをロードしておきます。ファイルは今までで作った、frame0.pngからframe9.pngです。これをTBitmap*の配列digitsに入れておきます。
void LoadDigits()
{
for( int i = 0 ; i < 10 ; i++ ){
String fname = "frame" + IntToStr(i) + ".png";
TPngImage* img = new TPngImage();
img->LoadFromFile(fname);
Graphics::TBitmap* bmp32 = new Graphics::TBitmap();
bmp32->Assign(img);
digits[i] = bmp32;
Form1->Image2->Picture->Bitmap->Assign(bmp32);
}
}フォームは、

Unit1.hは、
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>
#include <Vcl.Dialogs.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE で管理されるコンポーネント
TImage *Image1;
TOpenDialog *OpenDialog1;
TButton *AddRect;
TImage *Image2;
TButton *Auto;
TMemo *Memo1;
void __fastcall FormDestroy(TObject *Sender);
void __fastcall Image1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
int X, int Y);
void __fastcall Image1MouseMove(TObject *Sender, TShiftState Shift, int X, int Y);
void __fastcall Image1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift,
int X, int Y);
void __fastcall FormPaint(TObject *Sender);
//void __fastcall OpenClick(TObject *Sender);
void __fastcall AddRectClick(TObject *Sender);
//void __fastcall SaveClick(TObject *Sender);
void __fastcall AutoClick(TObject *Sender);
private: // ユーザー宣言
bool FDrawing; // ドラッグ中かどうかのフラグ
TPoint FStartPos; // マウスを押し下げた初期座標
TPoint FLastPos; // 直前のマウス座標(線を消す用)
TPoint FEndPos;
TBitmap *FBkBmp; // 背景画像を一時保存するビットマップ
public: // ユーザー宣言
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endifUnit1.cppは、
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include <jpeg.hpp>
#include <Vcl.Imaging.pngimage.hpp>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TRect absarea;
int width;
int height;
int vheight;
TBitmap* digits[10];
TBitmap* targetbitmap;
// NCC(正規化相互相関)を計算する関数
// TBitmapは24bit(pf24bit)または32bit(pf32bit)を想定
double CalculateNCC(TBitmap* target, TBitmap* templateImg, int startX, int startY)
{
// フォーマットを強制(ピクセルアクセスを単純化するためグレースケール化するか、輝度で計算)
target->PixelFormat = pf32bit;
templateImg->PixelFormat = pf32bit;
int tWidth = templateImg->Width;
int tHeight = templateImg->Height;
double sumT = 0, sumI = 0;
double sumTT = 0, sumII = 0;
double sumTI = 0;
double N = tWidth * tHeight;
for (int y = 0; y < tHeight; y++) {
// ScanLineによる高速ピクセルアクセス
RGBQUAD* rowT = (RGBQUAD*)templateImg->ScanLine[y];
RGBQUAD* rowI = (RGBQUAD*)target->ScanLine[startY + y];
for (int x = 0; x < tWidth; x++) {
// 輝度(Y)の計算 (簡易的にRGBの平均、または一般的RGB重み付け)
double valT = (rowT[x].rgbRed + rowT[x].rgbGreen + rowT[x].rgbBlue) / 3.0;
double valI = (rowI[startX + x].rgbRed + rowI[startX + x].rgbGreen + rowI[startX + x].rgbBlue) / 3.0;
sumT += valT;
sumI += valI;
sumTT += valT * valT;
sumII += valI * valI;
sumTI += valT * valI;
}
}
// NCC分子・分母の計算
double num = sumTI - (sumT * sumI) / N;
double den = std::sqrt((sumTT - (sumT * sumT) / N) * (sumII - (sumI * sumI) / N));
if (den == 0.0) return 0.0;
return num / den; // -1.0 から 1.0 の範囲。1.0に近いほど一致
}
void LoadDigits()
{
for( int i = 0 ; i < 10 ; i++ ){
String fname = "frame" + IntToStr(i) + ".png";
TPngImage* img = new TPngImage();
img->LoadFromFile(fname);
Graphics::TBitmap* bmp32 = new Graphics::TBitmap();
bmp32->Assign(img);
digits[i] = bmp32;
Form1->Image2->Picture->Bitmap->Assign(bmp32);
}
}
void LoadJpegFile(const AnsiString & Src,
Graphics::TBitmap * Bitmap)
{
TJPEGImage * Jpeg = new TJPEGImage();
Jpeg->Performance = jpBestQuality;
Jpeg->PixelFormat = jf24Bit;
Jpeg->Grayscale = false;
Jpeg->LoadFromFile(Src);
Bitmap->HandleType = bmDIB;
Bitmap->PixelFormat = pf32bit;
Bitmap->Width = Jpeg->Width;
width = Jpeg->Width;
Bitmap->Height = Jpeg->Height;
height = Jpeg->Height;
Bitmap->Canvas->Draw(0,0,Jpeg);
vheight = 640* height / width ;
delete Jpeg;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
FDrawing = false;
FBkBmp = new TBitmap();
LoadDigits();
LoadJpegFile("frame0.jpg",FBkBmp);
//Image1->Canvas->Draw(0,0,FBkBmp);
TRect dest = {0,0,640,vheight};
Image1->Height = vheight;
Image1->Width = 640;
//Image1->Canvas->Draw(dest,FBkBmp);
Image1->Canvas->StretchDraw(dest,FBkBmp);
//Canvas->StretchDraw()
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete FBkBmp;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
int X, int Y)
{
if (Button == mbLeft) // 左クリックのみ対象
{
FDrawing = true;
FStartPos = Point(X, Y);
FLastPos = Point(X, Y);
// 現在の Image1 の状態(背景)を FBkBmp にコピーして保存
FBkBmp->Assign(Image1->Picture->Bitmap);
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseMove(TObject *Sender, TShiftState Shift, int X,
int Y)
{
if (FDrawing)
{
// 毎回バックアップから背景を書き戻す(古いドラッグ枠を消すため)
Image1->Picture->Bitmap->Assign(FBkBmp);
// 描画設定
Image1->Canvas->Pen->Color = clRed;
Image1->Canvas->Pen->Width = 1;
Image1->Canvas->Brush->Style = bsClear; // 中は透明
// 開始位置から現在のカーソル位置まで四角形を描画
Image1->Canvas->Rectangle(FStartPos.x, FStartPos.y, X, Y);
// 画面更新
FEndPos.x = X;
FEndPos.y = Y;
Image1->Refresh();
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift,
int X, int Y)
{
if (Button == mbLeft && FDrawing)
{
FDrawing = false; // ドラッグ終了
// 最後に確定した図形を描画
Image1->Canvas->Pen->Color = clRed;
Image1->Canvas->Pen->Width = 1;
Image1->Canvas->Brush->Style = bsClear;
Image1->Canvas->Rectangle(FStartPos.x, FStartPos.y, X, Y);
Image1->Refresh();
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
//Graphics::TBitmap * Bitmap = new Graphics::TBitmap();
//LoadJpegFile("IMG_0201.JPG",Bitmap);
//Image1->Canvas->Draw(0,0,Bitmap);
//delete Bitmap;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
void __fastcall TForm1::AddRectClick(TObject *Sender)
{
TRect area = { FStartPos.X+1,FStartPos.Y+1,FEndPos.X-1,FEndPos.Y-1};
int width2 = area.Width();
int height2 = area.Height();
Image2->Width = width2;
Image2->Height = height2;
TRect dst = {0,0,Image2->Width,Image2->Height};
Image2->Canvas->CopyRect(dst, Image1->Canvas, area);
absarea = area;
targetbitmap = Image2->Picture->Bitmap;
for( int i = 0 ; i < 10 ; i++ ){
double corr = CalculateNCC(targetbitmap, digits[i], 0, 0);
Memo1->Lines->Add(IntToStr(i) +" " + FloatToStr(corr));
}
}
//---------------------------------------------------------------------------
/*
void __fastcall TForm1::SaveClick(TObject *Sender)
{
Image2->Picture->SaveToFile("output.png");
}
*/
//---------------------------------------------------------------------------
void __fastcall TForm1::AutoClick(TObject *Sender)
{
// validate ncc using self-correlation
/*
double results;
results = CalculateNCC(digits[0], digits[0], 0, 0);
Memo1->Lines->Add("corr value for 0:0 " + FloatToStr(results));
for( int i = 0 ; i < 10 ; i++ )
for( int j = i+1 ; j < 10 ; j++ ){
results = CalculateNCC(digits[i], digits[j], 0, 0);
Memo1->Lines->Add("corr value for " + IntToStr(i) + ":" +IntToStr(j) + " " + FloatToStr(results));
}
*/
}
//---------------------------------------------------------------------------正規化相関係数計算の検証のために、digits[n]とdigites[m]を与えて係数を計算すると、n=mの場合は、当然1.0で、以下
corr value for 0:1 0.497403843568344
corr value for 0:2 0.513521340671304
corr value for 0:3 0.544676194603459
corr value for 0:4 0.441693910906116
corr value for 0:5 0.523433517943657
corr value for 0:6 0.673184580965456
corr value for 0:7 0.562422706729977
corr value for 0:8 0.855937674234769
corr value for 0:9 0.670953225098394
corr value for 1:2 0.144512768275543
corr value for 1:3 0.618791626149772
corr value for 1:4 0.703411902318827
corr value for 1:5 0.24580687335676
corr value for 1:6 0.110811423262732
corr value for 1:7 0.821422184485028
corr value for 1:8 0.43676599126348
corr value for 1:9 0.507283094887215
corr value for 2:3 0.674584734835016
corr value for 2:4 0.138069110287927
corr value for 2:5 0.386484401439309
corr value for 2:6 0.526528194702154
corr value for 2:7 0.248703178868609
corr value for 2:8 0.701244969210292
corr value for 2:9 0.44524429777504
corr value for 3:4 0.565195747453251
corr value for 3:5 0.720428444692662
corr value for 3:6 0.544930625583829
corr value for 3:7 0.66982517784849
corr value for 3:8 0.720440773478347
corr value for 3:9 0.794073718147514
corr value for 4:5 0.540371934843182
corr value for 4:6 0.415059229461342
corr value for 4:7 0.493116005050189
corr value for 4:8 0.615000558958087
corr value for 4:9 0.694134879989582
corr value for 5:6 0.855956808475601
corr value for 5:7 0.283619341465053
corr value for 5:8 0.696968736729038
corr value for 5:9 0.769361084245236
corr value for 6:7 0.165892166815509
corr value for 6:8 0.822315523564578
corr value for 6:9 0.629434363785216
corr value for 7:8 0.486173411804205
corr value for 7:9 0.594712156491278
corr value for 8:9 0.82662032412464
相関値が0.8以上を赤で示しました。結果は辻褄があっていて、例えば、digit 0と8では、並べてみれば明白ですが、


なので類似性が高いです。segmentが一本足りないだけですから。
同様にして、8と9では


同じくsegment一本の違いです。非常に簡単な検証ですが、一応それらしい動作はしていそうです。実際に”誤認”しなければおけなのですが、1回テストしてみましょう。

数値列が小さくて恐縮ですが、digits 0との一致度(相関値)が0.94で最大ですからこれでRectで囲った領域は”0”と判定されます。何故1.0にならないのかは、微妙に切り取り座標の位置ないし大きさがテンプレートとズレているからですね。誤認されなければ良いですね。実際の自動判定画面は、

なので、数字の大きさは3種類ありますから、a prioriでわかっている範囲でtemplateの拡大縮小ができないとダメですね。そのあたりと自動判定は以後の記事で書くかもしれません。一番大事な相互相関係数計算ルーチンはよさそうなので、山場は越えたのでしょうか。誤判定が少なく実用的に使えそうかは、今後の検証によります。



コメント