Boost.GIL で HSV 変換を行う

これは,Boost Advent Calendar 2011 参加記事です.現在,開催されている Advent Calendar の一覧は Advent Calendar 2011 (jp) 開催予定リスト - Life like a clown を参照下さい.

当初,Boost.GIL の(初歩的な)逆引きリファレンス的な記事にしようかと思ったのですが,Boost.勉強会の Boost.GIL 画像処理入門 (PDF) に大体の事が書かれてあってやる事がなかったので,後半に書く予定だった「画像を明るくする」,「画像を鮮やかにする」等の処理を行うための HSV 変換についてをメインに書くことにします.

HSV 変換のアルゴリズムについては以下を参照下さい.

これを Boost.GIL で実現する方法ですが,boost::gil::transform_pixel と言う関数を使うと良いようです.

boost::gil::transform_pixel(const View& src, const View& dst, F functor)

対象のピクセルを受け取って別のピクセルを返す関数を、全ピクセルに対して適用します。この関数は、画素値と出力値が1対1対応である場合に用います(e.g. 明度、コントラスト, レベル補正, トーンカーブ, etc...)。

boost::gil::transform_pixel_positions(const View& src, const View& dst, F functor)

周囲のピクセルの値を元に結果となるピクセルを返す関数を、全ピクセルに対して適用します。transform_pixelとは、受け取る引数がロケータであるという点が異なっており、周囲の画素値を元に実際の出力値を求める場合に有効です(e.g. Blur, Sovel, Laplacian, etc...)。

映像奮闘記: boost.GILで組む、ジェネリックな画像処理のアルゴリズム

尚,第 3 引数に指定する関数オブジェクトは以下のようになります.

class transform_functor {
public:
    template<typename PixelT>
    PixelT operator()(PixelT src);
};

HSV 色空間内で処理を行うための hsvfilter

今回は,この形に沿った関数オブジェクトで「RGB 値を HSV 値に変換してユーザに何らかの処理を行ってもらった後,再び RGB 値に戻す」と言う機能を持つ hsvfilter を実装してみます.尚,現時点では各ピクセルが RGB 値のみを持つ画像でないとエラーになります.それ以外のピクセル型については,そのうち調べて実装していこうかと思います.

#ifndef HSVFILTER_H
#define HSVFILTER_H

#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_io.hpp>
#include <boost/function.hpp>
#include <boost/gil/gil_all.hpp>
#include <cassert>

/* ------------------------------------------------------------------------- */
/*
 *  hsvfilter
 *
 *  各ピクセルを HSV 値に変換した上で何らかの処理を行うためのフィルタ.
 */
/* ------------------------------------------------------------------------- */
class hsvfilter {
public:
    typedef boost::function<void (int&, int&, int&)> predicate_type;
private:
    typedef boost::tuple<int, int, int> rgb_type;
    typedef boost::tuple<int, int, int> hsv_type;
public:
    /* --------------------------------------------------------------------- */
    //  constructor
    /* --------------------------------------------------------------------- */
    explicit hsvfilter(predicate_type pred) : pred_(pred) {}
    
