Color Transfer の実装

今回は画像処理のアルゴリズムの中でも結果が面白く、かつ実装が簡単なColor Transferを紹介します。

Color Transferは一言で言えば、例示ベースの色調変換法です。入力としては「色を変えたい画像」(目的画像)と「こういう風な色にしたいと思う画像」(例示画像)を与えます。

その2つの画像をlab (論文の色変換を見たところCIE Labとは異なるもののようです) という色の表し方を使って表現し、色ヒストグラムを作ります。

目的画像の色ヒストグラムの平均と分散が、例示画像のそれと同じになるように色変換を行うと、結果の画像が得られます。詳しいアルゴリズムは下記のリンクにある論文を参照してください。

Color transfer between images [Reinhard 2001]

Color Transferはアルゴリズムは単純なのですが、色空間を変換するところで、やや面倒な行列演算をしなくてはならないので、以下に示すコードでは色変換のコードを単純にするためにColor3dというクラスを使っています。

基本的なコードの流れはmain.cppだけを見れば分かるようにしたつもりですが、もし分かりづらい部分があれば、Color3dクラスの方も参照してください。

もちろんコメントを下さっても結構です。

コード一覧 (リンクをクリックすると該当のコードに飛びます)


結果画像

元画像01
SONY DSC

参照画像01
reference01

結果画像01
output01

元画像02
target02

参照画像02
reference02

結果画像02
output02


Color3d.h

3次元の色表現を扱いやすくするためのクラスです。機能は少なめですが、おいおい増やしていく予定?こちらはヘッダファイルです。

#ifndef _COLOR_3D_H_
#define _COLOR_3D_H_

#include

class Color3d {
public:
    double v[3];

    // デフォルトコンストラクタ
    Color3d();

    // コンストラクタ
    Color3d(double r, double g, double b);

    // コピーコンストラクタ
    Color3d(const Color3d& c3d);

    // 演算子 =
    Color3d& operator=(const Color3d& c3d);

    // アクセス演算子
    double& operator()(int i);

    // 演算子 +
    Color3d operator+(const Color3d& c3d);

    // 演算子 -
    Color3d operator-(const Color3d& c3d);

    // 演算子 *
    Color3d operator*(const Color3d& c3d);

    /* メソッド定義 */
    // 値の定数倍
    Color3d multiply(double d);

    // 値の定数商
    Color3d divide(double d);
};

#endif

Color3d.cpp

こちらはColor3d.hの実装を書いたcppファイルです。

#include "Color3d.h"

// デフォルトコンストラクタ
Color3d::Color3d()
    : v()
{
    v[0] = 0.0;
    v[1] = 0.0;
    v[2] = 0.0;
}

// コンストラクタ
Color3d::Color3d(double v0, double v1, double v2)
    : v()
{
    v[0] = v0;
    v[1] = v1;
    v[2] = v2;
}

// コピーコンストラクタ
Color3d::Color3d(const Color3d& c3d)
    : v()
{
    v[0] = c3d.v[0];
    v[1] = c3d.v[1];
    v[2] = c3d.v[2];
}

// 演算子 =
Color3d& Color3d::operator=(const Color3d& c3d) {
    v[0] = c3d.v[0];
    v[1] = c3d.v[1];
    v[2] = c3d.v[2];
    return (*this);
}

// アクセス演算子
double& Color3d::operator()(int i) {
    assert(i >= 0 && i <= 2);
    return v[i];
}

// 演算子 +
Color3d Color3d::operator+(const Color3d& c3d) {
    Color3d ret = Color3d();
    ret.v[0] = v[0] + c3d.v[0];
    ret.v[1] = v[1] + c3d.v[1];
    ret.v[2] = v[2] + c3d.v[2];
    return ret;
}

// 演算子 -
Color3d Color3d::operator-(const Color3d& c3d) {
    Color3d ret = Color3d();
    ret.v[0] = v[0] - c3d.v[0];
    ret.v[1] = v[1] - c3d.v[1];
    ret.v[2] = v[2] - c3d.v[2];
    return ret;
}

// 演算子 +
Color3d Color3d::operator*(const Color3d& c3d) {
    Color3d ret = Color3d();
    ret.v[0] = v[0] * c3d.v[0];
    ret.v[1] = v[1] * c3d.v[1];
    ret.v[2] = v[2] * c3d.v[2];
    return ret;
}

// 値のスケーリング
Color3d Color3d::multiply(double d) {
    Color3d ret = Color3d();
    ret.v[0] = v[0] * d;
    ret.v[1] = v[1] * d;
    ret.v[2] = v[2] * d;
    return ret;
}

Color3d Color3d::divide(double d) {
    Color3d ret = Color3d();
    ret.v[0] = v[0] / d;
    ret.v[1] = v[1] / d;
    ret.v[2] = v[2] / d;
    return ret;
}

main.cpp

最後にColor Transferの処理が書いてあるmainのcppファイルです。

/***********************************************************
* Color Transfer Implementation
************************************************************
* This code is an implementation of the paper [Reinhard2000].
* The program transfers the color of one image (in this code
* reference image) to another image (in this code target image).
*
* usage: > ColorTransfer.exe [target image] [reference image]
*
* This code is this programmed by 'tatsy'. You can use this
* code for any purpose :-)
************************************************************/

#include
#include
#include
using namespace std;

#include <opencv2opencv.hpp>

#include "Color3d.h"

// Multiplication of matrix and vector
Color3d operator *(const cv::Mat& M, Color3d& v) {
    Color3d u = Color3d();
    for(int i=0; i<3; i++) {
        u(i) = 0.0;
        for(int j=0; j<3; j++) {
            u(i) += M.at(i, j) * v(j);
        }
    }
    return u;
}

