当前位置: 首页 > 编程日记 > 正文

自动红眼移除算法 附c++完整代码

说起红眼算法,这个话题非常古老了。

百度百科上的描述:

“红眼”一般是指在人物摄影时,当闪光灯照射到人眼的时候,瞳孔放大而产生的视网膜泛红现象。

由于红眼现象的程度是根据拍摄对象色素的深浅决定的,如果拍摄对象的眼睛颜色较深,红眼现象便不会特别明显。

“红眼”也指传染性结膜炎。

近些年好像没有看到摄影会出现这样的情况,毕竟科技发展迅速。

记得最早看到红眼移除算法是在ACDSee 这个看图软件的编辑功能区。

当然,当时ACDSee 也没有能力做到自动去红眼,也需要进行手工操作。

红眼移除不难,其实就是把眼睛区域的颜色修正一下。

但是难就难在修复之后,不要显得太过突兀,或者破坏眼睛周围的颜色 。

这就有点难办了。

当然其实最简单的思路,就是转色域空间处理后再转回RGB。

记得在2015年的时候,

曾经一度想要寻找红眼移除过度自然的算法思路,

当时仅仅是好奇,想要学习之。

直到2016年,在一个Delphi 图像控件的源码里看到了一个红颜移除算法函数。

把代码转写成C之后验证了一下,效果不错,过度很自然。

貌似好像有点暴露年龄了,

俺也曾经是Delphi程序员来的,无比怀念Delphi7。

贴上红眼算法的Delphi源码:

