Thursday, November 6, 2014

Color transfer (以下略)の実装

かなり古い技術ですが、ReinhardらのColor transfer between imagesは宅で実装するにはちょうどいいのでやってみました。正しいかは知りませんが。

http://dl.acm.org/citation.cfm?id=618848
http://www.cs.tau.ac.il/~turkel/imagepapers/ColorTransfer.pdf
この技術を簡単に説明すると、心理を考慮した色空間で、画像Aの色の分布を画像Bの分布に似せると、画像Aの構図で画像Bっぽい画像Cができる、と言ったものです。

画像A
画像B
画像C
上の画像AとBの場合では、画像Cの緑の映え方が、Bっぽい印象になりました。
でも、Bの哀愁漂う雰囲気は引き継がれていない気がします。こういう微妙な印象は、変えれないのかもしれません。もしかしたら実装が間違っているのかもしれません。よくわかりません。

今日はうどんだな、なんて思ったら、うどんっぽい画像になる、とかそういうこともありません。
うどん
うどんの雰囲気を持つはずの画像
論文に書いてある通りに実装したつもりですが、どこか違っているのかもしれません。でも、ま、見た感じ合っているっぽいので、違っても誤差の範囲でしょう。

以下、C++コード


#include <iostream>
#include <fstream>
#include <vector>

#include <omp.h>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#if defined(_DEBUG) || defined(DEBUG)
#pragma comment(lib, "opencv_core247d.lib")
#pragma comment(lib, "opencv_highgui247d.lib")
#pragma comment(lib, "opencv_imgproc247d.lib")
#else
#pragma comment(lib, "opencv_core247.lib")
#pragma comment(lib, "opencv_highgui247.lib")
#pragma comment(lib, "opencv_imgproc247.lib")
#endif

void BGR2LSM(cv::Mat bgr, cv::Mat& lsm){
 double trans[] = {
  0.0402, 0.5783, 0.3811,
  0.0782, 0.7244, 0.1967,
  0.8444, 0.1288, 0.0241};
 lsm = cv::Mat::zeros(bgr.size(),CV_64FC3);
 for(int i=0;i<bgr.size().height;i++){
  for(int j=0;j<bgr.size().width;j++){
   for(int k=0;k<3;k++){
    double t = 0;
    for(int l=0;l<3;l++){
     if(0==bgr.at<cv::Vec3b>(i,j)[l]){printf("error\n");}
     t += trans[k*3+l]*(double)bgr.at<cv::Vec3b>(i,j)[l];
    }
    lsm.at<cv::Vec3d>(i,j)[k] = t;
   }
  }
 }
}
void LSM2BGR(cv::Mat lsm, cv::Mat& bgr){
 double trans[] = {
  0.0497,-0.2439,1.2045,
  -1.2186,2.3809,-0.1624,
  4.4679,-3.5873,0.1193};
 bgr = cv::Mat::zeros(bgr.size(),CV_8UC3);
 for(int i=0;i<bgr.size().height;i++){
  for(int j=0;j<bgr.size().width;j++){
   for(int k=0;k<3;k++){
    double t = 0;
    for(int l=0;l<3;l++){
     t += trans[k*3+l]*(double)lsm.at<cv::Vec3d>(i,j)[l];
    }
    if(t<0)t=0;
    if(t>255)t=255;
    bgr.at<cv::Vec3b>(i,j)[k] = t;
   }   
  }
 }
}

void LSM2logLSM(cv::Mat lsm, cv::Mat& logLSM){
 logLSM = cv::Mat::zeros(lsm.size(),CV_64FC3);
 for(int i=0;i<lsm.size().height;i++){
  for(int j=0;j<lsm.size().width;j++){
   for(int k=0;k<3;k++){
    logLSM.at<cv::Vec3d>(i,j)[k] = log(lsm.at<cv::Vec3d>(i,j)[k]);
   }
  }
 }
}

void logLSM2LSM(cv::Mat logLSM, cv::Mat& lsm){
 lsm = cv::Mat::zeros(logLSM.size(),CV_64FC3);
 for(int i=0;i<lsm.size().height;i++){
  for(int j=0;j<lsm.size().width;j++){
   for(int k=0;k<3;k++){
    lsm.at<cv::Vec3d>(i,j)[k] = exp(logLSM.at<cv::Vec3d>(i,j)[k]);
   }
  }
 }
}

