2014年1月26日日曜日

様々な色領域のラベリング

人間の顔や手などを認識するのに、前処理として肌色を検出する。

RGBでなくHSVやYCbCr空間を使用すると安定して肌色を検出できることがよく知られている。

HSV


H:色相(Hue)
S:彩度(Saturation)
V:明度(Value)

H色相は、赤っぽい、青っぽいといった「色あい」を表す。
色相を環状に並べたものを色相環といい、赤は 0°、黄色は 60°といったように円上の角度で色あいを示す。

S彩度は「鮮やかさ」を表す。
色相が同じでも、彩度が高ければ鮮やかに見え、低ければグレーに近くなる。
彩度がゼロの場合は無彩色(黒、グレー、白)になります。

V明度は「明るさ」を表す。
明度が高ければ明るい色に、低ければ暗い色になる。

・S と V を固定し H を遷移させて虹色のグラデーションを生み出す
・S を 1.0、 V を 1.0 に固定し H を乱数で決めることで、グレーや黒といった無彩色を避けながらランダムな色を作り出す(逆に無彩色だけを選ぶことも。) 
※参考 http://www.demoscene.jp/?p=1460

以下、変換式。

H =
60 × (G - B) / (Max - Min), if Max = R
60 × (B - R) / (Max - Min), if Max = G
60 × (B - G) / (Max - Min), if Max = B

S = (Max - Min) / Max 

V=Max


YCbCr

Y:輝度
Cb、Cr:色差


Y = 0.299 × R + 0.587 × G + 0.114 × B
Cb = -0.172 × R - 0.339 × G + 0.511 × B
Cr = 0.511 × R - 0.428 × G + 0.083 × B

※係数は視覚度特性による。


肌色


(H < 50 || H > 210) かつ (-50 < Cb && Cb < -20) かつ (20 < Cr && Cr < 40)



白い紙に印刷された黒い文字をラベリングするにはどうしたらよいだろうか。
SとVを低く抑えたフィルターをかけてみるか。


画像のラベリング

画像のラベリングのサンプル。

画像のラベリングのスタンダードな手法は 2 つある。

1. 再帰処理


2. 接続表


1. は注目画素から探索範囲を膨張して、背景画素や探索範囲の境界に到達したら収束させる処理を、全ての画素に対して行う。
背景画素が少ない場合の探索では、画素×画素程度の計算量が必要になりそう。


2. は探索は全画素数の2~3倍程度で良さそう。1回目は、注目画素が抽出した物体画素だった場合に、4または8近傍を探索して、ラベル付けされていなければ ラベル番号n +1を振り、ラベル付けされていれば ラベル番号nを振る処理をラスター処理する。
2回目は、各画素の4または8近傍に対し、自分自身のラベルnよりも小さいラベルmが降られているか、振られていれば、nをすべて、小さいラベルmに置き換える。この処理をラスター処理する。
最後は、ラベルを小さい方から順に詰める処理をラベルの中で行う。


以下、コード。

/* ラベル用大域変数 */
const int MAX_IMAGESIZE = 4000;
int label[MAX_IMAGESIZE][MAX_IMAGESIZE];
int labeling( int n, int red, int green, int blue );
/* ラベリングを行う関数 */
int search_4neighbors( int x, int y, int n );
/* 4近傍のラベルの最大値を返す関数 */
void modify_label( int num1, int num2, int n );
/* ラベルを変更する関数 */
void modify_label( int num1, int num2, int n )
/* label[][]中の全ての num1 を num2 に変更 */
{
int x,y; /* 制御変数 */
for(y=0;y<height[n];y++){
for(x=0;x<width[n];x++){
if ( label[x][y]==num1){
label[x][y]=num2;
}
}
}
}
int search_4neighbors( int x, int y, int n )
/* label[x][y]の4近傍のラベルの最大値を返す関数 */
/* n は対象画像の番号(内外部判定のために使う) */
{
int max=0; /* 最大値 */
if (y-1>=0 && label[x][y-1]>max ){
max=label[x][y-1]; /* 上 */
}
if (x-1>=0 && label[x-1][y]>max ){
max=label[x-1][y]; /* 左 */
}
if (y+1<height[n] && label[x][y+1]>max ){
max=label[x][y+1]; /* 下 */
}
if (x+1<width[n] && label[x+1][y]>max ){
max=label[x+1][y]; /* 右 */
}
return max;
}
int labeling( int n, int red, int green, int blue )
/* 画像No.n 中の背景色(red,green,blue)以外の色の図形にラベリングして */
/* 結果を大域変数label[][]に代入する.戻り値は最終的なラベルの最大値.*/
{
int x,y,num; /* 制御変数,作業変数 */
int count=0; /* ラベル最大値 */
int new_count; /* 作業変数 */
/* ラベルを初期化 */
for(y=0;y<height[n];y++){
for(x=0;x<width[n];x++){
label[x][y]=0; /* 0:未処理を表す */
}
}
/* 画像No.nをスキャン */
for(y=0;y<height[n];y++){
for(x=0;x<width[n];x++){
if ( (image[n][x][y][0]!=red || image[n][x][y][1]!=green ||
image[n][x][y][2]!=blue) && label[x][y] == 0 ){
num = search_4neighbors(x,y,n);
if (num==0){ /* 新しい孤立領域 */
label[x][y]=++count; /* countに+1してから代入 */
} else {
label[x][y]=num;
}
}
}
}
/* label[][]を整形する */
if ( count > 0 ){
/* 重複の削除 */
for(y=0;y<height[n];y++){
for(x=0;x<width[n];x++){
if (label[x][y]!=0){
num = search_4neighbors(x,y,n);
if ( num > label[x][y]){
/* ラベルnum を全て変更 */
modify_label(num,label[x][y],n);
}
}
}
}
/* 空いた番号を詰める */
new_count=0;
for(y=0;y<height[n];y++){
for(x=0;x<width[n];x++){
if ( label[x][y] > new_count ){
new_count++;
modify_label(label[x][y],new_count,n);
}
}
}
return new_count; /* =最終的なラベルの最大値 */
} else return 0; /* =孤立領域なし */
}
view raw labeling.cpp hosted with ❤ by GitHub

