応用プログラミング 第10回
~プログラミングの応用
画像処理その3~
電気通信大学電子工学専攻
Intelligent Electronic Systems Group
長井 隆行
2本日の内容
1. typedefと#define 2. カラーからグレースケールへ 3. 輝度の変換 4. 画像の2値化 5. 画像を認識する 画像認識 文字認識 パターン(テンプレート)マッチング 6. 数字認識の例 7. 課題補足
BYTEについて p9-2.cでいきなり出現 unsigned char 型の別名を定義しただけ (定義はmybmp.hに書いてあります) #include <stdio.h>#include <stdlib.h> /*mallocを使うために必要*/ #include <memory.h> /*memsetを使うために必要*/
#pragma pack(2) /*構造体のメンバーの境界整列を制御(おまじないだと思ってよい)*/ #define BMP_HEADER_SIZE 54 /*ビットマップのヘッダは全部で54バイトあります*/
typedef unsigned char BYTE;
/*ビットマップファイルヘッダのための構造体定義*/ ・・・ mybmpx.hの最初の方
typedef
typedef を使うと既存の型を自分の好きな呼び名で呼ぶ ことができる unsigned charというのは長くてうつのが面倒なので、 BYTEという別名を定義した 構造体などにも使える struct tagBITMAPFILEHEADER bmfh; typedefstruct tagBITMAPFILEHEADER{
unsigned short bfType; unsigned long bfSize; unsigned short bfReserved1; unsigned short bfReserved2; unsigned long bfOffBits;
}BITMAPFILEHEADER; BITMAPFILEHEADER bmfh;
5
#define
似た機能で #define というのもある これは単なる文字列の置き換え(typedefは既存の型の別名を 定義するものなので違いに注意) 例) #define PI 3.1415 「PIという文字列がプログラム中に出てきたら、3.1415と いう文字列で置き換えなさい」 プログラムの文字列レベルで置換が起きる プログラムをうつのが楽になる 具体的な数字がプログラム中にあるよりも意味のある文字で置き 換えた方が分かりやすい 後から値を変更しやすい #define BYTEunsigned char とできるが、型の場合はtypedefを使っ
た方がよい セミコロンはいらない! 6
#defineを使った例
#include <stdio.h> #define NUM 5 /*人数*/typedef unsigned char BYTE;
/*構造体のプロトタイプ宣言*/
typedef struct seiseki {
int code; /*学籍番号*/ BYTE eng; /*英語の点数*/ BYTE math; /*数学の点数*/ char name[50]; /*名前*/ }SEISEKI; /*メイン関数*/ int main(void) { int i; SEISEKI classA[NUM]; /*計算には特に意味はない*/
for(i=0; i<NUM; i++){ classA[i].code=i+1; classA[i].eng = i*10; }
/*表示(構造体配列のポインタに注意!)*/
for(i=0; i<NUM; i++){
printf("code %d score(eng) %d ¥n", (classA+i)->code, (classA+i)->eng); } return 0; } p10-1.c 7
回転の問題 (補足)
規模の小さな具体的な問題で具体的に考えると分かりやすい 1 2 3 4 5 6 7 8 1 23 4 56 7 8 width height width2=height height2=width buf2[(height2-x-1)*width2+y] = buf[y*width+x]; y*width+x=2 (x=2, y=0) (height2-x-1)*width2+y=2 (x=2, y=0) x y x y x*width2+y (height2-x-1)*width2+y y*width+x xとyの入れ替え 上下の入れ替え 8カラーからグレースケール
前回は画素を移動しただけ(回転など) 画像処理の本質は、画素値を演算によって得ること ⇒フィルタリング(ぼかし、鮮鋭化など:ディジタル信号処理) 色の変換 カラー画像を白黒濃淡画像に変換するにはどうすれば?9
カラーからグレースケール 続き
ITU standard: Y = (222*Red+707*Green+71*Blue)/1000 NTSC standard: Y = 0.2989*R + 0.5870*G + 0.1140*B RとGとBの値を平均するのが一番簡単 Y=(R+G+B)/3 グレースケールのビットマップは、R=G=B=Yとすることで作る (他のやり方もあるがこれが一番簡単) 10グレースケールへの変換プログラム
#include <stdio.h> #include <stdlib.h> #include "pgm.h" int main(void) { 省略 /*出力用*/Rbuffer2 = (BYTE*)malloc( width*height ); /*メモリ確保*/
Gbuffer2 = (BYTE*)malloc( width*height ); Bbuffer2 = (BYTE*)malloc( width*height );
/*****************画像処理をここで行う********************/
for(y=0; y<height; y++){ for(x=0; x<width; x++){
Rbuffer2[y*width+x] = (BYTE) ((Rbuffer[y*width+x]+Gbuffer[y*width+x]+Bbuffer[y*width+x])/3.0); Gbuffer2[y*width+x] = Rbuffer2[y*width+x]; Bbuffer2[y*width+x] = Rbuffer2[y*width+x]; } } /*******************ここまで******************************/ /*書き込み処理*/
Plane2RGB(buffer, Rbuffer2, Gbuffer2, Bbuffer2, width, height); /*bufferは入力のものを転用する*/
SaveBitmap("result.bmp", buffer, width, height);
省略 } p10-2.c
輝度の変換
全体的に暗い画像を明るくしたい! 各画素を定数倍する(定数の大きさによって明るさが変化) カラーでも、グレースケールでも基本は同じ ×定数 ×定数 ×定数 画像の位置によって定数の大きさを変化させれば、特定のものだけを明るくすることもできる但し・・・
単純に定数倍すると失敗する可能性が高い プログラム上の問題 ⇒解決するにはどうすればいいでしょうか? double tmp_pix; double const_val=2; /*輝度を2倍にする*/ /*****************画像処理をここで行う********************/for(y=0; y<height; y++){
for(x=0; x<width; x++){
tmp_pix = const_val*Rbuffer[y*width+x]; Rbuffer2[y*width+x] = (BYTE) tmp_pix; tmp_pix = const_val*Gbuffer[y*width+x]; Gbuffer2[y*width+x] = (BYTE) tmp_pix; tmp_pix = const_val*Bbuffer[y*width+x]; Bbuffer2[y*width+x] = (BYTE) tmp_pix; }
13
ミニテスト(問1)
輝度を定数倍するプログラムの問題を解決しましょう まず、何が問題かを突き止めましょう プログラムを修正しましょう ヒント:画素を表現するのに使っている型がポイント(BYTEは unsigned charを定義しなおしたものです) double tmp_pix;
double const_val=2; /*輝度を2倍にする*/ /*****************画像処理をここで行う********************/
for(y=0; y<height; y++){
for(x=0; x<width; x++){
tmp_pix = const_val*Rbuffer[y*width+x]; Rbuffer2[y*width+x] = (BYTE) tmp_pix; tmp_pix = const_val*Gbuffer[y*width+x]; Gbuffer2[y*width+x] = (BYTE) tmp_pix; tmp_pix = const_val*Bbuffer[y*width+x]; Bbuffer2[y*width+x] = (BYTE) tmp_pix; } } 14
バナナを取り出せ!
画像中のバナナだけを取り出すにはどうすればよいか? if(画素値 == バナナの色) { 画素値*1 }else{ 画素値*0 } 注1)閾値はそれぞれの画像で異なっている 注2)別にバナナが死ぬほど好きなわけではない p10-4.c (ネットで集めた果物の画像付) 15画像から意味のある情報を取り出す
2値化 ⇒ 濃淡画像を白黒に変換する 意味のある情報を取り出す(情報を圧縮する) デザイン効果 16画像の2値化
width height グレースケール画像 新しい画像用のバッファ (入れ物) width height Color2Gray 指定したファイル名 で保存 出力 画素値 閾値Threshold 255 0 image1[0][x] ハードリミット関数 Rbuffer[0] Rbuffer[1] Rbuffer[2] Rbuffer[width-1] Rbuffer217
ミニテスト(問2)
2値化するプログラムをつくりましょう ヒント グレースケールの画素値が閾値(Threshold)より大きけ れば255,小さければ0をRbuffer2[i]に入れればよい Gbuffer2[i], Bbuffer2[i]も同じものを入れる 「もし~なら・・・、そうでなければ・・・」はどのようにします か?思い出しましょう 182値化プログラムの例1
int main(void) { 省略 double tmp_pix; double threshold=100; /*閾値*/ /*出力用*/Rbuffer2 = (BYTE*)malloc( width*height ); /*メモリ確保*/
Gbuffer2 = (BYTE*)malloc( width*height ); Bbuffer2 = (BYTE*)malloc( width*height );
/*****************画像処理をここで行う********************/
for(y=0; y<height; y++){ for(x=0; x<width; x++){ tmp_pix = ((Rbuffer[y*width+x]+Gbuffer[y*width+x]+Bbuffer[y*width+x])/3.0); if(tmp_pix > threshold) { Rbuffer2[y*width+x] = 255; Gbuffer2[y*width+x] = 255; Bbuffer2[y*width+x] = 255; }else{ Rbuffer2[y*width+x] = 0; Gbuffer2[y*width+x] = 0; Bbuffer2[y*width+x] = 0; } } } /*******************ここまで******************************/ /*書き込み処理*/
Plane2RGB(buffer, Rbuffer2, Gbuffer2, Bbuffer2, width, height); /*bufferは入力のものを転用する*/
SaveBitmap("result.bmp", buffer, width, height); 省略 } p10-5.c 19
2値化プログラムの例2
int main(void) { 省略 double tmp_pix; double threshold=100; /*閾値*/ /*出力用*/Rbuffer2 = (BYTE*)malloc( width*height ); /*メモリ確保*/
Gbuffer2 = (BYTE*)malloc( width*height ); Bbuffer2 = (BYTE*)malloc( width*height );
/*****************画像処理をここで行う********************/
for(y=0; y<height; y++){ for(x=0; x<width; x++){
tmp_pix = ((Rbuffer[y*width+x]+Gbuffer[y*width+x]+Bbuffer[y*width+x])/3.0);
Rbuffer2[y*width+x] =(tmp_pix > threshold) ? 255:0; Gbuffer2[y*width+x] =(tmp_pix > threshold) ? 255:0; Bbuffer2[y*width+x] =(tmp_pix > threshold) ? 255:0;
} }
/*******************ここまで******************************/ /*書き込み処理*/
Plane2RGB(buffer, Rbuffer2, Gbuffer2, Bbuffer2, width, height);/*bufferは入力のものを転用する*/
SaveBitmap("result.bmp", buffer, width, height); 省略 p10-6.c 20
条件演算子
条件式 ? 式1:式2 条件式が真 ⇒ 式1 条件式が偽 ⇒ 式2Rbuffer2[i] = (Rbuffer[i] > threshold) ? 255 : 0 ;
(Rbuffer[i] > threshold) が 真の時は255を出力 条件式
(Rbuffer[i] > threshold) が 偽の時は0を出力 ifを使って書き直すことができる
21
画像認識
画像認識とは 画像のパターンを判別すること 予め用意したパターンの辞書と比較 入力と最も近いパターンを判別結果とする 「近さの基準をどうするか?」が大変重要 システム 物体辞書 物体画像 入力 認識物体 出力 22例えば文字認識
画像入力 (スキャナorカメラ) 文字パターンの辞書 認識したい文字 全ての形に関する情報 どの文字パターン に最も近いかを判定 レイアウト解析 全ての文字を判別したか? 2値化 一つの文字を取り出す yes no 電 気 通 信 大 学 23パターンを認識する
レイアウト解析後に切り出した文字パターンが何か?を判別するには どうすればよいか 全ての文字に対して基準となるパターンを用意 基準パターンと入力パターンの近さを計る 基準パターンとはどのようなものか? 基準パターンと入力パターンの近さはどのように定義するか? 差の絶対値の和(SAD) 正規化相互相関(NCC)∑
∑
∑
− − − − = n i i n i i i n i i b b a a b b a a b a NCC 2 2 ) ( ) ( ) )( ( ) , ( | | ) , ( i n i i b a b a SAD =∑
− 平均 24テンプレートマッチング
テンプレート(パターン)マッチング 基準パターンは画像そのもの(テンプレート) 基準パターン(テンプレート)に最も近い物を探す 一般にテンプレート画像と入力画像は同じ大きさではない マッチする場所を探す必要がある 大きさの違いや傾きなども考慮する必要がある25
数字を認識してみよう
64×64ピクセルの画像に数字を書いて入力する どれと最も近いのかをどのように計算するかが問題 それぞれと比較する 入力 テンプレート(辞書) 26数字を認識してみよう(続き)
2値化した入力画像とテンプレートの画素の一致度(いく つ一致するか)を数える 2値化 テンプレートと比較 黒画素の数が一致した数 黒が多い方が近い?本当にこれでできるのか?
0 で一致した画素数 2976画素 1 で一致した画素数 2932画素 2 で一致した画素数 2873画素 3 で一致した画素数 2925画素 4 で一致した画素数 3014画素 5 で一致した画素数 2800画素 6 で一致した画素数 2979画素 7 で一致した画素数 2921画素 8 で一致した画素数 2911画素 9 で一致した画素数 2961画素処理の流れ
プログラムの流れをつかもう 興味のある人は是非作ってみましょう 入力画像の読み込み 入力画像の2値化 Rbuffer2へ i番目の数字テンプレート読み込み i番目の数字テンプレートとRbuffer2の比較 一致度をscore[i]へ保存 i<10 ? score[i]の最大値 とそのときのiを求める (max_indexに保存) 結果の表示 i++ No Yes29
数字を認識してみよう(続き)
もっとうまい方法はないか? まずは単純なマッチングの欠点を考えてみる 形のちょっとした変化に弱い 位置ずれに弱い もっと大雑把な数字の特徴をつかんだ方が良い うまく判別できる特徴を考えてみよう 文字認識ではエッジの方向性がよく使われる 特徴量 特徴量 30最終課題
透明人間になってみたい! ⇒せめて画像処理で楽しみましょう 2枚の画像を使う 自分で用意しても良い(三脚などでカメラを固定する) 自分で作ったプログラムで処理すると透明人間(もどき)に! 31動画にすればこんな感じ・・・
静止画をつなげたものが動画(ぱらぱら漫画と同じ仕組み) 32ポイント
画像はback.bmpとfront.bmpの2枚をこちらで用意(自分で用意してもいい です) 画素値はunsigned char型(BYTEで定義しなおしている)なのでオーバーフ ローを気にする必要がある 演算はdouble型で行って(必要があれば)結果をキャストする p10-7.cにヒントプログラムがある(やり方はひとつではないので、独自に作っ てももちろんかまいません) 好きな透明度の画像を最低1枚作ればよい(興味があれば、ビットマップをつ なげてAVIファイルにするフリーソフトがあるので試してみてください)http://yamatabi.que.ne.jp/soft/avimk/ (AVI Maker)
ソースコードと画像(できればJPEGに圧縮)をメールで送る