天下文章一大抄,看你會抄不會抄,這個算法的初步雛形其實很簡單,很多傻瓜級的軟件業提供了相應的一鍵功能,比如美圖秀秀。其實這就是個簡單的調色功能,實現的方式五花八門,沒有一個固定的標准,我們下面僅以幾個開源的軟件中的算法為例來說明實現過程。
第一的參考算法是來自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的相關隱射表的數據:
byte[] Table = { 0,0,0, 43,28,4, 57,35,5, 65,35,6, 66,36,7, 67,37,8, 68,38,9, 69,39,10, 70,40,11, 71,41,12, 72,42,13, 73,43,14, 74,44,15, 75,45,16, 76,46,17, 77,47,18, 78,48,19, 79,49,20, 80,50,21, 81,51,22, 82,52,23, 83,53,24, 83,53,24, 84,54,25, 85,55,26, 86,56,27, 87,57,28, 88,58,29, 89,59,30, 90,60,31, 91,61,32, 92,62,33, 93,63,34, 94,64,35, 95,65,36, 96,66,37, 97,67,38, 98,68,39, 99,69,40, 100,70,41, 101,71,42, 102,72,43, 103,73,44, 104,74,45, 105,75,46, 106,76,47, 107,77,48, 107,77,48, 108,78,49, 109,79,50, 110,80,51, 111,81,52, 112,82,53, 113,83,54, 114,84,55, 115,85,56, 116,86,57, 117,87,58, 118,88,59, 119,89,60, 120,90,61, 121,91,62, 122,92,63, 123,93,64, 124,94,65, 125,95,66, 125,95,66, 126,96,67, 127,97,68, 128,98,69, 129,99,70, 130,100,71, 131,101,72, 132,102,73, 133,103,74, 134,104,75, 135,105,76, 136,106,77, 137,107,78, 138,108,79, 139,109,80, 140,110,81, 141,111,82, 142,112,83, 143,113,84, 144,114,85, 145,115,86, 146,116,87, 147,117,88, 148,118,89, 149,119,90, 150,120,91, 151,121,92, 152,122,93, 153,123,94, 154,124,95, 155,125,96, 156,126,97, 157,127,98, 158,128,99, 159,129,100, 160,130,101, 161,131,102, 162,132,103, 163,133,104, 164,134,105, 165,135,106, 166,136,107, 167,137,108, 168,138,109, 169,139,110, 170,140,111, 171,141,112, 172,142,113, 173,143,114, 174,144,115, 175,145,116, 176,146,117, 177,147,118, 178,148,119, 179,149,120, 180,150,121, 181,151,122, 182,152,123, 183,153,124, 184,154,125, 185,155,126, 186,156,127, 187,157,128, 187,157,128, 188,158,129, 189,159,130, 190,160,131, 191,161,132, 192,162,133, 193,163,134, 194,164,135, 195,165,136, 196,166,137, 197,167,138, 198,168,139, 199,169,140, 200,170,141, 201,171,142, 202,172,143, 203,173,144, 204,174,145, 205,175,146, 206,176,147, 207,177,148, 208,178,149, 209,179,150, 210,180,151, 211,181,152, 212,182,153, 213,183,154, 214,184,155, 215,185,156, 216,186,157, 217,187,158, 218,188,159, 219,189,160, 220,190,161, 221,191,162, 222,192,163, 223,193,164, 224,194,165, 225,195,166, 226,196,167, 227,197,168, 228,198,169, 229,199,170, 230,200,171, 231,201,172, 232,202,173, 233,203,174, 234,204,175, 235,205,176, 236,206,177, 237,207,178, 238,208,179, 239,209,180, 240,210,181, 241,211,182, 242,212,183, 243,213,184, 244,214,185, 245,215,186, 246,216,187, 247,217,188, 248,218,189, 249,219,190, 250,220,191, 251,221,192, 252,222,193, 253,223,194, 254,224,195, 255,225,196, 255,225,196, 255,225,196, 255,225,196, 255,225,196, 255,225,197, 255,225,197, 255,225,197, 255,225,197, 255,226,198, 255,226,198, 255,226,198, 255,226,198, 255,226,198, 255,226,199, 255,226,199, 255,226,199, 255,226,199, 255,226,199, 255,227,200, 255,227,200, 255,227,200, 255,227,200, 255,227,200, 255,227,201, 255,227,201, 255,227,201, 255,227,201, 255,227,201, 255,228,202, 255,228,202, 255,228,202, 255,228,202, 255,228,202, 255,228,202, 255,228,203, 255,228,203, 255,228,203, 255,228,203, 255,228,203, 255,229,204, 255,229,204, 255,229,204, 255,229,204, 255,229,204, 255,229,204, 255,229,205, 255,229,205, 255,229,205, 255,229,205, 255,229,205, 255,230,205, 255,230,205, 255,231,205, 255,231,209, 255,233,214, 255,233,217, 255,242,233, 255,255,255 };
你如果在網絡上搜索PS+老照片效果,你會發現這其實只是處理過程中占有比例很小的一部分,但是這一部分卻對最終的效果起到了鋪墊的作用。也就是說,僅僅通過這一過程,只是獲得老照片效果的基礎,一個出色的自動老照片濾鏡還需要更多的其他處理,比如老照片必然存在一些皺褶,邊緣部位很有可能有磨損,圖片周邊一般比較偏暗等等。這些過程的實現需要較強的編碼能力和創造力,在GIMP中存在一個old photo插件,可以實現較為出色的效果,並且這個是個開源的軟件,有想開發此類算法的朋友應該去參考下。
相關程序下載: http://files.cnblogs.com/Imageshop/OldPhoto.rar

*********************************作者: laviewpbt 時間: 2013.12.6 聯系QQ: 33184777 轉載請保留本行信息************************
