众所周知,

图像方面的3A算法有:

AF自动对焦(Automatic Focus)
自动对焦即调节摄像头焦距自动得到清晰的图像的过程

AE自动曝光(Automatic Exposure)
自动曝光的是为了使感光器件获得合适的曝光量

AW自动白平衡(Automatic White Balance)
白平衡的本质是使白色物体在任何光源下都显示白色

前面的文章也有提及过,在刚开始做图像算法的时候,我是先攻克的自动白平衡算法。

后来攻克自动曝光的时候,傻啦吧唧的,踩了不少坑。

我相信一定不止我一个,一开始的时候抱着对图像均衡化,

软磨硬泡,想要做出兼顾自动曝光和自动白平衡的算法。

可惜,图像均衡化去做白平衡或者自动曝光,这条路是错的。

严格意义上来说,图像均衡化是拉伸曲线,这种做法有个弊端。

它没有考虑到图像的空间信息,也就是局部信息。

当然如果是处理音频之类的算法,肯定要考虑时间信息,因为数据是时序性为主的。

而图像,明显是空间信息为主的。

所以从理论上来说,用拉伸曲线这种不具备空间信息的操作,来做空间信息处理的事情,是不科学的。

我记得这博客刚开始写的时候,好多网友问我,为什么你要写那么多图像模糊算法,

图像模糊算法好像很鸡肋啊,没什么用的吧。

这就大错特错了,因为模糊算法是图像算法中,典型的包含空间信息的全局算法。

也就是说,如果要玩好图像算法,玩好模糊算法就是标配。

本次分享的算法为《Local Color Correction using Non-Linear Masking》,是ImageShop博主,

彭兄发出来的,安利一下他的博客https://www.cnblogs.com/imageshop 。

这个文章里的算法比较简单,

主要是通过图像模糊获取局域权重信息,然后映射回图片上。

matlab代码如下:

% Read the image
A=imread('input.jpg'); % Seperate the Channels
R=A(:,:,);
G=A(:,:,);
B=A(:,:,); % Calculate Intensity Component
I=(R+G+B)/; % Invert the image
I_inverted=-I; % Apply Average Filter to obtain the Mask Image
h_average=fspecial('average',);
M=imfilter(I_inverted,h_average); % Color Correction for R channel
R_new=zeros(size(R));
[c_y, c_x,~] = size(R);
for j = :c_x
for i = :c_y
p=double(R(i,j));
q=double(M(i,j));
R_new(i,j,:)=int8(*((p/)^(^((-q)/))));
end
end % Color Correction for G channel
G_new=zeros(size(G));
[c_y, c_x,~] = size(G);
for j = :c_x
for i = :c_y
p=double(G(i,j));
q=double(M(i,j));
G_new(i,j,:)=int8(*((p/)^(^((-q)/))));
end
end % Color Correction for B channel
B_new=zeros(size(B));
[c_y, c_x,~] = size(B);
for j = :c_x
for i = :c_y
p=double(B(i,j));
q=double(M(i,j));
B_new(i,j,:)=int8(*((p/)^(^((-q)/))));
end
end % Output Image
O=zeros(size(A));
O(:,:,)=R_new;
O(:,:,)=G_new;
O(:,:,)=B_new; % Convert the double output image to uint8
O=uint8(O); % Plot the images
subplot(,,), imshow(A), title('Original Image');
subplot(,,), imshow(M), title('Mask');
subplot(,,), imshow(O), title('Output Image');

算法步骤很清晰,就不展开了。

有兴趣的同学,品读下论文吧。

论文链接直达

这个算法其实只是简单采用局部信息进行曝光调节,

但是并不能很好的适配很多图片情景。

需要进行二次改造,

例如:  白平衡,纹理处理更加自然诸如此类,之后就能更加美美哒。

师傅领进门,修行在个人。

改进的思路和方法就不展开一一细说了,

有兴趣的同学,可以考虑进一步改进。

效果图如下:

主要的算法函数实现如下:

void LocalColorCorrection(unsigned char *Input, unsigned char *Output, int Width, int Height, int Channels) {
unsigned char *Mask = (unsigned char *) malloc(Width * Height * sizeof(unsigned char));
if (Mask == NULL)
return;
unsigned char LocalLut[ * ];
for (int mask = ; mask < ; ++mask) {
unsigned char *pLocalLut = LocalLut + (mask << );
for (int pix = ; pix < ; ++pix) {
pLocalLut[pix] = ClampToByte(255.0f * powf(pix / 255.0f, powf(2.0f, (128.0f - mask) / 128.0f)));
}
}
InvertGrayscale(Input, Output, Width, Height, Channels);
int Radius = (MAX(Width, Height) / ) + ;
BoxBlurGrayscale(Output, Mask, Width, Height, Radius);
for (int Y = ; Y < Height; Y++) {
unsigned char *pOutput = Output + (Y * Width * Channels);
unsigned char *pInput = Input + (Y * Width * Channels);
unsigned char *pMask = Mask + (Y * Width);
for (int X = ; X < Width; X++) {
unsigned char *pLocalLut = LocalLut + (pMask[X] << );
for (int C = ; C < Channels; C++) {
pOutput[C] = pLocalLut[pInput[C]];
}
pOutput += Channels;
pInput += Channels;
}
}
free(Mask);
}

