OpenCV 2.xで1次元のヒストグラム画像を生成する


このエントリーをはてなブックマークに追加

OpenCV 2.xでヒストグラム画像を生成する方法を調べました。結論から言うと、ヒストグラムを生成する関数はあるんですが、それを画像として表示する簡便な方法はないようです。いくつかのサイトを参考にして、自分でもヒストグラム画像を作成してみました。Windows 7 + OpenCV 2.4.8で動作を確認しています。

なお、OpenCVにこだわらないのであれば、PythonのmatplotlibやR言語を使うことも検討した方がよいと思います。

ヒストグラムの作成

白黒画像の輝度をもとにヒストグラムを作っていきます。まず白黒モードで画像を読み込みます。

    cv::Mat img = cv::imread("..\\img\\baboon200.jpg", CV_LOAD_IMAGE_GRAYSCALE);

次に、ヒストグラムを作成する関数cv::calcHistを呼びます。引数が多くややこしい。今回はbinの数が64個のヒストグラムを作成します。

    // ヒストグラムを生成するために必要なデータ
    int image_num = 1;      // 入力画像の枚数
    int channels[] = { 0 }; // cv::Matの何番目のチャネルを使うか 今回は白黒画像なので0番目のチャネル以外選択肢なし
    cv::MatND hist;         // ここにヒストグラムが出力される
    int dim_num = 1;        // ヒストグラムの次元数
    int bin_num = 64;       // ヒストグラムのビンの数
    int bin_nums[] = { bin_num };      // 今回は1次元のヒストグラムを作るので要素数は一つ
    float range[] = { 0, 256 };        // 扱うデータの最小値、最大値 今回は輝度データなので値域は[0, 255]
    const float *ranges[] = { range }; // 今回は1次元のヒストグラムを作るので要素数は一つ

    // 白黒画像から輝度のヒストグラムデータ(=各binごとの出現回数をカウントしたもの)を生成
    cv::calcHist(&img, image_num, channels, cv::Mat(), hist, dim_num, bin_nums, ranges);

作成したヒストグラムはcoutに流すと簡単にデバッグプリントできます。

    // テキスト形式でヒストグラムデータを確認
    std::cout << hist << std::endl;

出力例は以下の通りです。

[1; 0; 2; 8; 11; 21; 55; 55; 104; 125; 165; 208; 318; 264; 372; 416; 480; 507; 557; 632; 770; 815; 865; 950; 952; 1103; 1135; 1315; 1421; 1688; 1819; 1921; 1721; 1676; 1470; 1387; 1283; 1219; 1219; 1172; 1143; 1097; 1183; 1217; 1211; 1231; 974; 742; 456; 268; 183; 75; 16; 2; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0]

ヒストグラム画像の作成

さきほど作ったヒストグラムからヒストグラム画像を作成します。多角形を描画する関数を使う方法もありますが、今回は単に矩形をビンの数だけ並べる簡単な方法を採用します。画像のカラーヒストグラムの描画 | OpenCV.jpを下敷きにしています。

// ヒストグラムデータを表示用の画像に変換
void make_histogram_image(cv::MatND& hist, cv::Mat& hist_img, int bin_num)
{
    // histogramを描画するための画像領域を確保
    int img_width = 512;
    int img_height = 512;
    hist_img = cv::Mat(cv::Size(img_width, img_height), CV_8UC3);

    // ヒストグラムのスケーリング
    // ヒストグラムのbinの中で、頻度数最大のbinの高さが、ちょうど画像の縦幅と同じ値になるようにする
    double max_val = 0.0;
    cv::minMaxLoc(hist, 0, &max_val);
    hist = hist * (max_val ? img_height / max_val : 0.0);

    // ヒストグラムのbinの数だけ矩形を書く
    for (int j = 0; j < bin_num; ++j){
        // saturate_castは、安全に型変換するための関数。桁あふれを防止
        int bin_w = cv::saturate_cast<int>((double)img_width / bin_num);
        cv::rectangle(
            hist_img,
            cv::Point(j*bin_w, hist_img.rows),
            cv::Point((j + 1)*bin_w, hist_img.rows - cv::saturate_cast<int>(hist.at<float>(j))),
            cv::Scalar::all(0), -1);
    }
}

int main(){
…略…

    // ヒストグラムデータを表示用の画像に変換
    // OpenCVでは関数が用意されていないので自前で用意する必要がある
    cv::Mat hist_img;
    make_histogram_image(hist, hist_img, bin_num);

…略…
}

生成されるヒストグラム画像の例は以下のとおりです。

f:id:minus9d:20140416214745p:plain

コード全文

#include <iostream>
#include <vector>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"  // cv::calcHist

// 参考
// http://laconsigna.wordpress.com/2011/04/29/1d-histogram-on-opencv/
// http://opencv.jp/cookbook/opencv_img.html


// ヒストグラムデータを表示用の画像に変換
void make_histogram_image(cv::MatND& hist, cv::Mat& hist_img, int bin_num)
{
    // histogramを描画するための画像領域を確保
    int img_width = 512;
    int img_height = 512;
    hist_img = cv::Mat(cv::Size(img_width, img_height), CV_8UC3);

    // ヒストグラムのスケーリング
    // ヒストグラムのbinの中で、頻度数最大のbinの高さが、ちょうど画像の縦幅と同じ値になるようにする
    double max_val = 0.0;
    cv::minMaxLoc(hist, 0, &max_val);
    hist = hist * (max_val ? img_height / max_val : 0.0);

    // ヒストグラムのbinの数だけ矩形を書く
    for (int j = 0; j < bin_num; ++j){
        // saturate_castは、安全に型変換するための関数。桁あふれを防止
        int bin_w = cv::saturate_cast<int>((double)img_width / bin_num);
        cv::rectangle(
            hist_img,
            cv::Point(j*bin_w, hist_img.rows),
            cv::Point((j + 1)*bin_w, hist_img.rows - cv::saturate_cast<int>(hist.at<float>(j))),
            cv::Scalar::all(0), -1);
    }
}

int main()
{
    cv::Mat img = cv::imread("..\\img\\baboon200.jpg", CV_LOAD_IMAGE_GRAYSCALE);

    // ヒストグラムを生成するために必要なデータ
    int image_num = 1;      // 入力画像の枚数
    int channels[] = { 0 }; // cv::Matの何番目のチャネルを使うか 今回は白黒画像なので0番目のチャネル以外選択肢なし
    cv::MatND hist;         // ここにヒストグラムが出力される
    int dim_num = 1;        // ヒストグラムの次元数
    int bin_num = 64;       // ヒストグラムのビンの数
    int bin_nums[] = { bin_num };      // 今回は1次元のヒストグラムを作るので要素数は一つ
    float range[] = { 0, 256 };        // 扱うデータの最小値、最大値 今回は輝度データなので値域は[0, 255]
    const float *ranges[] = { range }; // 今回は1次元のヒストグラムを作るので要素数は一つ

    // 白黒画像から輝度のヒストグラムデータ(=各binごとの出現回数をカウントしたもの)を生成
    cv::calcHist(&img, image_num, channels, cv::Mat(), hist, dim_num, bin_nums, ranges);

    // テキスト形式でヒストグラムデータを確認
    std::cout << hist << std::endl;

    // ヒストグラムデータを表示用の画像に変換
    // OpenCVでは関数が用意されていないので自前で用意する必要がある
    cv::Mat hist_img;
    make_histogram_image(hist, hist_img, bin_num);
    cv::imshow("histogram image", hist_img);

    // 画像表示のためのwait
    cv::waitKey(-1);

    return 0;
}