    /* --------------------------------------------------------------------- */
    //  operator()
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    PixelT operator()(PixelT pixel) {
        rgb_type src = this->to_rgb(pixel);
        hsv_type hsv = this->rgb2hsv(src);
        pred_(hsv.get<0>(), hsv.get<1>(), hsv.get<2>());
        rgb_type dest = this->hsv2rgb(hsv);
        return this->from_rgb<PixelT>(dest);
    }

private:
    /* --------------------------------------------------------------------- */
    /*
     *  to_rgb
     *
     *  Boost.GIL の各種ピクセル型から RGB へ変換する.
     *  TODO: 現在は RGB の値のみが格納されてあるものしかサポートされて
     *  いない.その他のピクセル型を調査して対応する.
     */
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    rgb_type to_rgb(const PixelT& pixel) {
        assert(boost::gil::num_channels<PixelT>::value == 3);
        return boost::make_tuple(pixel[0], pixel[1], pixel[2]);
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  from_rgb
     *
     *  RGB から Boost.GIL の各種ピクセル型へ変換する.
     *  TODO: 現在は RGB の値のみが格納されてあるものしかサポートされて
     *  いない.その他のピクセル型を調査して対応する.
     */
    /* --------------------------------------------------------------------- */
    template <class PixelT>
    PixelT from_rgb(const rgb_type& rgb) {
        assert(boost::gil::num_channels<PixelT>::value == 3);
        
        PixelT dest;
        dest[0] = rgb.get<0>();
        dest[1] = rgb.get<1>();
        dest[2] = rgb.get<2>();
        return dest;
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  rgb2hsv
     *
     *  RGB 値を HSV 値に変換する.
     */
    /* --------------------------------------------------------------------- */
    hsv_type rgb2hsv(const rgb_type& rgb) {
        const int R = std::min(rgb.get<0>(), 255);
        const int G = std::min(rgb.get<1>(), 255);
        const int B = std::min(rgb.get<2>(), 255);
        
        const int maximum = std::max(std::max(R, G), B);
        const int minimum = std::min(std::min(R, G), B);
        
        const double V = maximum;
        const double S = (maximum != 0) ? 255.0 * (maximum - minimum) / static_cast<double>(maximum) : 0.0;
        
        double H = 0.0;
        if (S > 0.0 && maximum != minimum) {
            if (maximum == R) H = 60.0 * (G - B) / static_cast<double>(maximum - minimum);
            else if (maximum == G) H = 60.0 * (B - R) / static_cast<double>(maximum - minimum) + 120.0;
            else H = 60.0 * (R - G) / static_cast<double>(maximum - minimum) + 240.0;
            if (H < 0.0) H += 360.0;
        }
        
        return boost::make_tuple(static_cast<int>(H), static_cast<int>(S), static_cast<int>(V));
    }
    
    /* --------------------------------------------------------------------- */
    /*
     *  hsv2rgb
     *
     *  HSV 値を RGB 値に変換する.
     */
    /* --------------------------------------------------------------------- */
    rgb_type hsv2rgb(const hsv_type& hsv) {
        const int H = hsv.get<0>();
        const int S = hsv.get<1>();
        const int V = hsv.get<2>();
        
        if (S == 0) return boost::make_tuple(V, V, V);
        
        const int Hi = H / 60;
        const double F = H / 60.0 - Hi;
        const double M = (1.0 - S / 255.0) * V;
        const double N = (1.0 - S / 255.0 * F) * V;
        const double K = (1.0 - S / 255.0 * (1.0 - F)) * V;
        
        switch (Hi) {
            case 0: return boost::make_tuple(V, K, M);
            case 1: return boost::make_tuple(N, V, M);
            case 2: return boost::make_tuple(M, V, K);
            case 3: return boost::make_tuple(M, N, V);
            case 4: return boost::make_tuple(K, M, V);
            case 5: return boost::make_tuple(V, M, N);
            default: break;
        }
        
        return boost::make_tuple(-1, -1, -1);
    }
    
private:
    predicate_type pred_;
};

#endif // HSVFILTER_H

サンプルプログラム

この hsvfilter を使用して,画像を明るくするサンプルプログラムは以下になります.ここでは画像の明るさを調整していますが,これが例えば,画像の鮮やかさを調整する場合は,value の代わりに saturation の値を弄る事になります.

/* ------------------------------------------------------------------------- */
/*
 *  brightener
 *
 *  各ピクセルの明るさを一律に明るくしていくクラス.
 */
/* ------------------------------------------------------------------------- */
class brightener {
public:
    explicit brightener(int brightness) :
        brightness_(brightness) {}
    
    void operator()(int& hue, int& saturation, int& value) {
        value += brightness_;
        if (value > 255) value = 255;
    }
    
private:
    int brightness_;
};

/* ------------------------------------------------------------------------- */
//  main
/* ------------------------------------------------------------------------- */
int main(int argc, char* argv[]) {
    if (argc < 3) return -1;
    
    std::string src_path = argv[1];
    std::string dest_path = argv[2];
    
    boost::gil::rgb8_image_t src_image;
    boost::gil::jpeg_read_image(src_path, src_image);
    boost::gil::rgb8_image_t dest_image(src_image.dimensions());
    
    // メイン処理
    int brightness = 50;
    boost::gil::transform_pixels(
        boost::gil::const_view(src_image),
        boost::gil::view(dest_image),
        hsvfilter(brightener(brightness))
    );
    
    boost::gil::jpeg_write_view(dest_path, boost::gil::view(dest_image));
    
    return 0;
}