2014年1月12日日曜日

PPM(P6)画像のロード

C++でPPM画像のロードを書いた。


#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
/* 定数宣言 */
#define MAX_IMAGESIZE 4000 /* 想定する縦・横の最大画素数 */
#define MAX_BRIGHTNESS 255 /* 想定する最大階調値 */
#define GRAYLEVEL 256 /* 想定する階調数(=最大階調値+1) */
#define MAX_FILENAME 256 /* 想定するファイル名の最大長 */
#define MAX_BUFFERSIZE 256 /* 利用するバッファ最大長 */
#define MAX_NUM_OF_IMAGES 3 /* 利用する画像の枚数 */
/* 大域変数の宣言 */
/* 画像データ image[n][x][y][col] (col=0:R, =1:G, =2:B) */
unsigned char image[MAX_NUM_OF_IMAGES]
[MAX_IMAGESIZE][MAX_IMAGESIZE][3];
/* 横幅と縦幅 */
int width[MAX_NUM_OF_IMAGES],height[MAX_NUM_OF_IMAGES];
void load_color_image( int n, char name[] ); /* 画像の入力 */
void save_color_image( int n, char name[] ); /* 画像の出力 */
void load_color_image( int n, char name[] )
// カラー画像No.n
{
char fname[MAX_FILENAME]; /* ファイル名用の文字配列 */
char buffer[MAX_BUFFERSIZE]; /* データ読込み用変数 */
FILE *fp; /* ファイルポインタ */
int max_gray=0,x,y,col; /* 最大階調値と制御変数 */
// ファイルのオープン
std::ifstream file;
file.open(name, file.in | file.binary);
if(!file.is_open()){
std::cout << "ファイルが開けません" << std::endl;
exit(1);
}
// 読み込み
std::string line;
getline(file, line);
// P6モードのみ対応
if ( line != "P6" ){
printf("ファイルタイプがP6ではありません\n");
exit(1);
}
// 全行読み込み
width[n] = 0;
height[n] = 0;
while ( width[n] == 0 || height[n] == 0 ){
std::string line;
std::getline(file, line);
if ( line.at(0) != '#' ) {
sscanf_s( line.c_str(), "%d %d", &width[n], &height[n] );
}
}
/* max_gray の代入(#からのコメントは読み飛ばす) */
while ( max_gray == 0 ){
std::string line;
std::getline(file, line);
if ( buffer[0] != '#' ){
sscanf_s( line.c_str(), "%d", &max_gray );
}
}
/* パラメータの画面への表示 */
printf("横:%d, 縦:%d\n", width[n], height[n] );
printf("最大階調値 = %d\n",max_gray);
if ( width[n] > MAX_IMAGESIZE || height[n] >
MAX_IMAGESIZE ||
max_gray != MAX_BRIGHTNESS ){
printf("サイズか最大階調値が不適切です\n");
exit(1); /* 強制終了 */
}
// 画像データロード
int whilecnt = 0;
for(y=0;y<height[n];++y){
for(x=0;x<width[n];++x){
for(col=0;col<3;++col){
file.read(reinterpret_cast<char*>(&image[n][x][y][col]), 1);
}
}
}
std::cout << "while cnt: " << whilecnt << std::endl;
std::cout << "カラー画像を画像 No." << n << " に読み込みました" << std::endl;
file.close();
}
void save_color_image( int n, char name[] )
// n:画像番号
// 画像の横幅,縦幅はそれぞれ width[n], height[n] に予め入れておく
{
char fname[MAX_FILENAME]; /* ファイル名用の文字配列 */
FILE *fp; /* ファイルポインタ */
int x,y,col; /* ループ変数 */
std::ofstream file;
file.open(name, file.out | file.binary | file.trunc);
if(!file.is_open()){
std::cout << "ファイルが開けませんでした!" << std::endl;
exit(1);
}
// タイプ
file << "P6" << std::endl;
// コメント
file << "# pmm image created by masaz" << std::endl;
// 解像度
file << width[n] << " " << height[n] << std::endl;
// 階調
file << MAX_BRIGHTNESS << std::endl;
// データ出力
for(y=0;y<height[n];y++){
for(x=0;x<width[n];x++){
for(col=0;col<3;col++){
file.write(reinterpret_cast<char*>(&image[n][x][y][col]), 1);
}
}
}
file.close();
std::cout << "画像出力完了" << std::endl;
}
view raw image_load.cpp hosted with ❤ by GitHub