これは,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>
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:
explicit hsvfilter(predicate_type pred) : pred_(pred) {}
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:
TODO
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]);
}
TODO
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;
}
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));
}
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 を使用して,画像を明るくするサンプルプログラムは以下になります.ここでは画像の明るさを調整していますが,これが例えば,画像の鮮やかさを調整する場合は,value の代わりに saturation の値を弄る事になります.
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_;
};
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;
}