緑色のピクセルを数える

OpenCVでは色相の範囲が0~180であることに注意

一般的には色相は0~360の範囲で考えますが、OpenCVでは0~180で考えます。これは、8bitで扱える整数の最大値が255であることに起因するものです。緑色は約120度に当たる色相なので、この付近の色相を持つ画素を見つけ出します。今回は入力画像と同じ大きさの真っ黒な画像をもう1つ用意し、入力画像で緑色だった部分について、相当する位置の画素に255の値を入れていきます。これらの画像は2値画像になります。なお、色相が似ているかどうかを判別するためのIsSimilar関数を作っています。なお、0,360度が赤、60度が黄色、120度が緑、180度がシアン、240度が青、300度がマゼンタを表すおよその色相です。OpenCVでは0~180で色相を表すため、0,180が赤、30が黄色、60が緑、90がシアン、120が青、150がマゼンタを表すおよその色相になります。下の図はOpenCVでの色相と値の対応を示したものです。また、入力画像では緑色の部分の彩度が高いことから、彩度についても一定以上の値となるようなフィルターをかけ、精度の向上を試みています。
hue

各画素に直接アクセスする

dataメソッドでアクセスできます。例えば、HSV画像のimgについて、座標(x,y)の画素のHSVの値を取得するには次のようなコードで表現します。

色相H : img.data[ y * img.step + x * img.elemSize() + 0 ]

彩度S : img.data[ y * img.step + x * img.elemSize() + 1 ]

明度V : img.data[ y * img.step + x * img.elemSize() + 2 ]

今回はHSV画像ですので関係ありませんが、BGR画像の場合には、0で青B、1で緑G、2で赤Rの値が取得できます。なお、img.stepはMatの横幅、img.elemSize()は1つの画素のデータのサイズを表しています。最後に緑色であると判定された画素の数と、全体に占める割合を表示しています。今回は、248518 pixels; 20.1405 %というような結果が得られました。下の画像は緑色であると判定された部分が白で表された2値画像です。
green-area

#include "opencv/cv.h"
#include "opencv/highgui.h"
#include <iostream>
using namespace cv;
using namespace std;

bool IsSimilar(int ref, int target, int thr){
    if(abs(ref-target)<thr)return 1;
    else if(abs(ref-target+180)<thr||abs(ref-target-180)<thr)return 1;
    else return 0;
}

int main(){
    Mat img = imread("newyork.jpg", IMREAD_UNCHANGED);
    Mat hsv_img;
    cvtColor(img, hsv_img, CV_BGR2HSV);
    
    Mat green_img=Mat(Size(img.cols,img.rows),CV_8U);
    Mat yellow_img=Mat(Size(img.cols,img.rows),CV_8U);
    
    int green_pixels=0;
    for(int y = 0; y < hsv_img.rows; ++y){
        for(int x = 0; x < hsv_img.cols; ++x){
            //OpenCVでは色相Hの範囲が0~180になっていることに注意
            //緑は120度 OpenCVでは 120/2=60 あたり
            if(
               hsv_img.data[ y * hsv_img.step + x * hsv_img.elemSize() + 1 ]>100 &&
               IsSimilar(hsv_img.data[ y * hsv_img.step + x * hsv_img.elemSize() + 0 ], 60, 20)
               )
            {
                green_img.data[y * green_img.step + x * green_img.elemSize()]=255;
                green_pixels++;
            }
        }
    }
    
    imshow("IMAGE",img);
    imshow("green-area",green_img);
    cout<<green_pixels<<" pixels; "<<(double)green_pixels/img.cols/img.rows*100<<" %"<<endl;
    waitKey(0);
    return 0;
}

  • 碇シンジ

    if(abs(ref-target+180)<thr||abs(ref-target-180)<thr)return 1;

    IsSimiarメソッドの上記の文の意図を教えてください。+180と-180しているのがわかりません。

    • Yuki Mochizuki

      コメントありがとうございます。

      色相は一般的には0~360ですが、OpenCVではint型が0~255までの整数しか保持できないために、色相を一般的な値に1/2を掛けて保持しています。すなわち、一般的には120が緑ですが、OpenCVでは60が緑になります。

      さて、次の行でも180を足し引きして判定している部分ですが、まず、abs(ref-target)<thr の部分はそのままで、2つの色相の差がthr以下であればtrueを返すという意味になります。次の行ですが、今回は緑(60)を見つけたいため、targetが60になっていますので、実は不要になります。しかし、例えば、赤(0=180)を見つけたいときには色相環は環状になっていますので、10も170もどちらも赤に近いということになります。このときに先の一行だけだと、targetに0を指定した時に10だけが赤に近いと判定されてしまい、170についてはabs(170-10)=160ですので赤に近くないと判定されてしまいます。これを解消するために180足し引きして、もう一度判定しています。このように色相環において、値が近いか否かを示すためには0と180が一周回って同じ色相であることを考慮する必要があります。

    • 碇シンジ

      早速回答してくださり本当にありがとうございます。とても丁寧ですぐに理解することができましたm(_ _)m
      こちらは、お時間あるときでいいんですが、よろしければ、こちらについてもご教授願います。。 if(
      hsv_img.data[ y * hsv_img.step + x * hsv_img.elemSize() + 1 ]>100 &&

      このif文の判定は緑であるならば、その画素の彩度は100より大きいだろう。と言う理解で大丈夫でしょうか。

    • Yuki Mochizuki

      よかったです。hsv_img .data[ y * hsv_img.step + x * hsv_img.elemSize() + 1 ]>100としているのは、彩度sを取り出しているのですが、いくら色相が緑60に近くても彩度が小さければ灰色に近くなり緑っぽくはならないためです。そのため今回は彩度を100以上にしています。

    • 碇シンジ

      なるほど、とてもわかりやすいです!ありがとうございます。

      今回は明度については検討してないですが、色の判定には不要なのでしょうか?

    • Yuki Mochizuki

      どこまでやるかの問題になりますが、今回は若草色も深緑色も緑として取りたかったので明度では条件をつけていません。もちろんあまりに薄い緑や濃い緑を除外するなら明度でも条件をつけるのがベターだと思われます。

    • 碇シンジ

      本当にありがとうございました。とても助かりました。

      これからも利用させていただきますm(_ _)m

  • Pingback: ピクセルを操作してマンデルブロ集合の画像を作る - YUEDY()

  • Pingback: 【OpenCV】BGR->HSVの変換をする際に元画像の色を見てマスクの値を指定したい!変換前後の色の見え方【Python3】 | IT技術情報局()

  • やなぎ

    コメント失礼します。
    IsSimilar(hsv_img.data[ y * hsv_img.step + x * hsv_img.elemSize() + 0 ], 60, 20)
    の文章の最後の20の値は、何を示しているのか教えてください。プログラミング初心者で初歩的な質問になるかもしれませんが何卒よろしくお願いします。

    • Yuki Mochizuki

      bool IsSimilar(int ref, int target, int thr)
      部分を見てもらうといいのですが、色相の差がいくつ以下であれば似た色と判定するかという引数です。

    • やなぎ

      回答ありがとうございます!理解することができました。
      黒色の割合を算出する場合では、明度を0に設定して、色相と彩度については条件をつけなくてもいいのですか?

    • Yuki Mochizuki

      そうなります。ただ、真っ黒なピクセルということでしたら、HSVに変換しなくても、(R,G,B)=(0,0,0)という判定方法でもよいと思います。