procedure _IERemoveRedEyes(bitmap: TIEBitmap; fSelx1, fSely1, fSelx2, fSely2: integer; fOnProgress: TIEProgressEvent; Sender: TObject);
varrow, col: integer;nrv, bluf, redq, powr, powb, powg: double;per1: double;px: PRGB;
beginfSelX2 := imin(fSelX2, bitmap.Width);  dec(fSelX2);fSelY2 := imin(fSelY2, bitmap.Height); dec(fSelY2);per1 := 100 / (fSelY2 - fSelY1 + 0.5);for row := fSelY1 to fSelY2 dobeginpx := bitmap.Scanline[row];for col := fSelX1 to fSelX2 dobeginnrv := px^.g + px^.b;if nrv < 1 thennrv := 1;if px^.g > 1 thenbluf := px^.b / px^.gelsebluf := px^.b;bluf := dMax(0.5, dMin(1.5, Sqrt(bluf)));redq := (px^.r / nrv) * bluf;if redq > 0.7 thenbeginpowr := 1.775 - (redq * 0.75 + 0.25);if powr < 0 thenpowr := 0;powr := powr * powr;powb := 1 - (1 - powr) / 2;powg := 1 - (1 - powr) / 4;with px^ dobeginr := Round(powr * r);b := Round(powb * b);g := Round(powg * g);end;end;inc(px);end;if assigned(fOnProgress) thenfOnProgress(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;//保存为jpgif (!tje_encode_to_file(saveFile, Width, Height, Channels, true, Output)) {fprintf(stderr, "save JPEG fail.\n");return;}#ifdef USE_SHELL_OPENbrowse(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 = '\0';}}else if (drv)*drv = '\0';for (end = path; *end && *end != ':';)end++;for (p = end; p > path && *--p != '\\' && *p != '/';)if (*p == '.') {end = p;break;}if (ext)for (s = end; (*ext = *s++);)ext++;for (p = end; p > path;)if (*--p == '\\' || *p == '/') {p++;break;}if (name) {for (s = p; s < end;)*name++ = *s++;*name = '\0';}if (dir) {for (s = path; s < p;)*dir++ = *s++;*dir = '\0';}
}void getCurrentFilePath(const char *filePath, char *saveFile) {char drive[_MAX_DRIVE];char dir[_MAX_DIR];char fname[_MAX_FNAME];char ext[_MAX_EXT];splitpath(filePath, drive, dir, fname, ext);size_t n = strlen(filePath);memcpy(saveFile, filePath, n);char *cur_saveFile = saveFile + (n - strlen(ext));cur_saveFile[0] = '_';cur_saveFile[1] = 0;
}void drawPoint(unsigned char *bits, int width, int depth, int x, int y, const uint8_t *color) {for (int i = 0; i < min(depth, 3); ++i) {bits[(y * width + x) * depth + i] = color[i];}
}void drawLine(unsigned char *bits, int width, int depth, int startX, int startY, int endX, int endY,const uint8_t *col) {if (endX == startX) {if (startY > endY) {int a = startY;startY = endY;endY = a;}for (int y = startY; y <= endY; y++) {drawPoint(bits, width, depth, startX, y, col);}}else {float m = 1.0f * (endY - startY) / (endX - startX);int y = 0;if (startX > endX) {int a = startX;startX = endX;endX = a;}for (int x = startX; x <= endX; x++) {y = (int)(m * (x - startX) + startY);drawPoint(bits, width, depth, x, y, col);}}
}void drawRectangle(unsigned char *bits, int width, int depth, int x1, int y1, int x2, int y2, const uint8_t *col) {drawLine(bits, width, depth, x1, y1, x2, y1, col);drawLine(bits, width, depth, x2, y1, x2, y2, col);drawLine(bits, width, depth, x2, y2, x1, y2, col);drawLine(bits, width, depth, x1, y2, x1, y1, col);
}#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a): (b))
#endif
#ifndef MIN
#define MIN(a, b) (((a) > (b)) ? (b): (a))
#endifunsigned char ClampToByte(int Value) {return ((Value | ((signed int) (255 - Value) >> 31)) & ~((signed int) Value >> 31));
}int Clamp(int Value, int Min, int Max) {if (Value < Min)return Min;else if (Value > Max)return Max;elsereturn Value;
}void RemoveRedEyes(unsigned char *input, unsigned char *output, int width, int height, int depth, int CenterX, int CenterY,int Radius) {if (depth < 3) return;if ((input == nullptr) || (output == nullptr)) return;if ((width <= 0) || (height <= 0)) return;int Left = Clamp(CenterX - Radius, 0, width);int Top = Clamp(CenterY - Radius, 0, height);int Right = Clamp(CenterX + Radius, 0, width);int Bottom = Clamp(CenterY + Radius, 0, height);int PowRadius = Radius * Radius;for (int Y = Top; Y < Bottom; Y++) {unsigned char *in_scanline = input + Y * width * depth + Left * depth;unsigned char *out_scanline = output + Y * width * depth + Left * depth;int OffsetY = Y - CenterY;for (int X = Left; X < Right; X++) {int OffsetX = X - CenterX;int dis = OffsetX * OffsetX + OffsetY * OffsetY;if (dis <= PowRadius) {float bluf = 0;int Red = in_scanline[0];int Green = in_scanline[1];int Blue = in_scanline[2];int nrv = Blue + Green;if (nrv < 1) nrv = 1;if (Green > 1)bluf = (float) Blue / Green;elsebluf = (float) Blue;bluf = MAX(0.5f, MIN(1.5f, sqrt(bluf)));float redq = (float) Red / nrv * bluf;if (redq > 0.7f) {float powr = 1.775f - (redq * 0.75f +0.25f);if (powr < 0) powr = 0;powr = powr * powr;float powb = 0.5f + powr * 0.5f;float powg = 0.75f + powr * 0.25f;out_scanline[0] = ClampToByte(powr * Red + 0.5f);out_scanline[1] = ClampToByte(powg * Green + 0.5f);out_scanline[2] = ClampToByte(powb * Blue + 0.5f);}}in_scanline += depth;out_scanline += depth;}}
}int main(int argc, char **argv) {printf("mtcnn face detection\n");printf("blog:http://cpuimage.cnblogs.com/\n");if (argc < 2) {printf("usage: %s  model_path image_file \n ", argv[0]);printf("eg: %s  ../models ../sample.jpg \n ", argv[0]);printf("press any key to exit. \n");getchar();return 0;}const char *model_path = argv[1];char *szfile = argv[2];getCurrentFilePath(szfile, saveFile);int Width = 0;int Height = 0;int Channels = 0;unsigned char *inputImage = loadImage(szfile, &Width, &Height, &Channels);if (inputImage == nullptr || Channels != 3) return -1;ncnn::Mat ncnn_img = ncnn::Mat::from_pixels(inputImage, ncnn::Mat::PIXEL_RGB, Width, Height);std::vector<Bbox> finalBbox;MTCNN mtcnn(model_path);double startTime = now();mtcnn.detect(ncnn_img, finalBbox);double nDetectTime = calcElapsed(startTime, now());printf("time: %d ms.\n ", (int)(nDetectTime * 1000));int num_box = finalBbox.size();printf("face num: %u \n", num_box);bool draw_face_feat = false;for (int i = 0; i < num_box; i++) {if (draw_face_feat) {const uint8_t red[3] = {255, 0, 0};drawRectangle(inputImage, Width, Channels, finalBbox[i].x1, finalBbox[i].y1,finalBbox[i].x2,finalBbox[i].y2, red);const uint8_t blue[3] = {0, 0, 255};for (int num = 0; num < 5; num++) {drawPoint(inputImage, Width, Channels, (int) (finalBbox[i].ppoint[num] + 0.5f),(int) (finalBbox[i].ppoint[num + 5] + 0.5f), blue);}}int left_eye_x = (int) (finalBbox[i].ppoint[0] + 0.5f);int left_eye_y = (int) (finalBbox[i].ppoint[5] + 0.5f);int right_eye_x = (int) (finalBbox[i].ppoint[1] + 0.5f);int right_eye_y = (int) (finalBbox[i].ppoint[6] + 0.5f);int dis_eye = (int) sqrtf((right_eye_x - left_eye_x) * (right_eye_x - left_eye_x) +(right_eye_y - left_eye_y) * (right_eye_y - left_eye_y));int radius = MAX(1, dis_eye / 9);RemoveRedEyes(inputImage, inputImage, Width, Height, Channels, left_eye_x, left_eye_y, radius);RemoveRedEyes(inputImage, inputImage, Width, Height, Channels, right_eye_x, right_eye_y, radius);}saveImage("_done.jpg", Width, Height, Channels, inputImage);free(inputImage);printf("press any key to exit. \n");getchar();return 0;
}

算法见 RemoveRedEyes ,这个技巧可以用于类似的图片颜色处理。

要看人脸检测的结果,把draw_face_feat 改为 true 即可。

项目地址:

https://github.com/cpuimage/MTCNN

参数也很简单,

mtcnn 模型文件路径 图片路径

例如: mtcnn ../models ../sample.jpg

用cmake即可进行编译示例代码,详情见CMakeLists.txt。

若有其他相关问题或者需求也可以邮件联系俺探讨。

邮箱地址是: 
gaozhihan@vip.qq.com

转载于:https://www.cnblogs.com/cpuimage/p/9000203.html

相关文章:

【Dlib】在GPU环境中运行dlib中的例子dnn_mmod_ex报错...dlib::cuda_error...Error while calling cudaMalloc...

1、问题描述 在GPU环境下运行dlib中的例子dnn_mmod_ex时&#xff0c;报错&#xff1a; terminate called after throwing an instance of dlib::cuda_errorwhat(): Error while calling cudaMalloc(&data, new_size*sizeof(float)) in file /home/laoer/tools/dlib/dlib…

Exchange 2010正式发布了

2009年11月9号&#xff0c;Exchange 2010正式发布了&#xff0c;下载地址&#xff1a;http://www.microsoft.com/downloa ... 0-879f-d74208d6171d简体中文64位120天试用版转载于:https://blog.51cto.com/287416363/657202

【python】使用python脚本将CelebA中图片按照 list_attr_celeba.txt 中属性处理(删除、复制、移动)

1、目的 CelebA中的照片有四十种属性&#xff0c;参见&#xff1a; 【AI】CelebA数据介绍、下载及说明 根据需求从celebA中获取我们想要的图片&#xff0c;方法是将CelebA中图片按照 list_attr_celeba.txt 中属性执行删除、复制或移动操作。 命令格式&#xff1a; python3 C…

firefly 编译opencv3.3.1, CMake报错

更换gcc编译器可以解决 -D CMAKE_C_COMPILER/usr/bin/gcc-4.8转载于:https://www.cnblogs.com/gabrialrx/p/9001554.html

AI时代,为何机器人公司无法盈利只能走向倒闭?

点击上方↑↑↑蓝字关注我们~「2019 Python开发者日」&#xff0c;购票请扫码咨询 ↑↑↑作者 | Bram Vanderborght译者 | 弯月责编 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;导语&#xff1a;机器人专家需要公开诚实地讨论我们的成功&#xff0c;而不…

Google Objective-C Style Guide

看题目就知道了&#xff5e;哪天有空翻译成中文的&#xff5e;不多说了&#xff5e;上链接&#xff5e;Google Objective-C Style Guide转载于:https://blog.51cto.com/lulala/659124

ICPC 2019国际大学生程序设计竞赛,中国高校未能夺冠

点击上方↑↑↑蓝字关注我们~「2019 Python开发者日」&#xff0c;购票请扫码咨询 ↑↑↑整理 | 琥珀出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;一年一度的国际大学生程序设计竞赛&#xff08;International Collegiate Programming Contest&#xff0c;ICPC&am…

完爆Facebook/GraphQL,APIJSON全方位对比解析(一)-基础功能

相关阅读&#xff1a; 完爆Facebook/GraphQL&#xff0c;APIJSON全方位对比解析(二)-权限控制 完爆Facebook/GraphQL&#xff0c;APIJSON全方位对比解析(三)-表关联查询 自APIJSON发布以来&#xff0c;不断有网友拿来和Facebook的GraphQL对比&#xff0c; 甚至有不少人声称“完…

【AI】吴恩达斯坦福机器学习中文笔记汇总

1、吴恩达机器学习和深度学习课程的字幕翻译以及笔记整理参见&#xff1a; 以黄海广博士为首的一群机器学习爱好者发起的公益性质项目&#xff08;http://www.ai-start.com&#xff09;。 2、黄海广博士公益项目介绍 https://www.jianshu.com/p/16a749e332db 3、吴恩达 斯坦…

【C++】C++命名空间重定向

参见博客&#xff1a; namespace使用总结 命名空间的重定向的格式&#xff1a; namespace newName oldName; 在caffe源码走读时&#xff0c;遇到namespace的重定向用法&#xff0c;以前没有用过&#xff0c;源码如下&#xff0c;其中 GFLAGS_GFLAGS_H_是为了检测gflags的版…

宝宝都能看懂的机器学习世界

点击上方↑↑↑蓝字关注我们~「2019 Python开发者日」&#xff0c;购票请扫码咨询 ↑↑↑作者 | 武博士、宋知达、袁雪瑶、聂文韬来源 | 大鱼AI&#xff08;ID&#xff1a;DayuAI-Founder&#xff09;人类需要经过各式各样的学习才有办法认识这个世界。 当小朋友第一次看到猫后…

iOS LLDB调试命令(Low Lever Debug)

断点 设置断点 $breakpoint set -n XXX set 是子命令 -n 是选项 是--name 的缩写!查看断点列表 $breakpoint list删除 $breakpoint delete 组号禁用/启用 $breakpoint disable 禁用 $breakpoint enable 启用遍历整个项目中满足Game:这个字符的所有方法 $breakpoint set -r Game…

TCP通信速率与延时关系

刚刚研究了链路延时对TCP速率的影响&#xff0c;占位&#xff0c;有空会写一下。转载于:https://blog.51cto.com/csnas/659983

十三、序列化和反序列化(部分转载)

json和pickle序列化和反序列化json是用来实现不同程序之间的文件交互&#xff0c;由于不同程序之间需要进行文件信息交互&#xff0c;由于用python写的代码可能要与其他语言写的代码进行数据传输&#xff0c;json支持所有程序之间的交互&#xff0c;json将取代XML&#xff0c;由…

【C++】google gflags详解

参考博客&#xff1b;https://blog.csdn.net/lezardfu/article/details/23753741 0、简介 gflags是google的一个开源的处理命令行参数的库&#xff0c;使用c开发&#xff0c;具备python接口&#xff0c;可以替代getopt。gflags使用起来比getopt方便&#xff0c;但是不支持参数…

何恺明等人新作:效果超ResNet,利用NAS方法设计随机连接网络 | 技术头条

点击上方↑↑↑蓝字关注我们~「2019 Python开发者日」&#xff0c;购票请扫码咨询 ↑↑↑译者 | 刘畅编辑 | 一一出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;受人工设计的启发&#xff0c;用于图像识别的神经网络从简单的链状模型发展为具有多个分支的网络。ResN…

网络规划设计师考试命题模式持续在变   你变不变

命题模式持续在变 你变不变深入分析2009下半年&#xff5e;2010下半年3次网络规划设计师考试试卷中项目管理模块&#xff08;约有5~8题&#xff0c;约占总分数的6.67%~10.67%&#xff09;的命题规律&#xff0c;心中最强烈的一份感觉体现在一个字——变。“变”是事物持续发…

【C++】google glog详解

0、简介 glog是google的日志管理系统&#xff0c;配合gflags库&#xff0c;通过命令行参数管理日志。 源码下载&#xff1a;https://github.com/google/glog ubuntu安装&#xff1a; sudo apt-get install libgoogle-glog*参考博客&#xff1a; https://blog.csdn.net/jcjc91…

