Recoloring via Histogram Matching with OpenCV [w/ code]
Hi
I wanted to do the simplest recoloring/color-transfer I could find – and the internet is just a bust. Nothing free, good and usable available online… So I implemented the simplest color transfer algorithm in the wolrd – Histogram Matching.
Here’s the implementation with OpenCV
// Compute histogram and CDF for an image with mask
void do1ChnHist(const Mat& _i, const Mat& mask, double* h, double* cdf) {
Mat _t = _i.reshape(1,1);
Mat _tm;
mask.copyTo(_tm);
_tm = _tm.reshape(1,1);
for(int p=0;p<_t.cols;p++) {
if(_tm.at(0,p) > 0) {
uchar c = _t.at(0,p);
h[c] += 1.0;
}
}
//normalize hist
Mat _tmp(1,256,CV_64FC1,h);
double minVal,maxVal;
minMaxLoc(_tmp,&minVal,&maxVal);
_tmp = _tmp / maxVal;
cdf[0] = h[0];
for(int j=1;j<256;j++) {
cdf[j] = cdf[j-1]+h[j];
}
//normalize CDF
_tmp.data = (uchar*)cdf;
minMaxLoc(_tmp,&minVal,&maxVal);
_tmp = _tmp / maxVal;
}
// match histograms of 'src' to that of 'dst', according to both masks
void histMatchRGB(Mat& src, const Mat& src_mask, const Mat& dst, const Mat& dst_mask) {
#ifdef BTM_DEBUG
namedWindow("original source",CV_WINDOW_AUTOSIZE);
imshow("original source",src);
namedWindow("original query",CV_WINDOW_AUTOSIZE);
imshow("original query",dst);
#endif
vector<Mat> chns;
split(src,chns);
vector<Mat> chns1;
split(dst,chns1);
Mat src_hist = Mat::zeros(1,256,CV_64FC1);
Mat dst_hist = Mat::zeros(1,256,CV_64FC1);
Mat src_cdf = Mat::zeros(1,256,CV_64FC1);
Mat dst_cdf = Mat::zeros(1,256,CV_64FC1);
Mat Mv(1,256,CV_8UC1);
uchar* M = Mv.ptr<uchar>();
for(int i=0;i<3;i++) {
src_hist.setTo(cvScalar(0));
dst_hist.setTo(cvScalar(0));
src_cdf.setTo(cvScalar(0));
src_cdf.setTo(cvScalar(0));
do1ChnHist(chns[i],src_mask,src_hist,src_cdf);
do1ChnHist(chns1[i],dst_mask,dst_hist,dst_cdf);
uchar last = 0;
double* _src_cdf = src_cdf.ptr<double>();
double* _dst_cdf = dst_cdf.ptr<double>();
for(int j=0;j<src_cdf.cols;j++) {
double F1j = _src_cdf[j];
for(uchar k = last; k<dst_cdf.cols; k++) {
double F2k = _dst_cdf[k];
if(abs(F2k - F1j) < HISTMATCH_EPSILON || F2k > F1j) {
M[j] = k;
last = k;
break;
}
}
}
Mat lut(1,256,CV_8UC1,M);
LUT(chns[i],lut,chns[i]);
}
Mat res;
merge(chns,res);
#ifdef BTM_DEBUG
namedWindow("matched",CV_WINDOW_AUTOSIZE);
imshow("matched",res);
waitKey(BTM_WAIT_TIME);
#endif
res.copyTo(src);
}
Edit 31/1/10: nicer code in histMatch, better memory consumption.
The code is fairly simple.. I started using the OpenCV 2.0 C++ API and I must say it’s great, not having to worry about silly memory managment, everything is instanced on the stack and get’s released automatically.
Just a quick overview:
- Compute histogram and comulative distibution function (CDF) for each image.
- Create the lookup table (LUT) from one CDF to the other
- Apply the LUT to the image
External usage is simple
Mat src=cvLoadImage("image008.jpg");
Mat dst=cvLoadImage("image003.jpg");
Mat src_mask = cvLoadImage("image008_mask.bmp",0);
Mat dst_mask = cvLoadImage("image003_mask.bmp",0);
histMatchRGB(dst,dst_mask,src,src_mask);
Enjoy!
Roy.
What value are you using for HISTMATCH_EPSILON?
#define HISTMATCH_EPSILON 0.000001