画像認識

解決


あるふぁ  2008-07-16 00:41:55  No: 31263

ある画像の指定した範囲からを数字を抽出してEditに表示しようと考えております、検索したらOCRの技術を使えば可能なのでは?思ったのですが
どのようにすればいいのか全くわからず悩んでおります
過去に似たような事をされた方がおりましたら、ご教授お願いいたします


Mr.XRAY  2008-07-16 17:15:09  No: 31264

>ある画像の指定した範囲からを数字を抽出してEditに表示しようと考えております

う〜む.こればどんな目的なんでしょう.
文字認識の研究,趣味とか.

スキャナからでも画面でも基本的には考え方は同じだとは思うのですが,
いわゆる「文字認識」という分野ですね.
そう簡単に説明できない,というより私は説明できませんが,おそらくネット
上でサンプルを探し出すのは困難だと思います.
(市販品はけっこうあるんですが...)

たとえ知っていたとしても,処理のアルゴリズムを説明するのは容易ではな
いでしょう.

関係した学協会,研究会などに所属していると,研究目的のためにプログラム
の提供を受けることができることがあります.お互いの研究のためです.
(ここのQ and Aの掲示板も本来はそういう目的なんでしょうけどね)
もし,研究が目的であれば,そのような学会に参加するのも手です.

とりあえず,ネットで「文字認識」あるいは「文字認識  コンピュータ」
などで検索してみて,「文字認識」がいったいどんなものなのか調べてみて
はいかがでしょうか.

ちょっと見た感じでは以下のようなページがありました.
まっ,簡単ではないですね.
(ちなみに,私は文字認識処理関係の参考書をもっています.埃にまみれていますが)

http://pooh.fukushima-nct.ac.jp/nakaolab/sotuken/moji/RIRON.HTML
http://www.geocities.jp/itsuoinada/HOR_index/Horabstract.htm
(高い !!)
http://133.13.40.79/Syllabus/sub%5Cy-FIT.ppt 
(ブラウザの設定によってはダウンロードになるかも)

おおざっばな調査でいいと思いますので,もしよろしかったら

http://hpcgi2.nifty.com/Mr_XRAY/DelphiBBS/plcyclamen.cgi?tree=s1732#1732

を使ってやってみてください.
ただしテストで作成したので,動作は不安定です.とんてもないことが
起きるかも知れません(笑).


Mr.XRAY  2008-07-16 17:20:15  No: 31265

>http://133.13.40.79/Syllabus/sub%5Cy-FIT.ppt 
>(ブラウザの設定によってはダウンロードになるかも)

これは,このURLをブラウザのURL欄にコピペして起動してください.
ダブルクリックでは開らかないと思います.


Mr.XRAY  2008-07-16 17:56:18  No: 31266

追記です.
文字認識 DelphiでGoogleを検索してみてください.
解決策がみつかるわけではないのですが,もしかしたら何らかの参考になるかも
知れません.


あるふぁ  2008-07-16 18:58:39  No: 31267

Mr.XRAYさん返信ありがとうございます

何がやりたいかと言いますと、伝票をスキャンして商品コードを画像から読み取って1日に○○の商品が入荷等の帳票印字をやろうと思ってます

やはり数字認識だけとはいえ、難しそうですね・・・


もにゃ  2008-07-16 19:52:55  No: 31268

ほぼ単一の画像から数字を読み取るだけならそれほど難しくはないですよ。
僕なら、二階調化したあと、数字の特徴となりそうなピクセルの白黒判断してみます。

余談ですが、よくあるパスワード入力用の画像とかは本当に難問だと思います。


あるふぁ  2008-07-16 21:03:27  No: 31269

もにゅさん何か参考になりそうなサイト等はございますか?


スコピョン・キング  2008-07-16 21:30:37  No: 31270