仿抖音注册界面制作

话说上次完成了仿抖音我的界面制作之后&#xff0c;今天抽空又把注册界面给做了&#xff0c;还是做了些小改动&#xff0c;将第三方登录去掉了 注册还是老规矩直接奉上psd源码&#xff1a;仿抖音注册界面psd源码 个人博客https://myml666.github.io

00后的AI开发者进阶之道:从入门到鏖战MIT编程大赛 | 人物志

点击上方↑↑↑蓝字关注我们~「2019 Python开发者日」&#xff0c;购票请扫码咨询 ↑↑↑作者 | 若名出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;当所有中国的高中生都在拼命为跨过高考这道窄门疲惫不堪时&#xff0c;美国的准高中毕业生们也开始申请大学&#x…

js的全部替换函数replaceAll

JS替换功能函数&#xff0c;用正则表达式解决&#xff0c;js的全部替换&#xff0c;学习js的朋友可以参考下。 alert("abacacf".replace(a,9)); alert("abacacf".replace(/a/g,9)); 第一个运行的结果 9bacaf 这个只是替换了第一个 第二个运行的结果 9b9c9f…

【C++】Google Protocol Buffer(protobuf)详解(一)

1、简介 Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准&#xff0c; Protocol Buffers 是一种轻便高效的结构化数据存储格式&#xff0c;可以用于结构化数据串行化&#xff0c;或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通…

python使用difflib对比文件示例

使用difflib模块对比文件内容1 示例&#xff1a;字符串差异对比 vim duibi.py#!/usr/bin/env python # -*- coding: utf-8 -*- import re import os import difflib tex1"""tex1: this is a test for difflib ,just try to get difference of the log 现在试试…

技术大佬们都推荐的vim学习指南来了,值得收藏!

「2019 Python开发者日」全日程揭晓&#xff0c;请扫码咨询 ↑↑↑作者 | kbsc13&#xff0c;京东算法工程师&#xff0c;研究领域计算机视觉来源 | 机器学习与计算机视觉&#xff08;ID&#xff1a;AI_Developer&#xff09;编辑 | Jane【导语】在 Linux 下最常使用的文本编辑…

NoSQl分类

2019独角兽企业重金招聘Python工程师标准>>> http://nosql-database.org/ nosql简单分类 类型 部分代表 特点 列存储 hbase cassandra hypertable 顾名思义&#xff0c;是按列存储数据的。最大的特点是方便存储结构化和半结构化数据&#xff0c;方便做数据压缩&…

手撕代码之七大常用排序算法 | 附完整代码

点击上方↑↑↑蓝字关注我们~「2019 Python开发者日」全日程揭晓&#xff0c;请扫码咨询 ↑↑↑0.导语本节为手撕代码系列之第一弹&#xff0c;主要来手撕排序算法&#xff0c;主要包括以下几大排序算法&#xff1a;直接插入排序冒泡排序选择排序快速排序希尔排序堆排序归并排序…

【C++】google gtest 详解

1、参考博客&#xff1b; https://blog.csdn.net/baijiwei/article/details/81265491 https://www.cnblogs.com/coderzh/archive/2009/04/06/1426755.html 2、编译和安装 $ git clone https://github.com/google/googletest.git $ cd googletest $ mkdir mybuild $ cd mybui…

JS学习笔记之call、apply的用法

1、call和apply的区别 call和apply唯一的区别是传入参数的形式不同。 apply接受两个参数&#xff0c;第一个参数指定了函数体内this对象的指向&#xff0c;第二个参数为一个带下标的集合&#xff0c;可以是数组&#xff0c;也可以是类数组&#xff0c;apply方法会把集合中的元素…

实验LVS+keepalived

lvs说明:目前有三种IP负载均衡技术&#xff08;VS/NAT、VS/TUN和VS/DR&#xff09;&#xff1b;八种调度算法&#xff08;rr,wrr,lc,wlc,lblc,lblcr,dh,sh&#xff09;。在调度器的实现技术中&#xff0c;IP负载均衡技术是效率最高的。在已有的IP负载均衡技术中有通过网络地址转…

Spark Streaming笔记整理(二):案例、SSC、数据源与自定义Receiver

[TOC] 实时WordCount案例 主要是监听网络端口中的数据&#xff0c;并实时进行wc的计算。 Java版 测试代码如下&#xff1a; package cn.xpleaf.bigdata.spark.java.streaming.p1;import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.spark.Spar…