自动曝光修复算法 附完整C代码

众所周知,

图像方面的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(:,:,1);
G=A(:,:,2);
B=A(:,:,3);

% Calculate Intensity Component
I=(R+G+B)/3;

% Invert the image
I_inverted=255-I;

% Apply Average Filter to obtain the Mask Image
h_average=fspecial('average',15);
M=imfilter(I_inverted,h_average);

% Color Correction for R channel
R_new=zeros(size(R));
[c_y, c_x,~] = size(R);
for j = 1:c_x
        for i = 1:c_y
            p=double(R(i,j));
            q=double(M(i,j));
            R_new(i,j,:)=int8(255*((p/255)^(2^((128-q)/128))));
        end
end

% Color Correction for G channel
G_new=zeros(size(G));
[c_y, c_x,~] = size(G);
for j = 1:c_x
        for i = 1:c_y
            p=double(G(i,j));
            q=double(M(i,j));
            G_new(i,j,:)=int8(255*((p/255)^(2^((128-q)/128))));
        end
end

% Color Correction for B channel
B_new=zeros(size(B));
[c_y, c_x,~] = size(B);
for j = 1:c_x
        for i = 1:c_y
            p=double(B(i,j));
            q=double(M(i,j));
            B_new(i,j,:)=int8(255*((p/255)^(2^((128-q)/128))));
        end
end

% Output Image
O=zeros(size(A));
O(:,:,1)=R_new;
O(:,:,2)=G_new;
O(:,:,3)=B_new;

% Convert the double output image to uint8
O=uint8(O);

