怀旧滤波

来源:互联网 发布:怎么在淘宝买电棍 编辑:程序博客网 时间:2024/04/25 17:32
自己编码使用去色、曲线、色阶算法实现照片怀旧特效。

  天下文章一大抄,看你会抄不会抄,这个算法的初步雏形其实很简单,很多傻瓜级的软件业提供了相应的一键功能,比如美图秀秀。其实这就是个简单的调色功能,实现的方式五花八门,没有一个固定的标准,我们下面仅以几个开源的软件中的算法为例来说明实现过程。

      第一的参考算法是来自Paint.net的,在专业的软件中,这个功能的英文一般称之为Sepia,在Paint.net中,这个算法实现的主要代码如下:

复制代码
this.desaturate = new UnaryPixelOps.Desaturate();this.levels = new UnaryPixelOps.Level(                ColorBgra.Black,                 ColorBgra.White,                new float[] { 1.2f, 1.0f, 0.8f },                ColorBgra.Black,                ColorBgra.White);
复制代码

      即先去色,然后再执行色阶命令,似乎很简单,而我们先看看效果。

   

   

                       原图                        Paint.net的效果                    美图怀旧特效的结果

  从效果上比较接近的,这里和美图比较,并不是说美图就是行业标准,只是做个参照而已,我这里主要说说Paint.net这个代码的简易实现(并不是照抄其代码,实际上我根本没看Paint.net具体的实现代码,而只看其实现的思路)。

     第一步是个去色,去色的算法有N多种,我们这里以业界老大Adobe Photoshop提供的算法为标准实现,主要C++代码如下:

复制代码
void __stdcall Desaturate(unsigned char * Src , int Width, int Height ,int Stride ){    int X,Y, Max, Min, Value;    unsigned char * Pointer;    for (Y = 0; Y < Height; Y++)    {        Pointer = Src + Y * Stride;        for (X = 0; X < Width; X++)        {            if (*Pointer >= *(Pointer + 1))     //取R/G/B各分量的最大和最小值的平均值            {                Max = *Pointer;                Min = *(Pointer + 1);            }            else            {                Max = *(Pointer + 1);                Min = *Pointer;            }            if (*(Pointer + 2) > Max)                Max = *(Pointer + 2);            else if (*(Pointer + 2) < Min)                Min = *(Pointer + 2);            Value =( Max + Min )>>1;            *Pointer++ =Value;            *Pointer++ =Value;            *Pointer++ =Value;        }    }}
复制代码

   可见PS的去色算法的原理性实现还是很简单的,就是取RGB通道最大值和最小值的平均值,这相当于在色相饱和度效果中的饱和度取0的效果。

     所谓色阶指令,别看PS的Level界面做的很复杂,有N多输入参数,其实内部也没啥复杂的技术,简单的讲就是通过哪些参数计算出一个隐射表,最终都是通过Curve指令来实现的,所以在GIMP下这两个指令的参数可以在不同界面之间相互转换。下面先给出通过那些参数计算隐射表的过程:

复制代码
void  GetLevelTable(unsigned char * Table, unsigned char InputLeftLimit, unsigned char InputMiddle, unsigned char InputRightLimit, unsigned char OutputLeftLimit , unsigned char OutputRightLimit){    if (InputLeftLimit > 253) InputLeftLimit = 253;    if (InputLeftLimit < 0)    InputLeftLimit = 0;    if (InputRightLimit > 255)InputRightLimit = 255;    if (InputRightLimit < 2) InputRightLimit = 2;    if (InputMiddle > 254)InputMiddle = 254;    if (InputMiddle < 1)InputMiddle = 1;    if (InputMiddle > InputRightLimit)InputMiddle = InputRightLimit - 1;    if (InputMiddle < InputLeftLimit)InputMiddle = InputLeftLimit + 1;    if (OutputLeftLimit < 0)OutputLeftLimit = 0;    if (OutputLeftLimit > 255)OutputLeftLimit = 255;    if (OutputRightLimit < 0)OutputRightLimit = 0;    if (OutputRightLimit > 255)OutputRightLimit = 255;    for (int Index = 0; Index <= 255; Index++)    {        double Temp = Index - InputLeftLimit;        if (Temp < 0)         {            Temp = OutputLeftLimit;        }        else if (Temp + InputLeftLimit > InputRightLimit)        {            Temp = OutputRightLimit;        }        else         {            double Gamma = log(0.5) / log((double)(InputMiddle - InputLeftLimit) / (InputRightLimit - InputLeftLimit));            Temp = OutputLeftLimit + (OutputRightLimit - OutputLeftLimit) * pow((Temp / (InputRightLimit - InputLeftLimit)), Gamma);        }        if (Temp > 255)            Temp = 255;        else if (Temp < 0)            Temp = 0;        Table[Index] = Temp;    }}
复制代码

  我们先贴下PS的Level界面:

     

  我想稍微懂点英语的人对理解上面代码中的参数和这个界面中的那些位置的标签对应应该都没有问题,如果有,请回到初中课堂。 

     这里重点关注下上图中有提示文字“调整中间调输入色阶"一项,PS的这一栏的输入范围是9.9-0.01,而我上述代码中对应的范围是1-254,唯一的区别我觉得就是量化等级变得少了,这里的主要算法我记得是模仿的Gimp的。

     有了上述过程,只要在进行一个隐射就OK了,这部分其实就是PS的曲线功能的结果,虽然你看曲线的界面那么复杂,其实都是一些控制而已。

复制代码
void __stdcall Curve(unsigned char * Src , int Width, int Height , int Stride ,unsigned char * TableB,unsigned char * TableG,unsigned char * TableR){    int X,Y;    int ByteCount = Stride / Width;    unsigned char * Pointer;    for (Y = 0; Y < Height; Y++)    {        Pointer = Src + Y * Stride;        for (X = 0; X < Width; X++)        {            *Pointer++ = TableB[*Pointer];            *Pointer++ = TableG[*Pointer];            *Pointer++ = TableR[*Pointer];        }    }}
复制代码

  最后给出Level命令的代码:

复制代码
void __stdcall Level(unsigned char * Src , int Width, int Height ,int Stride , Channel DestChannel, unsigned char InputLeftLimit, unsigned char InputMiddle, unsigned char InputRightLimit, unsigned char OutputLeftLimit , unsigned char OutputRightLimit){    unsigned char * Table = (unsigned char *) malloc ( 256 * sizeof (unsigned char));    unsigned char * LinearTable = (unsigned char *) malloc ( 256 * sizeof (unsigned char));    for (int X=0;X<256;X++)    LinearTable[X] = X;     GetLevelTable(Table, InputLeftLimit,InputMiddle,InputRightLimit,OutputLeftLimit,OutputRightLimit);    if (DestChannel == RGB)        Curve(Src,Width,Height,Stride,Table,Table,Table);    else if (DestChannel == Blue)        Curve(Src,Width,Height,Stride,Table,LinearTable,LinearTable);    else if (DestChannel == Green)        Curve(Src,Width,Height,Stride,LinearTable,Table,LinearTable);    else if (DestChannel == Red)        Curve(Src,Width,Height,Stride,LinearTable,LinearTable,Table);    free(Table);    free(LinearTable);}
复制代码

  对应上述Paint.net的level指令,我们把我们的调用形式更给为:

Level(Dest, Width, Height, Stride, Channel.Blue, 0, 152, 255, 0, 255);Level(Dest, Width, Height, Stride, Channel.Red, 0, 101, 255, 0, 255);

  就OK了,具体的调用即效果可见后面的附件。

     第二中参考算法来自于ImageJ软件,JAVA版本的图像处理包。其所谓的Sepia的命令只能针对8位灰度图,其实这个就印证了上述的Desaturate过程,并且Sepia出现在其LookUp Tables命令组内,这也就和上述描述想对应:level指令也是一种简单的映射而已,我们这里贴出ImageJ的相关隐射表的数据:

 View Code

  你如果在网络上搜索PS+老照片效果,你会发现这其实只是处理过程中占有比例很小的一部分,但是这一部分却对最终的效果起到了铺垫的作用。也就是说,仅仅通过这一过程,只是获得老照片效果的基础,一个出色的自动老照片滤镜还需要更多的其他处理,比如老照片必然存在一些皱褶,边缘部位很有可能有磨损,图片周边一般比较偏暗等等。这些过程的实现需要较强的编码能力和创造力,在GIMP中存在一个old photo插件,可以实现较为出色的效果,并且这个是个开源的软件,有想开发此类算法的朋友应该去参考下。

    相关程序下载: http://files.cnblogs.com/Imageshop/OldPhoto.rar

0 0