Delphiでお手軽プログラミング

Delphiでお手軽プログラミングメニュー

DelphiでFANN(Fast Artificial Neural Network Library)を使って手書き数字(0~9)をAIに認識させる


FANNを使用する為のファイルの準備

https://mam-mam.net/delphi/fann.html からfannfloat.dll、fann.pas、MamFann.pasをダウンロードする。

トレーニングの為の手書き数字文字(0~9)を大量に作成する。

ひたすら手書き数字(0~9)の画像(16×16ピクセル)を作成しました。




















各画像(24Bit画像ですが、モノクロなので各1ドットのRGBはすべて同じ値のはず)の 各縦方向のRの値を足して正規化(0~1の間の値にする)したものと、 各横方向のRの値を足して正規化した合計32個のデータをFANNに学習させ、作成(出力)した学習結果ファイルを準備します。
学習結果ファイル number_train.net のダウンロード

Delphiを起動して新規作成を行い、学習結果ファイル number_train.netを使用して手書き数字(0~9)認識を行うプログラムを作成する

Delphi起動⇒ファイル⇒新規作成⇒WindowsVCLアプリケーション を選択します。
TImage 1個、TLabel 1個、TButton 2個、 TMemo 1個をフォームへドラッグ&ドロップします。

ソースコードを記述する

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls
  ,fann,mamfann;

type
  TForm1 = class(TForm)
    Image1: TImage;
    Label1: TLabel;
    Button1: TButton;
    Button2: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Image1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    procedure resetBmp();
    procedure createInputs(
      bm:TBitmap;
      var inp:array of TFann_type);
  public
    { Public 宣言 }
    md:boolean;
    pt:TPoint;
    bmp,bmp2:TBitmap;
    MamFann: TMamFann;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  resetBmp;
end;

procedure TForm1.Button2Click(Sender: TObject);
type
  trgb=record
    b,g,r:byte;
  end;
  prgb=^trgb;
var mbmp:TBitmap;
    x,y:integer;
    xmax,ymax,xmin,ymin:integer;
    ww,hh:integer;
    l,t:integer;
    rgb:prgb;
    inputs:array[0..31] of TFann_type;
    outputs:array[0..9] of TFann_type;
    i:integer;
    bigbmp:TBitmap;
    smax:single;
    num:integer;
begin
  bigbmp:=TBitmap.Create;
  bigbmp.PixelFormat:=pf24bit;
  bigbmp.Width:=bmp.Width*3;
  bigbmp.Height:=bmp.Height*3;
  bigbmp.Canvas.Brush.Color:=$ffffff;
  bigbmp.Canvas.Brush.Style:=bsSolid;
  bigbmp.Canvas.FillRect(
    Rect(0,0,bigbmp.Width,bigbmp.Height)
  );
  bigbmp.Canvas.CopyMode:=cmSrcCopy;
  bigbmp.Canvas.Draw(bmp.Width,bmp.Height,bmp);

  xmax:=-1;
  ymax:=-1;
  xmin:=bigbmp.Width-1;
  ymin:=bigbmp.Height-1;
  for y := 0 to bigbmp.Height-1 do
  begin
    rgb:=bigbmp.ScanLine[y];
    for x := 0 to bigbmp.Width-1 do
    begin
      if rgb.b<>255 then
      begin
        if xmax<x then xmax:=x;
        if xmin>x then xmin:=x;
        if ymax<y then ymax:=y;
        if ymin>y then ymin:=y;
      end;
      inc(rgb);
    end;
  end;
  if(xmax=-1) then
  begin
    bigbmp.Free;
    exit;//何も描画していない
  end;
  ww:=xmax-xmin+1;
  hh:=ymax-ymin+1;
  if ww>hh then hh:=ww else ww:=hh;
  l:=(xmin+xmax - ww) div 2;
  t:=(ymin+ymax - hh) div 2;

  mbmp:=TBitmap.Create;
  mbmp.PixelFormat:=pf24bit;
  mbmp.Width:=16;
  mbmp.Height:=16;
  mbmp.Canvas.Brush.Color:=$ffffff;
  mbmp.Canvas.FillRect(
    Rect(0,0,mbmp.Width,mbmp.Height)
  );
  SetStretchBltMode(mbmp.Canvas.Handle,HALFTONE);
  StretchBlt(
    mbmp.Canvas.Handle,0,0,mbmp.Width,mbmp.Height,
    bigbmp.Canvas.Handle,l,t,ww,hh,
    SRCCOPY
  );
  createInputs(mbmp,inputs);
  mbmp.Free;

  MamFann.Run(inputs,outputs);
  Memo1.Lines.Clear;


  smax:=0;
  num:=0;
  for i := 0 to length(outputs)-1 do
  begin
    Memo1.Lines.Add(Format('%1.1d : %5.3f',[i,outputs[i]]));
    if outputs[i]>smax then
    begin
      smax:=outputs[i];
      num:=i;
    end;
  end;
  Label1.Caption:=IntToStr(num)+'を描きましたよね?';
