图像美容之眼睛放大算法。
目前,手机上各种图像特效的软件应用App越来越盛行,比较有名如美图秀秀,camare360,美颜相机等,还有一些在某些特定的方向做的比较的优秀的如魔漫相机等。这些软件几乎无一例外的都提供了相当数量的针对人脸进行美化的功能,正是这些最基础的功能吸热了很多热血美女丑女中女的兴趣。以至于几乎我所认识的每个25-35之间的女性朋友都知道美图秀秀,而了解Photoshop的则微乎其微。不过最近听一些朋友谈到,认为手机上的图像软件已经过了开发的鼎盛期,也不晓得到底是不是这样。
作为人脸中最有神的部位眼睛,自然是各软件开发商不会错过的美化对象,拿运行速度极差的可牛影像为例,他至少提供了眼睛放大、去红眼、祛黑眼圈、眼睛变色、加眼影等众多和眼睛有关的美容刀。其实这些,在程序开发者开来不过一些美妙的代码发言后绽放多多鲜花,可确是众多人为之用之美容的神器。
但是,这些简单的功能,送之于百度或这个google搜索相关算法,能找到想匹配的信息量真的不多,最起码我没找到可以直接用之于实践的东西。
本文就共享一些我关于眼睛缩放这个问题的一些研究成果,这些研究是3年前进行的。
正规的来说,眼睛缩放属于图像的一种局部扭曲,关于扭曲,Photoshop中的液化滤镜最能体现这类算法的灵魂。眼睛缩放完全可以用其中的一种方式来实现,如下图所示:
(为节省篇幅,旋转了下)
使用该膨胀工具,选择合适的参数能得到非常理想的效果,但是如果仅仅为了这个功能区研究庞大的液化滤镜的算法,是极其需要勇气的精力的。虽然目前在开源内的软件中可以从GIMP的Iwarp代码或者paint.net的一个smudgle插件通过反编译的方式得到代码参考学习,但是这个过程是比较痛苦的,因为我因为痛苦过一次了,这里分享一个简单的方式去实现他,就减轻大家的痛苦了吧。
在手动的眼睛缩放过程中,一般有三个参数:中心点、画笔大小、力度,我们分别用(PointX,PointY),Radius,Strength表示。比如美图提供了如下的用户界面:
中心点是用户通过鼠标单击获得的,画笔的大小决定了算法所影响到的范围。力度影响眼睛放大的程度。
我的算法是通过如下图所示的简单过程实现的。
对于画笔半径以内的任一位置像素(X,Y),按照其所在的位置和半径之间的比值,根据强度的设定值按照某个线性的公式,从经过点(PointX,PointY)和(X,Y)的直线中选择一个位置像素作为新的像素值,如果这个新的位置位于两点之间(图中X1,Y1),则出现眼睛缩小的效果,而位于直线的延长线上(图中的X2,Y2),则会有眼睛放大的效果,用一段简单的代码表示就是:
Left = IIf(PointX - Radius < 0, 0, PointX - Radius) ' 计算边界值 Top = IIf(PointY - Radius < 0, 0, PointY - Radius) Right = IIf(PointX + Radius >= m_Width, m_Width - 1, PointX + Radius) Bottom = IIf(PointY + Radius >= m_Height, m_Height - 1, PointY + Radius) PowRadius = Radius * Radius For Y = Top To Bottom OffSetY = Y - PointY For X = Left To Right OffsetX = X - PointX XY = OffsetX * OffsetX + OffSetY * OffSetY ' 距离的平方 If XY <= PowRadius Then ' 在指定的半径内 ScaleFactor = 1 - XY / PowRadius ScaleFactor = 1 - Strength / 100 * ScaleFactor ' 按照这种关系计算取样点的位置 PosX = OffsetX * ScaleFactor + PointX PosY = OffSetY * ScaleFactor + PointY If PosX < 0 Then ' 放置越界 PosX = 0 ElseIf PosX >= m_Width Then PosX = m_Width - 1 End If If PosY < 0 Then PosY = 0 ElseIf PosY >= m_Height Then PosY = m_Height - 1 End If Speed = Y * m_Stride + X * 3 Index = PosY * m_Stride + PosX * 3 ImageData(Speed) = ImageDataB(Index) '将当前点的值赋值为取样点的值 ImageData(Speed + 1) = ImageDataB(Index + 1) ImageData(Speed + 2) = ImageDataB(Index + 2) End If Next Next
我没有提供的完整的代码,是因为真正懂的人只要从只言片语就能搞清楚,而那些伸手党在我这里是不收欢迎的。
上述代码有很多可以改进的地方,第一,上述出现的结果会有些不清晰,只是 由于计算的取样的坐标实际上是浮点数,因此直接取整会带来较大的误差,较为合理的做法是利用双线性插值之类的插值算法进行计算优化。第二:线性映射的计算公式也可以做调整,比如调整成那种离眼珠越进的那些像素的变化越小,而越远的变换越大等。
效果如下图:
按照上述细路写了个测试程序:https://files.cnblogs.com/Imageshop/ZoomEyes.rar
****************************作者: laviewpbt 时间: 2014.7.15 联系QQ: 33184777 转载请保留本行信息**********************