応用プログラミング 第9回
~プログラミングの応用
画像処理その2
~
電気通信大学電子工学専攻
Intelligent Electronic Systems Group
長井 隆行
本日の内容
1.
まずは課題
2.
画像処理の色々
3.
色々とプログラムを手直ししよう
4.
上下左右の入れ替え・回転
5.
縮小
6.
拡大
7.
拡大するのは難しい
課題
bitmap.bmp 赤(R,G,B)=(255,0,0) 橙(R,G,B)=(255,127,0) サイズ:16×16 各画素:24bit (R,G,B) (但し、bitmapはBGRの順で並んでいる) 茶(R,G,B)=(127,127,0)各自で試してみる
提出しなくてよい
bitmap.bmpをコンソールに表示してみましょう!
ビットマップのフォーマットに注意
各画素がある条件を満たせば特定の文字を表示する(例
えば、(255,0,0)なら"赤"など)
ヒントプログラムp9-0forInt.c or p9-0forMot.cをDL
可能
表示する(printfを使う)だけなので、プログラム中で画像
を保存する必要はない
課題 続き
注意点
ビットマップのフォーマッ
トに注意
左下からデータが並んで
いる
RGBではなく、BGRの順
番
全角か半角かを気にする
(特にスペース)
文字はアスキーでもよい
うまくいけばこんな感じで表示できます画像処理とは何か?
画像をみやすくする
明るさ・コントラスト・色調などの補正
回転・サイズの変更
ノイズを除去する
画像を圧縮する
JPEG、MPEG
画像を合成する
CG
画像を認識する
文字認識(OCR)
物体認識
ロボットビジョン(立体視など)
さっそく画像処理
と、その前に大事な事・・・
1.
画像の入出力はいつも同じなので
関数
にしておこう!
2.
その際に
BGRをRGBに入れ替えよう!
3.
さらにピクセルが左下からのものを左上からに並べ替えよう!
4.
カラー画像を処理するためには、
RGBそれぞれについて同じ
処理をする必要がある
⇒R、G、Bそれぞれを別の箱にしまった方が便利!
関数化
(ビットマップの読み込み)
unsigned char* LoadBitmap(char* filename, int* width, int* height) {
int i, size; FILE *fp;
unsigned char *buffer; /*入力画像用メモリのポインタ*/ unsigned char tmp_pix;
BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih;
fp = fopen( filename, "rb" ); /*ファイルを開く*/
fread ( &bmfh , sizeof(BITMAPFILEHEADER) , 1 , fp ); /*ビットマップのヘッダーを読み込む*/ fread ( &bmih , sizeof(BITMAPINFOHEADER) , 1 , fp );
*width = bmih.biWidth; *height = bmih.biHeight;
size = (*width) *( *height); /*ビットマップのサイズを算出*/ buffer = (unsigned char*)malloc( 3*size ); /*必要なサイズのメモリを確保*/
for ( i = (*height) - 1; i >= 0; i-- ) /*上下を入れ替えながら読み込む*/ {
fread( buffer + (*width) * 3 * i, 1, (*width) * 3, fp ); }
fclose( fp ); /*読み終わったのでファイルを閉じる*/ for ( i = 0; i < size; i++ ) /*BGRをRGBへ変換(入れ替え)*/ { tmp_pix = buffer[ i * 3 ]; buffer[ i * 3 ] = buffer[ i * 3 + 2 ]; buffer[ i * 3 + 2 ] = tmp_pix; } return buffer; } p9-1.c
関数化
(ビットマップの書き込み)
次のページに続く
void SaveBitmap(char* filename, unsigned char* buffer, int width, int height) { int i; FILE *fp; BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; char *tmp;
unsigned char tmp_pix; /*ファイルを書き込み用で開く*/ fp = fopen( filename , "wb" ); /*出力画像のヘッダを生成する*/ memset(&bmfh,0, sizeof(bmfh)); memset(&bmih,0, sizeof(bmih)); tmp = (char*) &(bmfh.bfType); tmp[0]='B'; tmp[1]='M'; bmfh.bfOffBits = BMP_HEADER_SIZE;
関数化
(ビットマップの書き込み)続き
bmih.biSize=BMP_HEADER_SIZE - 14; bmih.biWidth = width; bmih.biHeight = height; bmih.biPlanes = 1; bmih.biBitCount = 24; bmih.biCompression = 0; bmih.biSizeImage = width*height*3; /*RGBをBGRに変換する*/ for ( i = 0; i < width * height; i++ ) {tmp_pix = buffer[ i * 3 ]; buffer[ i * 3 ] = buffer[ i * 3 + 2 ]; buffer[ i * 3 + 2 ] = tmp_pix; }
fwrite( &bmfh , sizeof(BITMAPFILEHEADER) , 1 , fp ); /*ヘッダを書き込む*/ fwrite( &bmih , sizeof(BITMAPINFOHEADER) , 1 , fp );
for ( i = height - 1; i >= 0; i-- ) /*上下を入れ替えながら書き込む*/ {
fwrite( buffer + width * 3 * i, 1, width * 3, fp ); } fclose( fp ); /*ファイルを閉じる*/ return; } p9-1.c
関数化 (main関数)
/*ビットマップを開く関数のプロトタイプ宣言*/unsigned char* LoadBitmap(char* filename, int* width, int* height); /*ビットマップを保存する関数*/
void SaveBitmap(char* filename, unsigned char* buffer, int width, int height); /*メイン関数*/
int main(void) {
int width, height; int n , x , y;
unsigned char *buffer; /*入力画像用メモリのポインタ*/ /*ファイルを開く*/
buffer = LoadBitmap("girl.bmp", &width, &height);
/*****************画像処理をここで行う********************/ /*今はとりあえず何もしない*/
/*******************ここまで*********************************/ /*書き込み処理*/
/*ここでは入力画像をそのまま書き出す*/
SaveBitmap("result.bmp", buffer, width, height);
/*メモリを解放する*/ free( buffer ); return 0; } p9-1.c
RGBをそれぞれ別の箱にしまう!
int main(void) {int width, height; int n , x , y;
BYTE *buffer; /*入力画像用メモリのポインタ*/ BYTE *Rbuffer, *Gbuffer, *Bbuffer;
/*ファイルを開く*/
buffer = LoadBitmap("girl.bmp", &width, &height);
Rbuffer = (BYTE*)malloc( width*height ); /*メモリ確保*/ Gbuffer = (BYTE*)malloc( width*height );
Bbuffer = (BYTE*)malloc( width*height );
RGB2Plane(buffer, Rbuffer, Gbuffer, Bbuffer, width, height); /*RGBを色平面に分解*/
/*****************画像処理をここで行う********************/ /*今はとりあえず何もしない*/
/*******************ここまで******************************/ /*書き込み処理*/
Plane2RGB(buffer, Rbuffer, Gbuffer, Bbuffer, width, height); /*色平面をRGBに合成*/
SaveBitmap("result.bmp", buffer, width, height); /*メモリを解放する*/ free( buffer ); free( Rbuffer ); free( Gbuffer ); free( Bbuffer ); return 0; } p9-2.c
ミニテスト(問1)
空欄を埋めましょう
/*RGBをR,G,Bに分解*/void RGB2Plane(BYTE* buffer, BYTE* R, BYTE* G, BYTE* B, int width, int height) {
int i;
/*RGBを各色平面へ変換(R,G,Bはアドレス渡しになっていることに注意)*/ for (i=0; i<width*height; i++)
{
} }
/*R,G,Bに分解したものをRGBに合成*/
void Plane2RGB(BYTE* buffer, BYTE* R, BYTE* G, BYTE* B, int width, int height) {
int i;
/*RGBを各色平面へ変換(R,G,Bはアドレス渡しになっていることに注意)*/ for (i=0; i<width*height; i++)
{ buffer[3*i] = R[i]; buffer[3*i+1] = G[i]; buffer[3*i+2] = B[i]; } }
エラー処理をする
「ファイルを読み込む」や「メモリを確保する」などの処理は、仮に失敗すると後
に続く処理ができなくなるという問題がある。
もし「ファイルの読み込みに失敗したら」どうするか?
もし「メモリが足りなくて確保できなかったら」どうするか?
を考えておく必要がある。
fp = fopen( "bitmap.bmp" , "rb" );
if(fp==NULL)
{
printf("ファイルの読み込みに失敗!¥n");
return 0;
/*処理を継続できないので終了する*/
}
プリントで示すプログラムには、
スペースの関係
でエラー処理を入れていないの
で注意してください
ヘッダファイル
(.h)にしてしまう
main関数以外を「mybmp.h」というファイルに移動する
main関数のあるファイルの先頭に
#include “mybmp.h”
を追加
(注)mybmpi.hがインテル用、mybmpm.hがモトローラ用
#include <stdio.h> #include <stdlib.h> #include <memory.h> #include "mybmpi.h" /*自分で作ったヘッダーは""で囲む*/ /*メイン関数*/ int main(void) { 全く同じなので省略 } p9-3.c結局どうなったかというと?
#include “mybmp.h”とすることで、特に何もしなくても以
下のようにすることが可能
buffer = LoadBitmap(“xxx.bmp", &width, &height);Rbuffer = (BYTE*)malloc( width*height );/*メモリ確保*/
Gbuffer = (BYTE*)malloc( width*height );
Bbuffer = (BYTE*)malloc( width*height );
RGB2Plane(buffer, Rbuffer, Gbuffer, Bbuffer, width, height);
/*色平面をRGBに合成*/
Plane2RGB(buffer, Rbuffer, Gbuffer, Bbuffer, width, height);
SaveBitmap("result.bmp", buffer, width, height);
buffer[i] 左上からRGB順 Rbuffer Gbuffer Bbuffer /*本当はこの間で処理をする*/
さあ処理してみよう
画像の左右反転
どうすればよいでしょう?
入力画像 出力画像各画素にどのようにアクセスするか
色々ありえますが、ここでは2重ループを使って次のよう
に考えます
Rbuffer[0] width height横がx、縦がyのとき
Rbuffer[y*width+x];
Rbuffer[height*width-1]x
y
for(y=0; y<height; y++){ for(x=0; x<width; x++){ Rbuffer2[y*width+x] = Rbuffer[y*width+x]; } } 実際のメモリ ・ ・ ・
y
x
0 1 2 ここまででwidthが いくつあるか? y*width+x画像の左右反転
SaveBitmap(); Rbuffer[i] Rbuffer[0] Rbuffer[width-1] Rbuffer[1] width height ファイルから読み込み Rbuffer2[i] width height LoadBmp(); 指定したファイル名 で保存される Rbuffer2[width-1] Rbuffer2[0] Rbuffer2[width-2] Rbuffer[2] Rbuffer[x] Rbuffer2[width-1-x] Rbuffer2[width-3] 左右反転しながらコピー R,G,Bそれぞれ行う Rbuffer[2*width-1] Rbuffer[height*width-1] Rbuffer2[(height-1)*width]横がx、縦がyのとき
Rbuffer[y*width+x];
横がwidth-1-x、縦がy
Rbuffer2[??];
左右反転プログラム
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+1)*width-1-x] = Rbuffer[y*width+x]; Gbuffer2[(y+1)*width-1-x] = Gbuffer[y*width+x]; Bbuffer2[(y+1)*width-1-x] = Bbuffer[y*width+x]; } } /*******************ここまで******************************/ /*書き込み処理*/
Plane2RGB(buffer, Rbuffer2, Gbuffer2, Bbuffer2, width, height); /*bufferは入力のものを転用する*/ SaveBitmap("result.bmp", buffer, width, height);
/*以下省略*/ } p9-4.c 同じ行の最後まで進める:((y+1)*width-1) x個だけ前に戻る:-x
上下の入れ替えも同じ様に
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[(height-y-1)*width+x] = Rbuffer[y*width+x]; Gbuffer2[(height-y-1)*width+x] = Gbuffer[y*width+x]; Bbuffer2[(height-y-1)*width+x] = Bbuffer[y*width+x]; } } /*******************ここまで******************************/ /*書き込み処理*/
Plane2RGB(buffer, Rbuffer2, Gbuffer2, Bbuffer2, width, height); /*bufferは入力のものを転用する*/ SaveBitmap("result.bmp", buffer, width, height);
/*以下省略*/
ミニテスト(問2)
カメラをたてにして撮った次の画像を90度回転したい
Rbuffer[width-1] Rbuffer2[0]幅と高さの関係は?
Rbuffer[0] Rbuffer2[width2*(height2-1)] x方向 y 方 向 x 方 向 y方向 入れ替わるミニテスト(問2) 続き
int main(void) { /*省略*/int width2, height2;
/*****************画像処理をここで行う********************/
width2=height; height2=width;
for(y=0; y<height; y++){ for(x=0; x<width; x++){
} }
/*******************ここまで******************************/ /*書き込み処理*/
Plane2RGB(buffer, Rbuffer2, Gbuffer2, Bbuffer2, width2, height2);/*bufferは入力のものを転用する*/ SaveBitmap("result.bmp", buffer, width2, height2);
/*以下省略*/ }
画像を縮小する
画像を小さくするにはどうすればよいでしょう?
例えば1/2のサイズ(縦横それぞれ1/2)にするには?
本当はフィルタをかける必要がある(サンプリング定理)
2/3倍などはちょっと難しいのでここでは扱わない
画像を縮小する(続き)
一つおきに画素を取り出す(残りは捨てる)
本当はフィルタをかける必要がある(サンプリング定理)
2/3倍などはちょっと難しいのでここでは扱わない
画像を縮小するプログラム
int main(void) { int ratio; /*省略*/ ratio=2; /*倍率*/ height2=height/ratio; width2=width/ratio; /*出力用*/Rbuffer2 = (BYTE*)malloc( width2*height2 ); /*メモリ確保*/ Gbuffer2 = (BYTE*)malloc( width2*height2 );
Bbuffer2 = (BYTE*)malloc( width2*height2 );
/*****************画像処理をここで行う********************/ for(y=0; y<height2; y++){
for(x=0; x<width2; x++){ Rbuffer2[y*width2+x] = Rbuffer[y*width*ratio+x*ratio]; Gbuffer2[y*width2+x] = Gbuffer[y*width*ratio+x*ratio]; Bbuffer2[y*width2+x] = Bbuffer[y*width*ratio+x*ratio]; } } /*******************ここまで******************************/ /*省略*/ } p9-7.c
画像を拡大する
画像を拡大するにはどうすればよい?
縮小の逆をやればいいのでは?
画像を拡大する (続き)
画素を一つおきに埋めてゆく
あれれ、足りない
白いところはどうしよう?
白いところの埋め方は色々な方法がある
間を埋めることを
補間
という
補間の仕方
同じ画素値をコピーする(画素を膨らませる)
補間したきりんちゃん オリジナルのきりんちゃん補間の仕方 (線形補間)
周りの画素の情報を使う
画像は滑らかなので、周りの画素に近い値をとる可能性が高い
周囲4画素の平均 線形補間の結果 p 1-p q 1-q 画素値=(1-p){(1-q)a+ qb} +p{(1-q)c+qd} a b c d a,b,c,d:小さな画像の画素値補間のプログラム
int main(void) { int ratio; /*省略*/ ratio=2; height2=height/ratio; width2=width/ratio; /*出力用*/buffer2 = (BYTE*)malloc( width2*height2*3 );/*メモリ確保*/
Rbuffer2 = (BYTE*)malloc( width2*height2 ); Gbuffer2 = (BYTE*)malloc( width2*height2 ); Bbuffer2 = (BYTE*)malloc( width2*height2 );
/*****************画像処理をここで行う********************/ for(y=0; y<height2; y++){
for(x=0; x<width2; x++){
Rbuffer2[y*width2+x] = Rbuffer[((int) (y/ratio))*width + (int) (x/ratio)]; Gbuffer2[y*width2+x] = Gbuffer[((int) (y/ratio))*width + (int) (x/ratio)]; Bbuffer2[y*width2+x] = Bbuffer[((int) (y/ratio))*width + (int) (x/ratio)];
} } /*******************ここまで******************************/ /*省略*/ }