C++图像处理 -- 表面模糊

阅读提示

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    尽可能保持二者内容一致,可相互对照。

    本文代码必须包括《C++图像处理 -- 数据类型及公用函数》文章中的BmpData.h头文件。

 

    图像的表面模糊处理是Photoshop CS2以后才有的新功能,其作用是在保留图像边缘的情况下,对图像的表面进行模糊处理。在对人物皮肤处理上,比高斯模糊更有效。因为高斯模糊在使人物皮肤光洁的同时,也将一些边缘特征如脸部的眉毛、嘴唇等给模糊了,不得不用蒙版小心的抹去这些地方的模糊部分。

    在处理手法上,表面模糊也与其它卷积处理手段不同,如高斯模糊等在处理图像时都是采用统一的卷积矩阵进行,而表面模糊却是每一个像素点都有自己的卷积矩阵,而且还是3(4)套,用以对应于像素的R、G、B(A、R、G、B)分量。所以表面模糊在编程处理时,比其它卷积操作更复杂、更耗时,因为它要对每一个像素计算自己的卷积矩阵。表面模糊编程的难点也在计算卷积矩阵上,其它与一般图像卷积处理一样。

    表面模糊处理有2个参数,即模糊半径和模糊阈值,前者确定模糊的范围,后者确定模糊的程度。模糊范围就是卷积矩阵大小,如模糊半径为1,则模糊矩阵直径为1*2+1等于3,矩阵元素个数为3*3等于9,矩阵的中间元素即是当前像素点。

    矩阵元素值的计算公式为:

    mij = 1 - (|pij - p0|) / 2.5T

    其中,mij为矩阵的各元素值,pij为矩阵元素对应的像素分量值,p0为矩阵中心元素对应的像素分量值,T为阈值,|pij - p0|为矩阵元素对应像素分量值与中心元素对应像素分量值的绝对差值。如果mij < 0,则mij = 0。

    对ARGB格式图像数据来说,因有4个分量,故需要4套卷积矩阵。

    矩阵元素确定后,就可按照一般图像卷积操作进行处理了,即分别累计矩阵元素值和与之对应的像素分量值乘积,用累计像素分量值除以累计元素值,即可得到当前像素分量模糊处理后的值。

    下面是表面模糊处理代码:

//---------------------------------------------------------------------------

// ARGB图像数据表面模糊处理
// 参数: 图像数据, 模糊半径(1 - 100), 阈值(2 - 255)
VOID SurfaceBlur(BitmapData *data, UINT radius, UINT threshold)
{
	if (radius < 1 || radius > 100 || threshold < 2 || threshold > 255)
		return;

	// 设置模糊矩阵元素表
	FLOAT *matrixItems = new FLOAT[255*2+1];
	FLOAT *items = &matrixItems[255];
	FLOAT fv = threshold * 2.5;
	INT i;
	for (i = 1; i < 256; i ++)
	{
		items[-i] = items[i] = 1 - i / fv;
		if (items[i] < 0) break;
	}
	for (; i < 256; i ++)
		items[-i] = items[i] = 0;
	*items = 1;

	// 获取边框像素扩展图像数据到src
	BitmapData src;
	GetExpendData(data, radius, &src);
	// 获取数据处理参数
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(data, &src, width, height, pd, ps, dstOffset, srcOffset);

	INT size = (radius << 1) + 1;
	INT pOffset = ((src.Stride >> 2) + 1) * radius;
	INT iOffset = (src.Stride >> 2) - size;
	FLOAT pixelA, pixelR, pixelG, pixelB;
	FLOAT nuclearA, nuclearR, nuclearG, nuclearB;
	FLOAT ivA, ivR, ivG, ivB;
	for (UINT y = 0; y < height; y ++, ps += srcOffset, pd += dstOffset)
	{
		for (UINT x = 0; x < width; x ++, ps ++, pd ++)
		{
			pixelA = pixelR = pixelG = pixelB = 0;
			nuclearA = nuclearR = nuclearG = nuclearB = 0;
			PARGBQuad p = ps, p0 = p + pOffset;
			for (INT i = 0;  i < size; i ++, p += iOffset)
			{
				for (INT j = 0; j < size; j ++, p ++)
				{
					// 以Pij - p0为下标从模糊矩阵元素表中获取像素各分量的元素值
					ivB = items[p->Blue - p0->Blue];
					ivG = items[p->Green - p0->Green];
					ivR = items[p->Red - p0->Red];
					ivA = items[p->Alpha - p0->Alpha];
					// 累计像素各分量元素值
					nuclearB += ivB;
					nuclearG += ivG;
					nuclearR += ivR;
					nuclearA += ivA;
					// 累计像素各分量元素值与分量值的乘积
					pixelB += (ivB * p->Blue);
					pixelG += (ivG * p->Green);
					pixelR += (ivR * p->Red);
					pixelA += (ivA * p->Alpha);
				}
			}
			// 计算像素表面模糊后的新分量值
			if (nuclearB > 0)
				pd->Blue = (BYTE)(pixelB / nuclearB + 0.5);
			if (nuclearG > 0)
				pd->Green = (BYTE)(pixelG / nuclearG + 0.5);
			if (nuclearR > 0)
				pd->Red = (BYTE)(pixelR / nuclearR + 0.5);
			if (nuclearA > 0)
				pd->Alpha = (BYTE)(pixelA / nuclearA + 0.5);
		}
	}

	FreeBitmapData(&src);
	delete[] matrixItems;
	// 如果图像数据含Alpha,将PARGB像素格式还原为ARGB像素格式
	if (HasAlphaFlag(data))
		ArgbConvertPArgb(data);
}
//---------------------------------------------------------------------------

    表面模糊处理函数SurfaceBlur采用浮点数进行矩阵元素的计算和像素的模糊处理,为减少循环过程中的模糊矩阵元素的计算量,函数预先定义并计算了255*2+1大小的模糊矩阵元素表matrixItems,元素指针items指向matrixItems[255]元素位置,该位置为pij - p0 = 0时的元素值,如果pij - p0 > 0,其对应元素值在items右边,反之,如果pij - p0 < 0则对应元素值在items左边,如此便免去了|pij - p0|中的绝对值处理。

    为了加快运行速度,对于不含Alpha信息的图像可不处理alpha分量。也可将处理函数改为定点数运算,但计算精度要差一些。

    下面是一个利用SurfaceBlur函数对GDI+位图进行表面模糊的例子(使用BCB2010):

//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
	Gdiplus::Bitmap *bmp =  new Gdiplus::Bitmap(L"..\\..\\media\\Source1.jpg");
	Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
	g->DrawImage(bmp, 0, 0);

	BitmapData data;
	LockBitmap(bmp, &data);
	SurfaceBlur(&data, 3, 10);
	UnlockBitmap(bmp, &data);
	g->DrawImage(bmp, data.Width + 8, 0);

	delete g;
	delete bmp;
}
//---------------------------------------------------------------------------

    例子运行效果截图:

C++图像处理 -- 表面模糊_第1张图片

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]

    这里可访问《C++图像处理 -- 文章索引

你可能感兴趣的:(C/C++,C/C++图形图像)