做了一些算法性能上的优化,720P,1080P下实时没半点问题。

至于进一步优化性能和效果,就留待下回分解,

当然有没有下回,得看心情。

附完整C代码:

/**
*implmentation of Local Color Correction using Non-Linear Masking published by Nathan Moroney Hewlett-Packard Laboratories, Palo Alto, California.
**/
#include "browse.h" #define USE_SHELL_OPEN #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 <math.h>
#include <stdbool.h>
#include <stdio.h>
#include "timing.h"
#include <stdint.h>
#include <assert.h> #ifndef _MAX_DRIVE
#define _MAX_DRIVE 3
#endif
#ifndef _MAX_FNAME
#define _MAX_FNAME 256
#endif
#ifndef _MAX_EXT
#define _MAX_EXT 256
#endif
#ifndef _MAX_DIR
#define _MAX_DIR 256
#endif
#ifndef MIN
#define MIN(a, b) ( (a) > (b) ? (b) : (a) )
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
char saveFile[]; unsigned char *loadImage(const char *filename, int *Width, int *Height, int *Channels) {
return (stbi_load(filename, Width, Height, Channels, ));
} void saveImage(const char *filename, int Width, int Height, int Channels, unsigned char *Output) {
memcpy(saveFile + strlen(saveFile), filename, strlen(filename));
*(saveFile + strlen(saveFile) + ) = ; if (!tje_encode_to_file(saveFile, Width, Height, Channels, true, Output)) {
fprintf(stderr, "save JPEG fail.\n");
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[] && path[] == ':') {
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[] = '_';
cur_saveFile[] = ;
} int GetMirrorPos(int Length, int Pos) {
if (Pos < )
return -Pos;
else if (Pos >= Length)
return Length + Length - Pos - ;
else
return Pos;
} unsigned char ClampToByte(int Value) {
if (Value < )
return ;
else if (Value > )
return ;
else
return (unsigned char) Value;
} void FillLeftAndRight_Mirror(int *Array, int Length, int Radius) {
for (int X = ; X < Radius; X++) {
Array[X] = Array[Radius + Radius - X];
Array[Radius + Length + X] = Array[Radius + Length - X - ];
}
} int SumOfArray(const int *Array, int Length) {
int Sum = ;
for (int X = ; X < Length; X++) {
Sum += Array[X];
}
return Sum;
} void BoxBlurGrayscale(unsigned char *input, unsigned char *output, int Width, int Height, int Radius) {
if ((input == NULL) || (output == NULL)) return;
if ((Width <= ) || (Height <= ) || (Radius <= )) return;
if (Radius < ) return;
Radius = MIN(MIN(Radius, Width - ), Height - );
int SampleAmount = ( * Radius + ) * ( * Radius + );
float Inv = 1.0f / SampleAmount; int *ColValue = (int *) malloc((Width + Radius + Radius) * sizeof(int));
int *ColOffset = (int *) malloc((Height + Radius + Radius) * sizeof(int));
if ((ColValue == NULL) || (ColOffset == NULL)) {
if (ColValue != NULL) free(ColValue);
if (ColOffset != NULL) free(ColOffset);
return;
}
for (int Y = ; Y < Height + Radius + Radius; Y++)
ColOffset[Y] = GetMirrorPos(Height, Y - Radius);
{
for (int Y = ; Y < Height; Y++) {
unsigned char *scanLineOut = output + Y * Width;
if (Y == ) {
memset(ColValue + Radius, , Width * sizeof(int));
for (int Z = -Radius; Z <= Radius; Z++) {
unsigned char *scanLineIn = input + ColOffset[Z + Radius] * Width;
for (int X = ; X < Width; X++) {
ColValue[X + Radius] += scanLineIn[X];
}
}
} else {
unsigned char *RowMoveOut = input + ColOffset[Y - ] * Width;
unsigned char *RowMoveIn = input + ColOffset[Y + Radius + Radius] * Width;
for (int X = ; X < Width; X++) {
ColValue[X + Radius] -=
RowMoveOut[X] - RowMoveIn[X];
}
}
FillLeftAndRight_Mirror(ColValue, Width, Radius);
int LastSum = SumOfArray(ColValue, Radius * + );
scanLineOut[] = ClampToByte((int) (LastSum * Inv));
for (int X = + ; X < Width; X++) {
int NewSum = LastSum - ColValue[X - ] + ColValue[X + Radius + Radius];
scanLineOut[X] = ClampToByte((int) (NewSum * Inv));
LastSum = NewSum;
}
}
}
free(ColValue);
free(ColOffset);
} void InvertGrayscale(unsigned char *Input, unsigned char *Output, int Width, int Height, int Channels) {
if (Channels == ) {
for (unsigned int Y = ; Y < Height; Y++) {
unsigned char *pOutput = Output + (Y * Width);
unsigned char *pInput = Input + (Y * Width);
for (unsigned int X = ; X < Width; X++) {
pOutput[X] = (unsigned char) ( - pInput[X]);
}
}
} else {
for (unsigned int Y = ; Y < Height; Y++) {
unsigned char *pOutput = Output + (Y * Width);
unsigned char *pInput = Input + (Y * Width * Channels);
for (unsigned int X = ; X < Width; X++) {
pOutput[X] = (unsigned char) ( - ClampToByte(
( * pInput[] + * pInput[] + * pInput[]) >> ));
pInput += Channels;
}
}
}
} void LocalColorCorrection(unsigned char *Input, unsigned char *Output, int Width, int Height, int Channels) {
unsigned char *Mask = (unsigned char *) malloc(Width * Height * sizeof(unsigned char));
if (Mask == NULL)
return;
unsigned char LocalLut[ * ];
for (int mask = ; mask < ; ++mask) {
unsigned char *pLocalLut = LocalLut + (mask << );
for (int pix = ; pix < ; ++pix) {
pLocalLut[pix] = ClampToByte(255.0f * powf(pix / 255.0f, powf(2.0f, (128.0f - mask) / 128.0f)));
}
}
InvertGrayscale(Input, Output, Width, Height, Channels);
int Radius = (MAX(Width, Height) / ) + ;
BoxBlurGrayscale(Output, Mask, Width, Height, Radius);
for (int Y = ; Y < Height; Y++) {
unsigned char *pOutput = Output + (Y * Width * Channels);
unsigned char *pInput = Input + (Y * Width * Channels);
unsigned char *pMask = Mask + (Y * Width);
for (int X = ; X < Width; X++) {
unsigned char *pLocalLut = LocalLut + (pMask[X] << );
for (int C = ; C < Channels; C++) {
pOutput[C] = pLocalLut[pInput[C]];
}
pOutput += Channels;
pInput += Channels;
}
}
free(Mask);
} int main(int argc, char **argv) {
printf("Local Color Correction demo\n ");
printf("blog:http://cpuimage.cnblogs.com/ \n "); if (argc < ) {
printf("usage: %s image \n ", argv[]);
printf("eg: %s d:\\image.jpg \n ", argv[]); return ();
}
char *szfile = argv[]; getCurrentFilePath(szfile, saveFile); int Width = ;
int Height = ;
int Channels = ;
unsigned char *inputImage = NULL; double startTime = now();
inputImage = loadImage(szfile, &Width, &Height, &Channels); double nLoadTime = calcElapsed(startTime, now());
printf("load time: %d ms.\n ", (int) (nLoadTime * ));
if ((Channels != ) && (Width != ) && (Height != )) {
unsigned char *outputImg = (unsigned char *) stbi__malloc(Width * Channels * Height * sizeof(unsigned char));
if (inputImage) {
memcpy(outputImg, inputImage, (size_t) (Width * Channels * Height));
} else {
printf("load: %s fail!\n ", szfile);
}
startTime = now();
LocalColorCorrection(inputImage, outputImg, Width, Height, Channels);
double nProcessTime = calcElapsed(startTime, now()); printf("process time: %d ms.\n ", (int) (nProcessTime * )); startTime = now(); saveImage("done.jpg", Width, Height, Channels, outputImg);
double nSaveTime = calcElapsed(startTime, now()); printf("save time: %d ms.\n ", (int) (nSaveTime * )); if (outputImg) {
stbi_image_free(outputImg);
} if (inputImage) {
stbi_image_free(inputImage);
}
} else {
printf("load: %s fail!\n", szfile);
} getchar();
printf("press any key to exit. \n"); return (EXIT_SUCCESS);
}

项目地址:https://github.com/cpuimage/LocalColorCorrection

再来一个效果前后对比:

以上,权当抛砖引玉。

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

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

自动曝光修复算法 附完整C代码的更多相关文章

  1. 音频降噪算法 附完整C代码

    降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法和音频算法 都有其共通点. 图像是偏向 空间 处理,例如图片中的某个区域. 图像很多时候是以二维数据 ...

  2. mser 最大稳定极值区域(文字区域定位)算法 附完整C代码

    mser 的全称:Maximally Stable Extremal Regions 第一次听说这个算法时,是来自当时部门的一个同事, 提及到他的项目用它来做文字区域的定位,对这个算法做了一些优化. ...

  3. 基于RNN的音频降噪算法 (附完整C代码)

    前几天无意间看到一个项目rnnoise. 项目地址: https://github.com/xiph/rnnoise 基于RNN的音频降噪算法. 采用的是 GRU/LSTM 模型. 阅读下训练代码,可 ...

  4. 音频自动增益 与 静音检测 算法 附完整C代码

    前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...

  5. 音频自动增益 与 静音检测 算法 附完整C代码【转】

    转自:https://www.cnblogs.com/cpuimage/p/8908551.html 前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用 ...

  6. 基于傅里叶变换的音频重采样算法 (附完整c代码)

    前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲, ...

  7. 图片文档倾斜矫正算法 附完整c代码

    2年前在学习图像算法的时候看到一个文档倾斜矫正的算法. 也就是说能将一些文档图像进行旋转矫正, 当然这个算法一般用于一些文档扫描软件做后处理 或者用于ocr 文字识别做前处理. 相关的关键词: 抗倾斜 ...

  8. 磨皮美颜算法 附完整C代码

    前言 2017年底时候写了这篇<集 降噪 美颜 虚化 增强 为一体的极速图像润色算法 附Demo程序> 这也算是学习过程中比较有成就感的一个算法. 自2015年做算法开始到今天,还有个把月 ...

  9. 3D Lut 电影级调色算法 附完整C代码

    在前面的文章,我提到过VSCO Cam 的胶片滤镜算法实现是3d lut. 那么3d lut  到底是个什么东西呢? 或者说它是用来做什么的? 长话短说,3d lut(全称 : 3D Lookup t ...

随机推荐

  1. CentOS下调整home和根分区大小

    由于我们有时候没法预估或者说错误的盘符分区的时候,常常会导致我们后面的操作出现极大的不方便,这里我就记录下一个错误分区后对home和根分区存储空间大小调整的整个过程! ①查看我们现有机器的分区状况 c ...

  2. Revit

    log file Windows Vista or Windows 7:%LOCALAPPDATA%\Autodesk\Revit\Autodesk Revit 2016\Journals

  3. 【Web crawler】print_all_links

    How to repeat Procedures&Control CS重要概念 1.1 过程procedures 封装代码,代码重用 1.2 控制Control DEMO # -*- codi ...

  4. [翻译] Macros with a Variable Number of Arguments - GCC

    可变参数宏(Variadic Macro) 在1999年的ISO C标准中,可以声明一个像函数一样接受可变参数的宏.定义这种宏的语法与函数的定义相似.这是一个例子: #define debug(for ...

  5. 修改Tomcat8w.exe可执行路径:Path to executable

    学习java web时,先下载了Tomcat8.5,在Eclipse中配置了Tomcat的相关环境后,出现了个别版本不兼容问题,就打算换成8.0版本.但是删除整个8.5的文件夹后,运行8.0bin中的 ...

  6. Hadoop学习---Hadoop的深入学习

    Hadoop生态圈 存储数据HDFS(Hadoop Distributed File System),运行在通用硬件上的分布式文件系统.具有高度容错性.高吞吐量的的特点. 处理数据MapReduce, ...

  7. js数组 标签: javascript 2016-08-03 14:15 131人阅读 评论(0) 收藏

    数组排序 reverse()方法 reverse()方法会反转数组的顺序. sort()方法 默认情况下sort()方法按升序排列数组项.为实现排序sort()方法调用每项的toString(),然后 ...

  8. C#中internal关键字

    对于一些大型的项目,通常由很多个DLL文件组成,引用了这些DLL,就能访问DLL里面的类和类里面的方法.比如,你写了一个记录日志的DLL,任何项目只要引用此DLL就能实现记录日志的功能,这个DLL文件 ...

  9. SecurityError: The operation is insecure.(js不安全操作)

    今天突然就遇上了这样的情况,本来在出错的这一行的后面,还有要执行的语句,都没有办法执行,真实坑爹,而最要命的事情,这样的情况,在我的chrome浏览器里没有,但是在firefox里就会出现. The ...

  10. 关于tcp状态及一些延展

    1.常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭. TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断 ...