PS中有个表面模糊的功能,这个功能可以在保留边缘的情况下对图像平坦区域进行模糊滤波,这个功能可以实现很好的磨皮效果,它的算法如下:
这个算法很简单,设置半径r,得到一个边长为(2r+1)的正方形窗口,那么窗口中心像素的像素值即为x,当然,对于像素的RGB三个分量,是需要分别计算的,因此这个算法的时间消耗比较大。
类似的算法还有双边滤波等,不过这类滤波普遍都比较耗时,虽然双边滤波已经可以在PC上实时,但是,在手机端还存在较大问题。
下面给出一些对比效果图:
原图 表面模糊(r=20,y=28)
双边滤波(r=20)
可以看出,表面模糊的效果要比双边滤波效果更好一些!
图像的表面模糊处理,其作用是在保留图像边缘的情况下,对图像的表面进行模糊处理。在对人物皮肤处理上,比高斯模糊更有效。因为高斯模糊在使人物皮肤光洁的同时,也将一些边缘特征如脸部的眉毛、嘴唇等给模糊了,不得不用蒙版小心的抹去这些地方的模糊部分。
在处理手法上,表面模糊也与其它卷积处理手段不同,如高斯模糊等在处理图像时都是采用统一的卷积矩阵进行,而表面模糊却是每一个像素点都有自己的卷积矩阵,而且还是3(4)套,用以对应于像素的R、G、B(A、R、G、B)分量。所以表面模糊在编程处理时,比其它卷积操作更复杂、更耗时,因为它要对每一个像素计算自己的卷积矩阵。表面模糊编程的难点也在计算卷积矩阵上,其它与一般图像卷积处理一样。
表面模糊处理有2个参数,即模糊半径和模糊阈值,前者确定模糊的范围,后者确定模糊的程度。模糊范围就是卷积矩阵大小,如模糊半径为1,则模糊矩阵直径为
1×2+1=3
,矩阵元素个数为
3×3=9
,矩阵的中间元素即是当前像素点。
矩阵元素值的计算公式为:
wij=1−(|Iij−I0|)2.5T
其中,
T
是阈值,
wij
是模板矩阵的元素值,也可以称为权重,
Iij
是图像值,
I0
是模板矩阵中心的图像值。一般来说,
wij
会做一个预处理:
wij=max(0,wij)
根据卷积运算,每一个像素通过表面模糊之后的值为:
Inew=∑wijIij∑wij
clc;
clear all;
close all;
addpath('E:\Visual Effects\PS Algorithm');
Img=imread('1.jpg');
Img=double(Img);
imshow(Img/255);
img_out=Img;
R=Img(:, :, 1);
G=Img(:, :, 2);
B=Img(:, :, 3);
radi=5;
thre=25;
img_out(:, :, 1)=Surface_Blur(R, radi, thre);
img_out(:, :, 2)=Surface_Blur(G, radi, thre);
img_out(:, :, 3)=Surface_Blur(B, radi, thre);
figure, imshow(img_out/255);
%% surface blur
function I_out=Surface_Blur(I_in, radi, thre)
[r, c]=size(I_in);
I_out=I_in;
for ii=1+radi : r-radi
for jj=1+radi : c-radi
patch=I_in (ii-radi:ii+radi, jj-radi:jj+radi);
p0=I_in(ii, jj);
mask_1= repmat(p0, 2*radi+1, 2*radi+1);
mask_2=1-abs(patch-mask_1)/(2.5*thre);
mask_3=max(mask_2, 0);
I_out(ii, jj)=sum(sum(patch.*mask_3))/sum(mask_3(:));
end
end
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
图像的表面模糊处理是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套卷积矩阵。
矩阵元素确定后,就可按照一般图像卷积操作进行处理了,即分别累计矩阵元素值和与之对应的像素分量值乘积,用累计像素分量值除以累计元素值,即可得到当前像素分量模糊处理后的值。
下面是表面模糊处理代码:
-
-
-
-
- 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;
-
-
- 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 ++)
- {
-
- 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;
-
- 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;
- }
-
例子运行效果截图: