说起红眼算法,这个话题非常古老了。
百度百科上的描述:
“红眼”一般是指在人物摄影时,当闪光灯照射到人眼的时候,瞳孔放大而产生的视网膜泛红现象。
由于红眼现象的程度是根据拍摄对象色素的深浅决定的,如果拍摄对象的眼睛颜色较深,红眼现象便不会特别明显。
“红眼”也指传染性结膜炎。
近些年好像没有看到摄影会出现这样的情况,毕竟科技发展迅速。
记得最早看到红眼移除算法是在ACDSee 这个看图软件的编辑功能区。
当然,当时ACDSee 也没有能力做到自动去红眼,也需要进行手工操作。
红眼移除不难,其实就是把眼睛区域的颜色修正一下。
但是难就难在修复之后,不要显得太过突兀,或者破坏眼睛周围的颜色 。
这就有点难办了。
当然其实最简单的思路,就是转色域空间处理后再转回RGB。
记得在2015年的时候,
曾经一度想要寻找红眼移除过度自然的算法思路,
当时仅仅是好奇,想要学习之。
直到2016年,在一个Delphi 图像控件的源码里看到了一个红颜移除算法函数。
把代码转写成C之后验证了一下,效果不错,过度很自然。
貌似好像有点暴露年龄了,
俺也曾经是Delphi程序员来的,无比怀念Delphi7。
贴上红眼算法的Delphi源码:
procedure _IERemoveRedEyes(bitmap: TIEBitmap; fSelx1, fSely1, fSelx2, fSely2: integer; fOnProgress: TIEProgressEvent; Sender: TObject); var row, col: integer; nrv, bluf, redq, powr, powb, powg: double; per1: double; px: PRGB; begin fSelX2 := imin(fSelX2, bitmap.Width); dec(fSelX2); fSelY2 := imin(fSelY2, bitmap.Height); dec(fSelY2); per1 := 100 / (fSelY2 - fSelY1 + 0.5); for row := fSelY1 to fSelY2 do begin px := bitmap.Scanline[row]; for col := fSelX1 to fSelX2 do begin nrv := px^.g + px^.b; if nrv < 1 then nrv := 1; if px^.g > 1 then bluf := px^.b / px^.g else bluf := px^.b; bluf := dMax(0.5, dMin(1.5, Sqrt(bluf))); redq := (px^.r / nrv) * bluf; if redq > 0.7 then begin powr := 1.775 - (redq * 0.75 + 0.25); if powr < 0 then powr := 0; powr := powr * powr; powb := 1 - (1 - powr) / 2; powg := 1 - (1 - powr) / 4; with px^ do begin r := Round(powr * r); b := Round(powb * b); g := Round(powg * g); end; end; inc(px); end; if assigned(fOnProgress) then fOnProgress(Sender, trunc(per1 * (row - fSelY1 + 1))); Application.ProcessMessages; end; end;
非常非常简单的代码。
但是思路很巧妙。
不多说,各位看官自己品味一下。
先上个效果图:
说明下本文背景前提:
人脸识别暂时采用MTCNN,示例不考虑判断是否存在红眼。
人脸检测部分,详情见博文《MTCNN人脸检测 附完整C++代码》
算法步骤:
检测人脸,对齐得到人脸五个特征点。
算出两眼球之间的距离,
估算眼球的大概大小,
(示例代码采用 两眼球之间的距离的九分之一)
计算相应的半径,
按圆形修复眼球颜色即可。
完整示例代码献上:
#include "mtcnn.h" #include "browse.h" #define USE_SHELL_OPEN #ifndef nullptr #define nullptr 0 #endif #if defined(_MSC_VER) #define _CRT_SECURE_NO_WARNINGS #include <windows.h> #else #include <unistd.h> #endif #define STB_IMAGE_STATIC #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" //ref:https://github.com/nothings/stb/blob/master/stb_image.h #define TJE_IMPLEMENTATION #include "tiny_jpeg.h" //ref:https://github.com/serge-rgb/TinyJPEG/blob/master/tiny_jpeg.h #include <stdint.h> #include "timing.h" char saveFile[1024]; unsigned char *loadImage(const char *filename, int *Width, int *Height, int *Channels) { return stbi_load(filename, Width, Height, Channels, 0); } void saveImage(const char *filename, int Width, int Height, int Channels, unsigned char *Output) { memcpy(saveFile + strlen(saveFile), filename, strlen(filename)); *(saveFile + strlen(saveFile) + 1) = 0; //保存为jpg if (!tje_encode_to_file(saveFile, Width, Height, Channels, true, Output)) { fprintf(stderr, "save JPEG fail. "); return; } #ifdef USE_SHELL_OPEN browse(saveFile); #endif } void splitpath(const char *path, char *drv, char *dir, char *name, char *ext) { const char *end; const char *p; const char *s; if (path[0] && path[1] == ':') { if (drv) { *drv++ = *path++; *drv++ = *path++; *drv = '