% Plot the images
subplot(1,3,1), imshow(A), title('Original Image');
subplot(1,3,2), imshow(M), title('Mask');
subplot(1,3,3), 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[256 * 256];
    for (int mask = 0; mask < 256; ++mask) {
        unsigned char *pLocalLut = LocalLut + (mask << 8);
        for (int pix = 0; pix < 256; ++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) / 512) + 1;
    BoxBlurGrayscale(Output, Mask, Width, Height, Radius);
    for (int Y = 0; 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 = 0; X < Width; X++) {
            unsigned char *pLocalLut = LocalLut + (pMask[X] << 8);
            for (int C = 0; 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[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;

    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[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;
}

int GetMirrorPos(int Length, int Pos) {
    if (Pos < 0)
        return -Pos;
    else if (Pos >= Length)
        return Length + Length - Pos - 2;
    else
        return Pos;
}

unsigned char ClampToByte(int Value) {
    if (Value < 0)
        return 0;
    else if (Value > 255)
        return 255;
    else
        return (unsigned char) Value;
}

void FillLeftAndRight_Mirror(int *Array, int Length, int Radius) {
    for (int X = 0; X < Radius; X++) {
        Array[X] = Array[Radius + Radius - X];
        Array[Radius + Length + X] = Array[Radius + Length - X - 2];
    }
}

int SumOfArray(const int *Array, int Length) {
    int Sum = 0;
    for (int X = 0; 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 <= 0) || (Height <= 0) || (Radius <= 0)) return;
    if (Radius < 1) return;
    Radius = MIN(MIN(Radius, Width - 1), Height - 1);
    int SampleAmount = (2 * Radius + 1) * (2 * Radius + 1);
    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 = 0; Y < Height + Radius + Radius; Y++)
        ColOffset[Y] = GetMirrorPos(Height, Y - Radius);
    {
        for (int Y = 0; Y < Height; Y++) {
            unsigned char *scanLineOut = output + Y * Width;
            if (Y == 0) {
                memset(ColValue + Radius, 0, Width * sizeof(int));
                for (int Z = -Radius; Z <= Radius; Z++) {
                    unsigned char *scanLineIn = input + ColOffset[Z + Radius] * Width;
                    for (int X = 0; X < Width; X++) {
                        ColValue[X + Radius] += scanLineIn[X];
                    }
                }
            } else {
                unsigned char *RowMoveOut = input + ColOffset[Y - 1] * Width;
                unsigned char *RowMoveIn = input + ColOffset[Y + Radius + Radius] * Width;
                for (int X = 0; X < Width; X++) {
                    ColValue[X + Radius] -=
                            RowMoveOut[X] - RowMoveIn[X];
                }
            }
            FillLeftAndRight_Mirror(ColValue, Width, Radius);
            int LastSum = SumOfArray(ColValue, Radius * 2 + 1);
            scanLineOut[0] = ClampToByte((int) (LastSum * Inv));
            for (int X = 0 + 1; X < Width; X++) {
                int NewSum = LastSum - ColValue[X - 1] + 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 == 1) {
        for (unsigned int Y = 0; Y < Height; Y++) {
            unsigned char *pOutput = Output + (Y * Width);
            unsigned char *pInput = Input + (Y * Width);
            for (unsigned int X = 0; X < Width; X++) {
                pOutput[X] = (unsigned char) (255 - pInput[X]);
            }
        }
    } else {
        for (unsigned int Y = 0; Y < Height; Y++) {
            unsigned char *pOutput = Output + (Y * Width);
            unsigned char *pInput = Input + (Y * Width * Channels);
            for (unsigned int X = 0; X < Width; X++) {
                pOutput[X] = (unsigned char) (255 - ClampToByte(
                        (21842 * pInput[0] + 21842 * pInput[1] + 21842 * pInput[2]) >> 16));
                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[256 * 256];
    for (int mask = 0; mask < 256; ++mask) {
        unsigned char *pLocalLut = LocalLut + (mask << 8);
        for (int pix = 0; pix < 256; ++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) / 512) + 1;
    BoxBlurGrayscale(Output, Mask, Width, Height, Radius);
    for (int Y = 0; 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 = 0; X < Width; X++) {
            unsigned char *pLocalLut = LocalLut + (pMask[X] << 8);
            for (int C = 0; 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 < 2) {
        printf("usage: %s   image \n ", argv[0]);
        printf("eg: %s   d:\\image.jpg \n ", argv[0]);

        return (0);
    }
    char *szfile = argv[1];

    getCurrentFilePath(szfile, saveFile);

    int Width = 0;
    int Height = 0;
    int Channels = 0;
    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 * 1000));
    if ((Channels != 0) && (Width != 0) && (Height != 0)) {
        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 * 1000));

        startTime = now();

        saveImage("done.jpg", Width, Height, Channels, outputImg);
        double nSaveTime = calcElapsed(startTime, now());

        printf("save time: %d ms.\n ", (int) (nSaveTime * 1000));

        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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端那些事

过渡与动画 - 逐帧动画&steps调速函数

写在前面 上一篇中我们熟悉五种内置的缓动曲线和(三次)贝塞尔曲线,并且基于此完成了缓动效果. 但是如果我们想要实现逐帧动画,基于贝塞尔曲线的调速函数就显得有些无...

27810
来自专栏大数据挖掘DT机器学习

很棒的R语言回归模型和方差模型

对于初学者,利用R语言自带的数据进行练习是不错的选择,下面这些模型便是最好的实例。 1、回归模型 回归模型利用自带的faithful数据来示例,faithful...

4738
来自专栏极客猴

Python 绘图,我只用 Matplotlib(二)

上篇文章,我们了解到 Matplotlib 是一个风格类似 Matlab 的基于 Python 的绘图库。它提供了一整套和matlab相似的命令API,十分适合...

1341
来自专栏IMWeb前端团队

移动端重构实战系列7——环形UI

本文作者:IMWeb 结一 原文出处:IMWeb社区 未经同意,禁止转载 ”本系列教程为实战教程,是本人移动端重构经验及思想的一次总结,也是对sand...

2326
来自专栏Java与Android技术栈

Java实现图片滤镜的高级玩法

github 地址:https://github.com/imageprocessor/cv4j

1573
来自专栏顶级程序员

阿里巴巴公司根据截图查到泄露信息的具体员工的技术是什么?

? 在月饼事件中的新闻中提到。阿里对员工访问的界面做了一定的处理。貌似这不是简单的水印。这种处理是什么,是怎么做到的呢? =====第三次更新===== 评论...

5209
来自专栏walterlv - 吕毅的博客

Grid 布局算法!自己动手实现一个 Grid

2018-05-20 07:11

932
来自专栏Flutter入门

偶遇FFmpeg(二)——常用命令

将输入的视频的帧,以左上角为坐标的原点,剪切成x,y坐标开始的指定大小。 语法:

3564
来自专栏非典型技术宅

iOS实践:通过核心动画完成过山车1. 思路和所用到的内容2. 辅助元素的创建(背景颜色、草坪、大地、小树、云彩)3. 雪山的实现4. 轨道的实现

1845
来自专栏天天P图攻城狮

终端图像处理系列 - OpenGL混合模式的使用

OpenGL一次渲染过程包含了多个阶段,包括顶点着色器、图元组装、栅格化、片元着色器、测试和混合等,最后将结果输出的FrameBuffer上。

85414

扫码关注云+社区

领取腾讯云代金券