// Transformation from RGB to LMS
const double RGB2LMS[3][3] = {
    { 0.3811, 0.5783, 0.0402 },
    { 0.1967, 0.7244, 0.0782 },
    { 0.0241, 0.1288, 0.8444 }
};

// Transformation from LMS to RGB
const double LMS2RGB[3][3] = {
    {  4.4679, -3.5873,  0.1193 },
    { -1.2186,  2.3809, -0.1624 },
    {  0.0497, -0.2439,  1.2045 }
};

// First transformation from LMS to lab
const double LMS2lab1[3][3] = {
    { 1.0 / sqrt(3.0), 0.0, 0.0 },
    { 0.0, 1.0 / sqrt(6.0), 0.0 },
    { 0.0, 0.0, 1.0 / sqrt(2.0) }
};

// Second transformation from LMS to lab
const double LMS2lab2[3][3] = {
    { 1.0,  1.0,  1.0 },
    { 1.0,  1.0, -2.0 },
    { 1.0, -1.0,  0.0 }
};

const double eps = 1.0e-4;

int main(int argc, char** argv) {
    // Check number of arguments
    if(argc <= 2) {
        cout << "usage: > ColorTransfer.exe [target image] [reference image]" << endl;
        return -1;
    }

    // Load target image
    cv::Mat target = cv::imread(argv[1], CV_LOAD_IMAGE_COLOR);
    if(target.empty()) {
        cout << "Failed to load file "" << argv[1] << """ << endl;
        return -1;
    }
    cv::cvtColor(target, target, CV_BGR2RGB);
    target.convertTo(target, CV_64FC3, 1.0 / 255.0);

    // Load reference image
    cv::Mat refer  = cv::imread(argv[2], CV_LOAD_IMAGE_COLOR);
    if(refer.empty()) {
        cout << "Failed to load file "" << argv[2] << """ << endl;
        return -1;
    }
    cv::cvtColor(refer, refer, CV_BGR2RGB);
    refer.convertTo(refer, CV_64FC3, 1.0 / 255.0);

    // Construct transformation matrix
    const size_t bufsize = sizeof(double) * 3 * 3;
    cv::Mat mRGB2LMS = cv::Mat(3, 3, CV_64FC1);
    memcpy(mRGB2LMS.data, &RGB2LMS[0][0], bufsize);

    cv::Mat mLMS2RGB = cv::Mat(3, 3, CV_64FC1);
    memcpy(mLMS2RGB.data, &LMS2RGB[0][0], bufsize);

    cv::Mat mLMS2lab1 = cv::Mat(3, 3, CV_64FC1);
    memcpy(mLMS2lab1.data, &LMS2lab1[0][0], bufsize);

    cv::Mat mLMS2lab2 = cv::Mat(3, 3, CV_64FC1);
    memcpy(mLMS2lab2.data, &LMS2lab2[0][0], bufsize);

    cv::Mat mLMS2lab = mLMS2lab2 * mLMS2lab1;
    cv::Mat mlab2LMS = mLMS2lab.inv();

    // Transform images from RGB to lab and
    // compute average and standard deviation of each color channels
    Color3d v;
    Color3d mt = Color3d(0.0, 0.0, 0.0);
    Color3d st  = Color3d(0.0, 0.0, 0.0);
    for(int y=0; y<target.rows; y++) {
        for(int x=0; x<target.cols; x++) {
            v = target.at(y, x);
            v = mRGB2LMS * v;
            for(int c=0; c<3; c++) v(c) = v(c) > eps ? log10(v(c)) : log10(eps);

            target.at(y, x) = mLMS2lab * v;
            mt = mt + target.at(y, x);
            st  = st + target.at(y, x) * target.at(y, x);
        }
    }

    Color3d mr = Color3d(0.0, 0.0, 0.0);
    Color3d sr  = Color3d(0.0, 0.0, 0.0);
    for(int y=0; y<refer.rows; y++) {
        for(int x=0; x<refer.cols; x++) {
            v = refer.at(y, x);
            v = mRGB2LMS * v;
            for(int c=0; c<3; c++) v(c) = v(c) > eps ? log10(v(c)) : log10(eps);

            refer.at(y, x) = mLMS2lab * v;
            mr = mr + refer.at(y, x);
            sr = sr + refer.at(y, x) * refer.at(y, x);
        }
    }

    int Nt = target.rows * target.cols;
    int Nr = refer.rows * refer.cols;
    mt = mt.divide(Nt);
    mr = mr.divide(Nr);
    st = st.divide(Nt) - mt * mt;
    sr = sr.divide(Nr) - mr * mr;
    for(int i=0; i<3; i++) {
        st(i) = sqrt(st(i));
        sr(i) = sqrt(sr(i));
    }

    // Transfer colors
    for(int y=0; y<target.rows; y++) {
        for(int x=0; x<target.cols; x++) {
            for(int c=0; c<3; c++) {
                double val = target.at(y, x*3+c);
                target.at(y, x*3+c) = (val - mt(c)) / st(c) * sr(c) + mr(c);
            }
        }
    }

    // Transform back from lab to RGB
    for(int y=0; y<target.rows; y++) {
        for(int x=0; x<target.cols; x++) {
            v = target.at(y, x);
            v = mlab2LMS * v;
            for(int c=0; c<3; c++) v(c) = v(c) > -5.0 ? pow(10.0, v(c)) : eps;

            target.at(y, x) = mLMS2RGB * v;
        }
    }
    target.convertTo(target, CV_8UC3, 255.0);
    cv::cvtColor(target, target, CV_RGB2BGR);

    cv::namedWindow("target");
    cv::imshow("target", target);
    cv::imwrite("output.jpg", target);
    cv::waitKey(0);
    cv::destroyAllWindows();
}

今回の記事はここまでです。

最後までお読みいただきありがとうございました。