プロが教えるわが家の防犯対策術!

OpenCVを使って画像を4値化したいのですが、アルゴリズムはわかっているのですがどうプログラムを組めばいいのかわかりません。

アルゴリズムはまず濃度ヒストグラムを作って
(1)適当な閾値を3つ(t1,t2,t3),決める。
(2)4つの領域で平均濃度を計算し、それらをa1,a2,a3,a4とする。(4つの領域ってどこ?
(3)新閾値t1,t2,t3とt1=(a1+a2)/2,t2=(a2+a3)/2,t3=(a3+a4)/2として計算する。
(4)Σ(j=1,3) |tj-tj|<εならば停止。そうでなければ(2)に戻る。

なのですが、教えて下さい。

A 回答 (4件)

 こんにちは。



 数学としてのN値化は分からないのですが、画像処理としてのN値化でお話しすると、

 (1)RGB[0-255, 0-255, 0-255]の画素をY[0-255]のグレースケールに変換
 (2)Y[0-255]に対して、[N-1]個の閾値を境界線として0~[N-1]を出力する

 が多いように思います。

 (2)は、例えば2値化の場合、1個の閾値を境界線にして、画像フォーマットが1ビットであれば、0~1を出力します。24ビットカラーなどであれば、RGB(0,0,0)又はRGB(255,255,255)を出力します。
 此れを4値化で考えると、3個の閾値で0~3を出力しますが、フォーマットが1ビットでは入りきらない為、4ビット以上の画像フォーマットにセーブすると言う事です(大抵24ビットを使用する)。

 0→RGB(0,0,0)
 1→RGB(85,85,85)
 2→RGB(170,170,170)
 3→RGB(255,255,255)

 アラも在るのですが、以下参考になれば。

//配列の個数を数える
#define ArrayCount(a) (sizeof(a)/sizeof(a[0]))

//グレースケール変換[0-255]を出力する
int RGBToY(int R, int G, int B)
{
return ((54 * R) + (183 * G) + (18 * B)) >> 8;
}

//閾値と比較して[0~count]を決定する
int GetLevel(int color, const int array[], int count)
{
for(int i = 0; i < count; ++i)
if(color < array[i])
break;

return i;
}

//テスト
void test()
{
//IPLに読み込む(24ビットカラー)
IplImage* img = ::cvLoadImage("testimage.bmp", CV_LOAD_IMAGE_ANYDEPTH | CV_LOAD_IMAGE_ANYCOLOR);

//閾値の配列
const int thresholds[] = {64, 128, 192};

//閾値の数
const int count = ArrayCount(thresholds);

//count x band = 255にする為
const int band = 255 / count;

for(int y = 0; y < img->height; y++)
{
for(int x = 0; x < img->width; x++)
{
const int pos = (y * img->widthStep) + (x * img->nChannels);
const int R = (unsigned char)img->imageData[pos];
const int G = (unsigned char)img->imageData[pos + 1];
const int B = (unsigned char)img->imageData[pos + 2];
const int Y = ::RGBToY(R, G, B);

const int result = ::GetLevel(Y, thresholds, count) * band;

for(int i = 0; i < img->nChannels; ++i)
img->imageData[pos + i] = result;
}
}

//4値化した結果を24ビットカラーでセーブする
::cvSaveImage("yontika.bmp", img);

//IPLの解放
::cvReleaseImage(&img);
}

この回答への補足

プログラムしてみたところできました!
1つ質問ですが、この部分がどういう処理なのか教えていただけますでしょうか。

for(int y = 0; y < img->height; y++)
{
for(int x = 0; x < img->width; x++)
{
const int pos = (y * img->widthStep) + (x * img->nChannels);
const int R = (unsigned char)img->imageData[pos];
const int G = (unsigned char)img->imageData[pos + 1];
const int B = (unsigned char)img->imageData[pos + 2];
const int Y = ::RGBToY(R, G, B);

const int result = ::GetLevel(Y, thresholds, count) * band;

for(int i = 0; i < img->nChannels; ++i)
img->imageData[pos + i] = result;
}
}

補足日時:2009/04/20 12:43
    • good
    • 0

 こんにちは。

補足頂きました。

 グレースケールの公式のつもりだったのですが、一般的ではないのかもしれません。

 以下を使用しています(LibPNGと言う、PNG画像を処理する有名なライブラリのヘルプドキュメント内に紹介されているグレースケールの公式です)。
 http://dencha.ojaru.jp/programs_07/pg_graphic_10 …
 出力画素 = 0.212671 * R成分 + 0.715160 * G成分 + 0.072169 * B成分

 グレースケールの公式や手段に関しては、其の他にも幾つか有る様です。
 http://ofo.jp/osakana/cgtips/grayscale.phtml

 話を戻すと、以下の様な関数に成ります。

 int RGBToY(int R, int G, int B)
 {
  return (0.212671 * R) + (0.715160 * G) + (0.072169 * B);
 }

 小数を含む計算は速度が落ちる為、各係数を256倍して整数だけの計算にしています。

 int RGBToY(int R, int G, int B)
 {
  return ((54 * R) + (183 * G) + (18 * B)) / 256;
 }

 >> 8は8ビット右にシフトする演算です。/ 256と同じ結果が出せます。
 / 256とするよりも>> 8とした方がCPUは早く計算出来るので、

 int RGBToY(int R, int G, int B)
 {
  return ((54 * R) + (183 * G) + (18 * B)) >> 8;
 }

 としています。

 グレースケール変換については、以下が非常に参考になります。
 http://cell.fixstars.com/ps3linux/index.php/2.9% …
    • good
    • 0
この回答へのお礼

いろいろ教えていただきありがとうございました!
大変参考になりました!

お礼日時:2009/04/22 00:25

 こんにちは。

補足頂きました。
 この手の事は中々簡単に説明出来るモノではないのですが、手っ取り早く解説すると、

//(1)
for(int y = 0; y < img->height; y++)
{
//(2)
for(int x = 0; x < img->width; x++)
{
//(3)
const int pos = (y * img->widthStep) + (x * img->nChannels);
//(4)
const int R = (unsigned char)img->imageData[pos];
//(5)
const int G = (unsigned char)img->imageData[pos + 1];
//(6)
const int B = (unsigned char)img->imageData[pos + 2];
//(7)
const int Y = ::RGBToY(R, G, B);
//(8)
const int result = ::GetLevel(Y, thresholds, count) * band;
//(9)
for(int i = 0; i < img->nChannels; ++i)
img->imageData[pos + i] = result;
}
}

imgはファイルから読み込んできたカラービットマップを示しています(読み込んでくるファイルは24bitを前提としています)。
(1)imgの縦幅の分だけ回ります
(2)imgの横幅の分だけ回ります
(3)imgから処理対象の画素の位置を計算しています。img->widthStepは次の行に改行する為に必要な正確なバイト数を示しています。
 img->nChannelsは、画素一つ辺りに幾つの色素があるかを示しています。
 24bit に限定しているので img->nChannelsは 3 で RGB(赤、緑、青)です(其れ以外は表示が可笑しくなる)。
 この場合、次の画素へ移動するには3バイトずつ移動する事になります。

(4)赤の色素を取り出しています[0-255]。pos = 0の時 赤の位置は pos
(5)緑の色素を取り出しています[0-255]。pos = 0の時 緑の位置は pos + 1
(6)青の色素を取り出しています[0-255]。pos = 0の時 青の位置は pos + 2 次の画素に移動してpos = 3になる
(7)赤、緑、青の色素を使ってグレースケールに変換します[0-255]。
(8)グレースケールされた数字を[0-255]を閾値と比較して[0-3]へ4値化します。
 しかし[0-3]ではディスプレイ出来ませんので、band を掛け合わせることで resultが

 0→0
 1→85
 2→170
 3→255

 に成る様に調整します。ですので、band = 85です。
(9)赤、緑、青のデータに書き込みます(例えばresult = 170であった場合、赤170 緑170 青170 にしている)。

 上記の手順を画像が持つ全ての画素に行う事で、4値化されたグレースケールになります。
 此れでよろしいでしょうか。

この回答への補足

ありがとうございます!
大変わかりやすいです。

何度も申し訳ないのですが、もう一つ質問させてください。

//グレースケール変換[0-255]を出力する
int RGBToY(int R, int G, int B)
{
return ((54 * R) + (183 * G) + (18 * B)) >> 8;
}

の関数で{54,183,18}というのは適当な値と解釈してよろしいでしょうか。
また「>>8」というのはどういう意味なのでしょうか。
よろしくお願いします。

補足日時:2009/04/21 00:18
    • good
    • 0

>4つの領域ってどこ?


min~t1、t1~t2、t2~t3、t3~maxの4領域。

例えば、最初に0~63、64~127、128~191、192~255の4つの領域に4分し、それぞれの範囲での平均濃度を求める。

それぞれの平均が1、82、130、254だった場合、t1=(1+82)÷2=41、t2=(82+130)÷2=106、t3=(130+254)÷2=192となる。

次に、0~40、41~105、106~191、192~255の4つの領域に4分し、それぞれの範囲での平均濃度を求める。

以下、終了条件に達する(何回繰り返してもt1、t2、t3が3つとも、ある程度以上は変化しなくなる)まで繰り返し。上記の例では、t3が2回目で変化しなくなっている。

但し「ある周期で、いくつかの値を繰り返す」と言う「発振」を起こす可能性があるので、終了判定に注意する事。例えば、t1が「40⇒43⇒40⇒43⇒…」と延々と繰り返す可能性がある。

この回答への補足

平均濃度はヒストグラムを4つの領域にわけるのでしょうか?
また終了条件に達したらその後はどうすればいいのでしょうか?

補足日時:2009/04/17 13:54
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!