基于opencv跟c++的图像处理:直方图匹配

基于opencv和c++的图像处理:直方图匹配

在冈萨雷斯的那本《数字图像处理》中提到了一种神奇的变换:直方图匹配变换(Histogram Matching), 输入两幅图A和B,A和B的直方图不同,直方图匹配变换是这样的一个变换s = F(r), 使得变换之后,A的直方图和B的直方图一样。也就是它们的颜色分布变成一样。  


比如下面两幅图:

基于opencv跟c++的图像处理:直方图匹配

基于opencv跟c++的图像处理:直方图匹配


一个沙漠,一个海滩,它们的RGB直方图显然是不一样的。

但是执行直方图匹配变换后,沙漠那张图就变成这样了


基于opencv跟c++的图像处理:直方图匹配

和海滩那张图的直方图比一下,会发现上图的直方图与之几乎是一样的。沙漠图也就带上了海滩的味道。 这个变换的神奇之处在于,假设海滩那种图中的每个像素点都是可以*移动的,在经过某次神奇的移动之后,海滩变成了沙漠,但是他们视觉上的色彩效果是一致的,不同的是像素点在不同的位置所造成的结构上的差异。
细看之后,海滩化后的沙漠怎么还有一个地方是沙子的颜色呢?其实是海滩上的沙子移过来的。当然,这个变换其实是近似的,主要是因为其中用到了一个变换的反变换,而该变换并不是双射,所以其反变换是近似的,这个也是代码中比较复杂的地方。详细的算法细节可以参考冈萨雷斯的那本数字图像处理,当然最好是看英文版,那本中文版我是根本没看懂。


相关代码:

bool GFImage::HistogramMatching(const GFImage& anoImage)
{
	assert(GetChannel() == anoImage.GetChannel());
	//r->s
	vector<vector<uchar> > vRSMap;
	CalculateMapFunByHisEq(vRSMap);
	//z->s
	vector<vector<uchar> > vZSMap;
	anoImage.CalculateMapFunByHisEq(vZSMap);

	vector<vector<uchar> > vRZMap;
	vRZMap.resize(GetChannel());
	for(int ch = 0; ch < GetChannel(); ch++)
	{
		vRZMap[ch].resize(256);
	}

	for (int ch = 0; ch < GetChannel(); ch++)
	{
		vector<int> vSZMap;
		vSZMap.resize(256);
		for (int i = 0;i < 256;i++)
		{
			vSZMap[i] = -1;
		}
		for (int z = 255; z>=0; z--)
		{
			vSZMap[vZSMap[ch][z]] = z;
		}
		vector<int> vSIndex;
		//加前哨
		vSIndex.push_back(-1);
		for (int s = 0; s< 256 ;s++)
		{
			if (vSZMap[s] != -1)
			{
				vSIndex.push_back(s);
			}
		}
		//加后哨
		vSIndex.push_back(256);
		for (int i = 0; i < vSIndex.size() - 1; i++)
		{
			int startIdx = vSIndex[i] + 1;
			int endIdx = vSIndex[i + 1] - 1;

			int lowerIdx = vSIndex[i];
			int upperIdx = vSIndex[i + 1];

			if (i == 0)
			{
				lowerIdx = upperIdx;
			}
			if ( i == vSIndex.size() - 2)
			{
				upperIdx = lowerIdx;
			}

			int nLen = endIdx - startIdx + 1;
			int nMidIdx = startIdx + nLen / 2;
			for (int j = startIdx; j < nMidIdx; j++)
			{
				vSZMap[j] = vSZMap[lowerIdx];
			}
			for (int j = nMidIdx; j <= endIdx ; j++)
			{
				vSZMap[j] = vSZMap[upperIdx];
			}			
		}
		
		for (int r = 0; r < 256; r++)
		{
			vRZMap[ch][r] = vSZMap[vRSMap[ch][r]];	
		}
	}

	for (int ch = 0; ch < GetChannel(); ch++)
	{
		uchar * pData = GetData();
		for (int r = 0;r < GetHeight(); r++)
		{
			uchar * pLine = pData + r * GetWidthStep();
			for (int c = 0; c < GetWidth(); c++)
			{
				uchar val = pLine[GetChannel() * c + ch];
				pLine[GetChannel() * c + ch] = vRZMap[ch][val];
			}
		}
	}
	return true;

}

完整代码可参考这里