Categories
code graphics opencv vision

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 += 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&#91;i&#93;,src_mask,src_hist,src_cdf);
		do1ChnHist(chns1&#91;i&#93;,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&#91;j&#93;;
			for(uchar k = last; k<dst_cdf.cols; k++) {
				double F2k = _dst_cdf&#91;k&#93;;
				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.