森北出版「画像処理とパターン認識入門-基礎からVC#/VC++.NETによるプロジェクト作成まで-」
には色々なパターン認識アルゴリズムが載っていますね。
フーリエ識別子による数字認識  っていうのが最も近いかな。
ソースはここ(C#だけど)
http://www.morikita.co.jp/soft/84591/

でも、見たけどDelphiに移植するのは大変だ(意味無いジャン)

数字だけなら、しかも手書きじゃないなら、単純なテンプレートマッチングアルゴリズムで一から組んでも99.999%ぐらいの正解率は出来そう。


通りがかり  2008-07-16 22:59:50  No: 31271

画像を認識させるのでしたら、比較的単純な(印字)数字でも
ニ値化はもちろん(閾値など)、ノイズの除去などは必須で
更にパターン認識は基本となります。
(カスレ、汚れ、傾きなど悪条件が想定できるため)

通常のアプリケーション開発者でしたらその知識・技術の経験
だけでは極めて困難です。(..可能性は無いとは言えませんが)
その類の専門技術屋がいた方がよいかと・・・
(全部私がやった訳ではありませんが体験談です)

..こんな回答で申し訳ないですが、
    正直言ってかなり難しいです。


もにゃ  2008-07-16 23:12:30  No: 31272

汎用性を求めると画像認知アルゴリズムは極端に難しくなります。
アルゴリズムを簡単にするために、汎用性を捨てる場合、
自分の専用環境に特化したアルゴリズムを詰めていきますので自分の状況に適応したサンプルがネットに落ちていることなど皆無といってよいと思います。


もにゃ  2008-07-16 23:49:25  No: 31273

すこし冷たかった気がするので再投稿。
確かに、スコピョン・キングさんがおっしゃっている
「テンプレートマッチング」
が最適かと思います、これをキーワードにググってみてください。


あるふぁ  2008-07-17 18:31:12  No: 31274

もにゃさん返信ありがとうございます
「テンプレートマッチング」でググってみて頑張ってみます^^;

このあたりで解決とさせていただきますね


スコピョン  2008-07-22 19:56:32  No: 31275

もう十分遅い気がしますが、適当に作ったのでお披露目します。

image1 サイズ 370x330
image2 サイズ 30x38
image3 サイズ 370x57
image4 サイズ 370x57

button1 caption: 画像ペースト
button2 caption: 配列作成
button3 caption: 認識

memo1

を置いて(各イベントはアタッチ)、下記ユニットコピペで。
使い方は、「配列作成」ボタンをクリックしテンプレート配列を作成後、
image1で数値を2回りくらい大きめにマウスでドラッグし、「認識」ボタンクリック。
たたき台としてはまあまあの認識率です。きちんと文字の多きさを合わせれば99%以上は出るでしょう。
ほとんどのコードがWEBからのコピペでできています。いい時代だなぁ。
----------------------------------------
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Clipbrd, StdCtrls, ExtCtrls, math;

type
  TRGBTripleArray = array[0..High(Integer) div 3 - 1] of RGBTRIPLE;
  PRGBTripleArray = ^TRGBTripleArray; //配列型のポインタ

  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    Memo1: TMemo;
    Button2: TButton;
    Image2: TImage;
    Button3: TButton;
    Image3: TImage;
    Image4: TImage;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(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 Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

  FDrag: boolean; //マウスがドラッグ中かを示すフラグ
  DownPt: TPoint; //描く矩形の基点となるマウスがおろされた座標
  RR: TRect; //現在の選択矩形

  MatchSt: array[0..9] of array[0..31] of array[0..23] of Integer;

implementation

{$R *.dfm}

procedure AngleTextOut(CV: TCanvas; const sText: string; x, y, angle: integer; size: Integer);
var
  LogFont: TLogFont;
  SaveFont: TFont;
begin

  SaveFont := TFont.Create;
  SaveFont.Assign(CV.Font);

  GetObject(SaveFont.Handle, sizeof(TLogFont), @LogFont);

  with LogFont do
  begin

    lfEscapement := angle * 10; //角度は0.1度単位

    lfPitchAndFamily := FIXED_PITCH or FF_DONTCARE;

    lfQuality := ANTIALIASED_QUALITY; //最高画質
    lfHeight := size; //負
    lfWidth := 0; //ゼロ
    lfWeight := FW_EXTRABOLD; //太さ

       { Font Weights }
       {
        FW_DONTCARE = 0;         FW_EXTRABOLD = 800;
        FW_THIN = 100;           FW_HEAVY = 900;
        FW_EXTRALIGHT = 200;     FW_ULTRALIGHT = FW_EXTRALIGHT;
        FW_LIGHT = 300;          FW_REGULAR = FW_NORMAL;
        FW_NORMAL = 400;         FW_DEMIBOLD = FW_SEMIBOLD;
        FW_MEDIUM = 500;         FW_ULTRABOLD = FW_EXTRABOLD;
        FW_SEMIBOLD = 600;       FW_BLACK = FW_HEAVY;
        FW_BOLD = 700;
      }

  end;

  CV.Font.Handle := CreateFontIndirect(LogFont);
  SetBkMode(CV.Handle, TRANSPARENT);

  CV.TextOut(x, y, sText);

  CV.Font.Assign(SaveFont);
  SaveFont.Free;
end;

procedure BrendImage(imageA: TImage; imageB: TImage; rate: Integer);
var
  n, m: Integer;
  PA, PB: PRGBTripleArray;

  r, g, b: Integer;

begin

  for n := 0 to imageA.Picture.bitmap.height - 1 do
  begin
    PA := imageA.Picture.bitmap.ScanLine[n];
    PB := imageB.Picture.bitmap.ScanLine[n];
    for m := 0 to imageA.Picture.bitmap.width - 1 do
    begin
      r := PA[m].rgbtRed;
      g := PA[m].rgbtGreen;
      b := PA[m].rgbtBlue;

      PB[m].rgbtRed := (r * rate + PB[m].rgbtRed * (100 - rate)) div 100;
      PB[m].rgbtGreen := (r * rate + PB[m].rgbtGreen * (100 - rate)) div 100;
      PB[m].rgbtBlue := (r * rate + PB[m].rgbtBlue * (100 - rate)) div 100;

    end;
  end;

end;

procedure TForm1.Button1Click(Sender: TObject);
begin

  if Clipboard.HasFormat(CF_BITMAP) then
  begin
    Image1.Picture.Assign(Clipboard);
  end;

end;

procedure TForm1.Button2Click(Sender: TObject);
var
  gb: TBitmap;

  I, J, K, r, g, b, kido, posx, posy: Integer;
  PA, PB: PRGBTripleArray;
  sub, minsub, minnum: Integer;
  min_by_num: array[0..9] of Integer;

begin

  gb := TBitmap.Create;
  gb.Width := Image2.Width;
  gb.Height := Image2.Height;

    //前の矩形を消去
  Image1.Canvas.DrawFocusRect(RR);

  gb.Canvas.CopyRect(Rect(0, 0, Image2.Width, Image2.Height),

    Image1.Canvas,

    RR
    );

  Image2.Picture.Bitmap.Assign(gb);

  Image2.Picture.Bitmap.PixelFormat := pf24bit;

  gb.Free;

   //新たな座標で矩形を描く
  Image1.Canvas.DrawFocusRect(RR);

  Application.ProcessMessages;

  minsub := High(Integer);
  minnum := -1;

  //マッチング処理開始

  for K := 0 to 9 do   //10種類(0〜9)を順番に試す
  begin
     ;

    min_by_num[K] := High(Integer);

    for posy := 0 to 5 do     //座標を縦横1dotにずらしながら
    begin
      for posx := 0 to 5 do
      begin

        sub := 0;
        for I := 0 to 31 do    //24x32の矩形領域で比較
        begin

          PA := image2.Picture.bitmap.ScanLine[I + posy];

          for J := 0 to 23 do
          begin
            r := PA[J + posx].rgbtRed;
            g := PA[J + posx].rgbtGreen;
            b := PA[J + posx].rgbtBlue;

            kido := 256 - ((r + g + b) div 3);

            if kido <= 0 then
              kido := 0;

            if kido >= 256 then
              kido := 255;

            kido := kido div 16;

            sub := sub + abs(MatchSt[K][I][J] - kido);//差分絶対値和を積算している

          end;

        end;

        //算出した輝度の差分絶対値和が今までのものより小さいなら、その時点でのベストを更新
        if sub < minsub then
        begin
          minsub := sub;
          minnum := K;
        end;

        if sub < min_by_num[K] then  //数字毎のベストの集計
        begin
          min_by_num[K] := sub;
        end;
        

      end;
    end;

    Memo1.Lines.Add( Format( '%d:最小値=%d', [K,min_by_num[K]] ) );

  end;

  Memo1.Lines.Add('--------');
  Memo1.Lines.Add( Format( '認識値:%d', [minnum] ) );

end;

procedure TForm1.Button3Click(Sender: TObject);
var

  FontName: array[0..8] of AnsiString;
  I, J, K, fsize: Integer;
  ypos: Integer;

  PA, PB: PRGBTripleArray;

  r, g, b: Integer;
  St: AnsiString;
  kido: Integer;

begin

  FontName[0] := 'Times New Roman';
  FontName[1] := 'Lucida Console';
  FontName[2] := 'MS Pゴシック';
  FontName[3] := 'MS 明朝';
  FontName[4] := 'MS UI Gothic';
  FontName[5] := 'Tahoma';
  FontName[6] := 'Verdana';
  FontName[7] := 'Courier New';
  FontName[8] := 'Arial';

  image3.Picture.Bitmap.PixelFormat := pf24bit;
  image4.Picture.Bitmap.PixelFormat := pf24bit;

  image3.Picture.bitmap.Width := image3.Width;
  image4.Picture.bitmap.Width := image4.Width;

  image3.Picture.bitmap.height := image3.height;
  image4.Picture.bitmap.height := image4.height;

  for I := 0 to 8 do
  begin

    fsize := -32;
    ypos := 0;

    if I = 1 then
      ypos := 2;

    if I = 5 then       //フォント毎に微調整しないとうまくない
      fsize := -31
    else if I = 1 then
      fsize := -30
    else if I = 6 then
      fsize := -30
    else if I = 7 then
      fsize := -35
    else if I = 8 then
      fsize := -33;

    image3.Canvas.Font.Name := FontName[i];
    image1.Canvas.Font.Name := FontName[i];

    image3.Canvas.Brush.Color := clWhite;
    image3.Canvas.FillRect(Rect(0, 0, image3.Width, image3.height));

    for J := 0 to 9 do
    begin
      AngleTextOut(image3.canvas, IntToStr(J), 10 + 34 * J, 8 + ypos, 0, fsize);

      AngleTextOut(image1.canvas, IntToStr(J), 10 + 34 * J, 8 + 34 * I + ypos, 0, fsize);

    end;

    BrendImage(image3, image4, 100 div (I + 1));

  end;

  // 配列の生成。輝度はRGBの平均で代用。
  // また、0〜15に正規化している。
  for K := 0 to 9 do
  begin
      //Memo1.Lines.Add( IntToStr(K) + '--------------');

    for I := 0 to 31 do
    begin

      PA := image4.Picture.bitmap.ScanLine[I + 10]; 
         //St :='';

      for J := 0 to 23 do
      begin
        r := PA[K * 34 + J + 8].rgbtRed;
        g := PA[K * 34 + J + 8].rgbtGreen;
        b := PA[K * 34 + J + 8].rgbtBlue;

        kido := 256 - ((r + g + b) div 3);

        if kido <= 0 then
          kido := 0;

        if kido >= 256 then
          kido := 255;

        kido := kido div 16;

        MatchSt[K][I][J] := kido;

          //St := St+ ',' + Format( '%x', [kido] );
      end;
        //Memo1.Lines.Add( St);

    end;

  end;

end;

procedure TForm1.FormShow(Sender: TObject);
begin
  if Clipboard.HasFormat(CF_BITMAP) then
  begin
    Image1.Picture.Assign(Clipboard);
  end;

end;

procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then Exit;
   //前の矩形を消去
  Image1.Canvas.DrawFocusRect(RR);
  DownPt := Point(X, Y);
  FDrag := true;
  RR := Rect(X, Y, X, Y);
  Image1.Canvas.DrawFocusRect(RR);
end;

procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if FDrag then begin
   //前の矩形を消去
    Image1.Canvas.DrawFocusRect(RR);

   //現在のマウス位置と基点の座標から新たな矩形をセット
    RR := Rect(MIN(Downpt.X, X), MIN(Downpt.Y, Y),
      MAX(Downpt.X, X), MAX(Downpt.Y, Y));
   //新たな座標で矩形を描く
    Image1.Canvas.DrawFocusRect(RR);

  end;
end;

procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  //ドラッグ終了でフラグを Falseに
  FDrag := false;
end;

end.


スコピョン  2008-07-22 19:58:05  No: 31276

(修正)
すんません。下記が正しいです。

button1 caption: 画像ペースト
button3 caption: 配列作成
button2 caption: 認識


あるふぁ  2008-07-22 23:39:54  No: 31277

解決してるのにありがとうございます><
参考にさせていただきますね


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

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






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