end;

procedure TForm1.createInputs(bm: TBitmap; var inp: array of TFann_type);
var x,y:integer;
    sxy:integer;
begin
  for x := 0 to bm.Width-1 do
  begin
    sxy:=0;
    for y := 0 to bm.Height-1 do
      sxy:=sxy+(bm.Canvas.Pixels[x,y] and $ff);
    inp[x]:=sxy/(255*bm.Height);
  end;
  for y := 0 to bm.Height-1 do
  begin
    sxy:=0;
    for x := 0 to bm.Width-1 do
      sxy:=sxy+(bm.Canvas.Pixels[x,y] and $ff);
    inp[y+16]:=sxy/(255*bm.Width);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var NeuronNumInLayer:array of Cardinal;
begin
  Image1.Width:=256;
  Image1.Height:=256;
  md:=False;
  bmp:=TBitmap.Create;
  bmp2:=TBitmap.Create;
  resetBmp;
  //TMamFannクラスのインスタンス化
  if Assigned(MamFann) then
    FreeAndNil(MamFann);
  setlength(NeuronNumInLayer,4);//レイヤー(層)の数
  NeuronNumInLayer[0]:=32;       //入力層のニューロン数
  NeuronNumInLayer[1]:=25;       //中間層のニューロン数
  NeuronNumInLayer[2]:=20;       //中間層のニューロン数
  NeuronNumInLayer[3]:=10;       //出力層のニューロン数
  MamFann:=TMamFann.Create(NeuronNumInLayer);
  MamFann.LoadFromFile('number_train.net');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  bmp.Free;
  bmp2.Free;
  MamFann.Free;
end;

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if md=False then
  begin
    pt.X:=X;
    pt.Y:=Y;
    md:=True;
  end;
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if md=True then
  begin
    bmp.Canvas.MoveTo(pt.X,pt.Y);
    bmp.Canvas.LineTo(X,Y);
    bmp2.Canvas.MoveTo(pt.X,pt.Y);
    bmp2.Canvas.LineTo(X,Y);
    Image1.Picture.Bitmap.Assign(bmp2);
    pt.X:=X;
    pt.Y:=Y;
  end;
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if md=True then
  begin
    bmp.Canvas.MoveTo(pt.X,pt.Y);
    bmp.Canvas.LineTo(X,Y);
    bmp2.Canvas.MoveTo(pt.X,pt.Y);
    bmp2.Canvas.LineTo(X,Y);
    Image1.Picture.Bitmap.Assign(bmp2);
    pt.X:=X;
    pt.Y:=Y;
    md:=False;
  end;
end;

procedure TForm1.resetBmp;
begin
  bmp.Width:=Image1.Width;
  bmp.Height:=Image1.Height;
  bmp.PixelFormat:=pf24bit;
  bmp.Canvas.Brush.Style:=bsSolid;
  bmp.Canvas.Brush.Color:=$FFFFFF;
  bmp.Canvas.FillRect(Rect(0,0,bmp.Width,bmp.Height));

  bmp.Canvas.Pen.Width:=16;
  bmp.Canvas.Pen.Style:=psSolid;
  bmp.Canvas.Pen.Color:=$000000;

  bmp2.Width:=Image1.Width;
  bmp2.Height:=Image1.Height;
  bmp2.PixelFormat:=pf24bit;
  bmp2.Canvas.Brush.Style:=bsSolid;
  bmp2.Canvas.Brush.Color:=$FFFFFF;
  bmp2.Canvas.FillRect(Rect(0,0,bmp2.Width,bmp2.Height));
  bmp2.Canvas.Pen.Width:=1;
  bmp2.Canvas.Pen.Style:=psSolid;
  bmp2.Canvas.Pen.Color:=$777777;
  bmp2.Canvas.MoveTo(0,bmp.Height div 2);
  bmp2.Canvas.LineTo(bmp.Width,bmp.Height div 2);
  bmp2.Canvas.MoveTo(bmp.Width div 2,0);
  bmp2.Canvas.LineTo(bmp.Width div 2,bmp.Height);
  bmp2.Canvas.Pen.Width:=16;
  bmp2.Canvas.Pen.Style:=psSolid;
  bmp2.Canvas.Pen.Color:=$000000;
  Image1.Picture.Bitmap.Assign(bmp2);
end;

end.

実行する

実行ファイルが生成させるフォルダ内に「fannfloat.dll」と「number_train.net」を配置するのを忘れずに。(又はc:\windosフォルダ等に配置する)
実行ボタンを押して実行します。(デバッグ実行でもOK)

Image1に数字を手書き(マウスドラッグでもOK)し、Button2(認識)ボタンをクリックすると、 描いた数字が何か応答します。



Copyright 2020 Mam