void logLSM2LAB_Ruderman(cv::Mat logLSM, cv::Mat& lab){
 double trans[] = {
  1.0/sqrt(3.0), 1.0/sqrt(3.0), 1.0/sqrt(3.0),
  1.0/sqrt(6.0), 1.0/sqrt(6.0),-2.0/sqrt(6.0),
  1.0/sqrt(2.0),-1.0/sqrt(2.0), 0
 };
 lab = cv::Mat::zeros(logLSM.size(),CV_64FC3);
 for(int i=0;i<logLSM.size().height;i++){
  for(int j=0;j<logLSM.size().width;j++){
   for(int k=0;k<3;k++){
    double t = 0;
    for(int l=0;l<3;l++){
     t += trans[k*3+l]*(double)logLSM.at<cv::Vec3d>(i,j)[l];
    }
    lab.at<cv::Vec3d>(i,j)[k] = t;
   }
  }
 }
}

void LAB_Ruderman2logLSM(cv::Mat lab, cv::Mat& logLSM){
 double trans[] = {
  sqrt(3.0)/3.0, sqrt(6.0)/6.0, sqrt(2.0)/2.0,
  sqrt(3.0)/3.0, sqrt(6.0)/6.0,-sqrt(2.0)/2.0,
  sqrt(3.0)/3.0,-sqrt(6.0)/3.0, 0
 };
 logLSM = cv::Mat::zeros(lab.size(),CV_64FC3);
 for(int i=0;i<logLSM.size().height;i++){
  for(int j=0;j<logLSM.size().width;j++){
   for(int k=0;k<3;k++){
    double t = 0;
    for(int l=0;l<3;l++){
     t += trans[k*3+l]*(double)lab.at<cv::Vec3d>(i,j)[l];
    }
    logLSM.at<cv::Vec3d>(i,j)[k] = t;
   }
  }
 }
}

void BGR2LAB_Ruderman(cv::Mat bgr, cv::Mat &lab){
 cv::Mat lsm,logLSM;
 BGR2LSM(bgr,lsm);
 LSM2logLSM(lsm,logLSM);
 logLSM2LAB_Ruderman(logLSM,lab);
}

void LAB_Ruderman2BGR(cv::Mat lab, cv::Mat& bgr){
 cv::Mat lsm,logLSM;
 LAB_Ruderman2logLSM(lab,logLSM);
 logLSM2LSM(logLSM,lsm);
 LSM2BGR(lsm,bgr);
}

void average(cv::Mat im, cv::Vec3d &mean, cv::Vec3d &var)
{
 for(int k=0;k<3;k++){
  mean[k] = 0;
 }
 for(int i=0;i<im.size().height;i++){
  for(int j=0;j<im.size().width;j++){
   for(int k=0;k<3;k++){
    mean[k] += im.at<cv::Vec3d>(i,j)[k];
   }
  }
 }
 for(int k=0;k<3;k++){
  mean[k] /= im.size().height*im.size().width;
 }
 for(int i=0;i<im.size().height;i++){
  for(int j=0;j<im.size().width;j++){
   for(int k=0;k<3;k++){
    double t = im.at<cv::Vec3d>(i,j)[k]-mean[k];
    var[k] += t*t;
   }
  }
 }
 for(int k=0;k<3;k++){
  var[k] /= im.size().height*im.size().width;
 }
}

void correction(cv::Mat& source, cv::Mat target){
 cv::Vec3d smean,svar,tmean,tvar;
 average(source,smean,svar);
 average(target,tmean,tvar);
 cv::Vec3d ssd,tsd;
 for(int k=0;k<3;k++){
  ssd[k] = sqrt(svar[k]);
  tsd[k] = sqrt(tvar[k]);
 }
 for(int i=0;i<source.size().height;i++){
  for(int j=0;j<source.size().width;j++){
   for(int k=0;k<3;k++){
    double labd = source.at<cv::Vec3d>(i,j)[k] - smean[k];
    source.at<cv::Vec3d>(i,j)[k] = labd*tsd[k]/ssd[k]+tmean[k];
   }
  }
 }
}

void normalizeRangeVec3b(cv::Mat &im, int min, int max){
 for(int i=0;i<im.size().height;i++){
  for(int j=0;j<im.size().width;j++){
   for(int k=0;k<3;k++){
    im.at<cv::Vec3b>(i,j)[k] = 
     (double)(max-min)*(double)im.at<cv::Vec3b>(i,j)[k]/255.0 + min;
   }
  }
 }
}

int main(int argc, char* argv[])
{
 std::string sourcename = "a.jpg";
 std::string targetname = "b.jpg";
 cv::Mat source = cv::imread(sourcename);
 cv::Mat target = cv::imread(targetname);
 normalizeRangeVec3b(source,1,255);
 normalizeRangeVec3b(target,1,255);
 
 //Ruderman
 cv::Mat sourcelab, targetlab;
 BGR2LAB_Ruderman(source,sourcelab);
 BGR2LAB_Ruderman(target,targetlab);

 correction(sourcelab,targetlab);
 //reverce
 LAB_Ruderman2BGR(sourcelab,source);
 LAB_Ruderman2BGR(targetlab,target);
 cv::imwrite(targetname+"like"+sourcename,source);
 return 0;
}





No comments: