图像处理自动曝光算法

2018-06-02 21:42:00 jiubanbannixue 阅读数 2332
众所周知,


图像方面的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. ");


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 = '';


}


} else if (drv)


*drv = '';


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 = '';


}


if (dir) {


for (s = path; s < p;)


*dir++ = *s++;


*dir = '';


}


}


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 ");


printf("blog:http://cpuimage.cnblogs.com/ ");


if (argc < 2) {


printf("usage: %s image ", argv[0]);


printf("eg: %s d:\image.jpg ", 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. ", (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! ", szfile);


}


startTime = now();


LocalColorCorrection(inputImage, outputImg, Width, Height, Channels);


double nProcessTime = calcElapsed(startTime, now());


printf("process time: %d ms. ", (int) (nProcessTime * 1000));


startTime = now();


saveImage("done.jpg", Width, Height, Channels, outputImg);


double nSaveTime = calcElapsed(startTime, now());


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


if (outputImg) {


stbi_image_free(outputImg);


}


if (inputImage) {


stbi_image_free(inputImage);


}


} else {


printf("load: %s fail! ", szfile);


}


getchar();


printf("press any key to exit. ");


return (EXIT_SUCCESS);


}


最后你觉得我们的文章对你有帮助,欢迎关注我,可以私信我:久伴,领取学习资料,在评论下方可以关注我的学习群,你可以随时在上面向我们提问,把你在学习C++过程中所遇到的问题发给我们。我们每天都会按时回复大家的每一个问题,希望久伴可以伴随你从入门到专家。

2019-11-03 13:28:21 hahahahhahha 阅读数 619

3A技术即自动对焦(AF)、自动曝光(AE)和自动白平衡(AWB)。

3A数字成像技术利用了AF自动对焦算法、AE自动曝光算法及AWB自动白平衡算法来实现图像对比度最大、改善主体拍摄物过曝光或曝光不足情况、使画面在不同光线照射下的色差得到补偿,从而呈现较高画质的图像信息。

采用了3A数字成像技术的摄像机能够很好的保障图像精准的色彩还原度,呈现完美的日夜监控效果。

3A技术长久以来被认为是数字图像技术中最有挑战性的技术。

它与模块化技术或算法不同,并非可一成不变地应用于各同类摄像产品上,需要根据摄像产品采用的传感器,镜头重新开发与之相适应的算法。

换而言之,如果不具备坚实的基础知识,大量的经验与know-how积累的话,要成功开发高性能3A技术难之又难。

3A性能的好坏决定了拍摄画面的明亮度,色调接近抑或偏离自然并且也决定了聚焦速度快慢。特别在拍摄场景的照明条件和被拍摄物体经常变化的环境条件下获取高画质图像尤为重要。例如户外用安防摄像机等。

 

自动白平衡
平衡, 是指在图像处理的过程中, 对原本材质为白色的物体的图像进行色彩还原, 去除外部光源色温的影响, 使其在照片上也显示白色。所以无论照明条件为自然光﹑人工照明(荧光灯,白炽灯等),还是各种照明光源不一混合条件的情况下,都会根据光源变化自动对颜色进行补正,还原最自然﹑正确的颜色,从而获得出色的颜色表现。

 自动曝光

当户外照明过于明亮时拍摄画面容易出现飞白,与之相反,照明条件过于昏暗的户内和夜晚户外场景下,容易产生被拍摄对象难于识别的现象。

自动曝光可使即便在过于明亮/昏暗的场景下拍摄到平常照明条件下的明亮清晰画面。并且,当摄像机的宽动态功能处于打开状态时,有时会出现明暗反差大的情景,自动曝光功能可根据情况相应地对过明亮/昏暗的部分适当地进行调暗/亮。

自动对焦

准确的自动对焦能够避免目标场景,人物偏离焦点的情况,由此有效避免偏离焦点导致细节模糊造成的画面模糊现象。高性能的自动对焦能够在对焦速度和精确度上作好平衡,使之能够兼具“高速”“准确”不仅要及时抓拍到目标,并且能够清晰记录被拍摄目标的动作细节。

2020-02-21 15:43:37 weixin_44580210 阅读数 339

图像传感器与信号处理——自动曝光算法

我是搞视觉SLAM的,在很久之前我和一位视觉SLAM大佬交流过,视觉SLAM难以落地原因有很多,其中之一就是环境光照对SLAM系统有较大的影响,而在实际的应用过程中,我们很难去真正控制环境中的光照变化,尤其是在室外。因此如何使得输入SLAM系统的图像具备稳定的光照就是非常值得研究的,而自动曝光就是其中一个非常重要的解决方案。这就是我这几天在家看这几篇论文的出发点。

1. 如何实现自动曝光?

自动曝光的过程是一个经典的反馈控制过程

首先,既然是控制我们就需要知道控制的是什么,对于一般的相机来说,和曝光相关的参数配置有光圈曝光时间增益等,其中光圈的配置必须通过硬件实现,因此在实际操作过程中一般只控制曝光时间增益来实现自动曝光。

其次,既然是反馈我们就需要建立一个标准来衡量曝光程度的优劣,而这个衡量标准定义的不同则意味着自动曝光算法的不同,下文就是据此对自动曝光算法进行分类,分别是基于直方图统计的算法、基于图像熵的算法、基于梯度的算法。

2. 基于直方图统计的算法

基于直方图统计的算法相关文章较多,而且都比较旧想法也比较简单,下面这些文章都是基于直方图统计的算法:
(1)An Advanced Video Camera System with Robust AF, AE, and AWB Control,2001
(2)Real-time Image Fusion and Adaptive Exposure Control for Smart Surveillance Systems,2007
(3) 2.3 Automatic Camera Exposure Control,2007
(4) 2.4 Autonomous Configuration of Parameters in Robotic Digital Cameras,2009
这里只对下面这篇比较新的一篇论文进行分析:

2.1 Acquisition of Agronomic Images with Sufficient Quality by Automatic Exposure Time Control and Histogram Matching,2013

这篇文章主要描述的是一个在农用机器人上应用的实际场景,最终目的是通过自动曝光实现农用机器人更加准确地识别稻田里面的特征。对于自动曝光算法,其在图像直方图的基础上有了进一步的改进,算法框架如下图所示:
在这里插入图片描述
除了通过图像直方图计算曝光时间外,还会通过直方图匹配对曝光后的图像进行进一步优化。这篇文章采用的归一化直方图,首先计算:μn(g)=ε(gm)np(g) with m=sgp(g) \mu_{n}(g)=\sum_{\varepsilon}(g-m)^{n} p(g) \text { with } m=\sum_{s} g \cdot p(g) 其中gg的即直方图的横坐标,其范围是00255255p(g)p(g)是横坐标gg在直方图中对应的值,mm是平均值在此基础上可以计算方差v=μ2(g)v=\mu_{2}(g)、偏度γ=μ3(g)μ23/2(g)\gamma=\mu_{3}(g) \mu_{2}^{-3 / 2}(g)和峰度κ=μ4(g)μ22(g)\kappa=\mu_{4}(g) \mu_{2}^{-2}(g),这篇文章中用于Decision making的两个参数是平均值mm和偏度γ\gamma,如下所示:

 while (mimui and γiγui)or(mimli and γiγli)\text{ while } \left(m_{i} \geq m_{u i} \text { and } \gamma_{i} \geq\left|\gamma_{u i}\right|\right) \text{or} \left(m_{i} \leq m_{l i} \text { and } \gamma_{i} \geq \gamma_{l i}\right)ET=(mRref+mGrefmR+mG)ETcurr E T=\left(\frac{m_{R r e f}+m_{G r e f}}{m_{R}+m_{G}}\right) E T_{c u r r} 其中mim_{i}γi\gamma_{i}为图像第ii个通道的均值和偏度(红绿蓝三个通道,但是由于农用场景的限制,这篇文章只考虑红和绿两个通道),muim_{u i}mlim_{l i}分别是均值的最大最小边界,γui\gamma_{u i}γli\gamma_{l i}分别是偏度的最大最小边界。mRrefm_{R r e f}mGrefm_{G r e f}分别为参考图像的直方图平均值,通过上式就可以计算出优化后的曝光时间,即ET modification中的过程。
如果在Decision making中判定不需要进行曝光时间优化后就可以直接进行Image quality Improvement,其流程是分别计算输入图像和参考图像的直方图的累加概率P(gr)=i=0εsp(gi) and P(gc)=i=0Rp(gc) P\left(g_{r}\right)=\sum_{i=0}^{\varepsilon_{s}} p\left(g_{i}\right) \text { and } P\left(g_{c}\right)=\sum_{i=0}^{R} p\left(g_{c}\right) 通过累加概率进行匹配,然后搜索最接近的P(gr)P\left(g_{r}\right)值,然后用grg_{r}改变gcg_{c}(这里论文中是这么说的,但是具体如何改变并没有举例说明,具体怎么实现的会有点不清楚)。

3. 基于熵的算法

基于熵的自动曝光算法相关的文章好像并不多,目前就找到这一篇比较有代表性的:

3.1 Camera Parameters Auto-Adjusting Technique for Robust Robot Vision,2010

这篇文章主要是介绍了一种基于图像熵的摄像机参数自动调节技术,根据香农定理信息内容可以通过熵进行描述,并且熵可以随着信息内容增加而增大。在RGB色域中计算图像熵,公式如下:Entropy=i=0L1PRilogPRii=0L1PGilogPGii=0L1PBilogPBi \begin{aligned} \text {Entropy}=&-\sum_{i=0}^{L-1} P_{R i} \log P_{R i}-\sum_{i=0}^{L-1} P_{G i} \log P_{G i}-\sum_{i=0}^{L-1} P_{B i} \log P_{B i} \end{aligned} 其中,L=256L=256为RGB颜色通道的离散范围,PRi,PGi,PBiP_{R i}, P_{G i}, P_{B i}分别是颜色Ri,Gi,BiR i, G i, B i在图像中的概率,由上式可知0=Min( Entropy ) Entropy Max( Entropy )=3i=0255(1/256)log(1/256)=16.6355 0=\operatorname{Min}(\text { Entropy }) \leq \text { Entropy } \leq \operatorname{Max}(\text { Entropy })=-3 * \sum_{i=0}^{255}(1 / 256) \log (1 / 256)=16.6355 文章中提到控制的参数为曝光时间和增益,那么通过实验可以测得图像熵随曝光时间和增益这两个参数变化的趋势如下图所示:
在这里插入图片描述其中图中蓝色背脊线上的参数都是可以接受的,但是如上图所示的这种三维图是不方便搜索最佳参数的,因此在实际应用中,将曝光时间和增益设置为相同的数值,这样就可以将三维图转化为如下图所示的二维图:
在这里插入图片描述
其中图(a)是室内测得的图像熵随参数变化的曲线,图(b)是室外测得的图像熵随参数变化的曲线,如图所示,图像熵随着参数呈单调递增再单调递减的变化趋势。因此搜索就变得简单,可以从一个较低的曝光时间和增益开始,以一定的步长朝峰值搜索,直到下一步的图像熵小于当前步,那么就寻找到了最优的曝光和增益。

4. 基于梯度的算法

这是最近几年比较热门的一种算法,起码对于SLAM来说,实验证明这些算法是有效的,这里分享两篇方法比较类似的文章:

4.1 Active Exposure Control for Robust Visual Odometry in HDR Environments,2017

题目直抒胸臆呀,就是通过自动曝光控制提高视觉里程计在高动态环境下的鲁棒性。这篇文章中提出了在视觉里程计中应用自动曝光需要解决的问题是,一方面需要最大化图像信息,另一方面需要补偿由于曝光时间调整带来的时间差

首先解决第一个问题,如何最大化图像信息,首先论文中定义了光度响应函数I=f(X)=f(EΔt) I=f(X)=f(E \Delta t) 其中,II为图像的光照强度,辐照度EE描述了单位时间射入像素的能量,Δt\Delta t为曝光时间,而这个函数本身ff是很难有具体的表达式的,这也就是为什么在自动曝光系统中采用的多是反馈控制算法,如果有了光度响应函数的具体表达式,我们就可以通过这个表达式直接获得合适的曝光时间。在此基础上定义了逆响应函数g=lnf1 g=\ln f^{-1} 这个逆响应函数并不是直接对响应函数求逆,那么光度响应函数的逆响应函数如下:g~(I)=lnE+lnΔt \tilde{g}(I)=\ln E+\ln \Delta t 我们通过采集不同的曝光时间Δt\Delta t的图像,并计算图像的光照强度II,这样就可以看做获得了一组离散数据g(k),k=0,1,,Zmaxg(k), k=0,1, \ldots, Z_{\max },然后利用这组数据采用十阶的多项式函数进行拟合函数 gg,获得函数 gg后可以进一步函数的微分形式gg^{\prime},如下图所示就是这样一条曲线:
在这里插入图片描述
有了这个基础后,下面进一步建立曝光程度的评价标准,论文中建立了四种标准,并对标准进行了讨论对比,首先将像素u\mathbf{u}的梯度幅值定义为:G(I,u,Δt)=I(u,Δt)2 G(\mathbf{I}, \mathbf{u}, \Delta t)=\|\nabla \mathbf{I}(\mathbf{u}, \Delta t)\|^{2} 其中I()=[Ix,Iy]\nabla \mathrm{I}(\cdot)=\left[\frac{\partial \mathrm{I}}{\partial x}, \frac{\partial \mathrm{I}}{\partial y}\right],那么四种标准分别是:
(1)直接求和Msum=uiIG(ui) M_{\mathrm{sum}}=\sum_{\mathbf{u}_{i} \in \mathrm{I}} G\left(\mathbf{u}_{i}\right)
(2)对数求和Mshim=uiImui M_{\mathrm{shim}}=\sum_{\mathbf{u}_{i} \in \mathrm{I}} m_{\mathbf{u}_{i}} 其中mui={1Nlog(λ(G~(ui)σ)+1),G(ui)σ0,G(ui)<σ m_{\mathbf{u}_{i}}=\left\{\begin{array}{ll} {\frac{1}{N} \log \left(\lambda\left(\tilde{G}\left(\mathbf{u}_{i}\right)-\sigma\right)+1\right),} & {G\left(\mathbf{u}_{i}\right) \geq \sigma} \\ {0,} & {G\left(\mathbf{u}_{i}\right)<\sigma} \end{array}\right. 这种评价标准在下一篇论文中作更加详细的分析。

(3)百分位数Mperc(p)= percentile ({G(ui)}uiI,p) M_{\mathrm{perc}}(p)=\text { percentile }\left(\left\{G\left(\mathbf{u}_{i}\right)\right\}_{\mathbf{u}_{i} \in \mathrm{I}}, p\right) 其中, percentile \text { percentile }函数是求百分位数,例如当参数 pp 为0.5时,那么Mperc(p)M_{\mathrm{perc}}(p)就是所有梯度幅值中的中位数。

(4)软百分位数Msoftperc (p)=i[0,S]Wih(p)Gith M_{\text {softperc }}(p)=\sum_{i \in[0, S]} W_{i \mathrm{h}}(p) \cdot G_{i \mathrm{th}} 其中SS为图像中所有像素的数量,GithG_{i \mathrm{th}}为排好序的梯度幅值,而权重WihW_{i \mathrm{h}}定义如下:Wih={1Nsin(π2[pS]i)k,ipS1Nsin(π2π2ipSSpS)k,i>pS W_{i \mathrm{h}}=\left\{\begin{array}{ll} {\frac{1}{N} \sin \left(\frac{\pi}{2[p \cdot S]} i\right)^{k},} & {i \leq\lfloor p \cdot S\rfloor} \\ {\frac{1}{N} \sin \left(\frac{\pi}{2}-\frac{\pi}{2} \frac{i-\lfloor p \cdot S\rfloor}{S-\lfloor p \cdot S\rfloor}\right)^{k},} & {i>\lfloor p \cdot S\rfloor} \end{array}\right. 其中符号\lfloor\cdot\rfloor为向下取整。

在论文中,通过对比FAST角点在各自标准下最优图像提取数量进行实验对比,发现百分位数标准软百分位数标准提取数量会更多,然后还通过下面这张图进行图进行直观效果说明,我觉得很有意思:在这里插入图片描述从上到下分别是四个评价标准计算的值随曝光时间的变化曲线,可以看到,前两者是随着曝光时间单调上升,而在最顶点是已经出现了过曝光的情况,后两者则不会,曲线有极大值,而在极大值时图像曝光对于细节的保留确实较优。

最后说明如何根据评价标准光度响应函数进行自动曝光控制了,那么要做的就是将评价标准MM对曝光时间Δt\Delta t求导,然后通过梯度下降法求得极大值,那么不管采用上面哪种评价标准,中间都包括求G()Δt\frac{\partial G(\cdot)}{\partial \Delta t},那么有G()Δt=2I(u,Δt)Δt[I(u,Δt)] \frac{\partial G(\cdot)}{\partial \Delta t}=2 \nabla \mathbf{I}(\mathbf{u}, \Delta t)^{\top} \frac{\partial}{\partial \Delta t}[\nabla \mathbf{I}(\mathbf{u}, \Delta t)] 根据二阶导数的对称性,有Δt[I(u,Δt)]=[ΔtI(u,Δt)] \frac{\partial}{\partial \Delta t}[\nabla \mathbf{I}(\mathbf{u}, \Delta t)]=\nabla\left[\frac{\partial}{\partial \Delta t} \mathbf{I}(\mathbf{u}, \Delta t)\right] IΔt\frac{\partial \mathbf{I}}{\partial \Delta t}就需要用到我们前面提到的光度响应函数IΔt=f(E(u)Δt)Δt=f(E(u)Δt)E(u)=f[f1(I)]E(u)=E(u)[f1](I)=1g(I)Δt \frac{\partial \mathbf{I}}{\partial \Delta t} = \frac{f(E(\mathbf{u}) \Delta t)}{\partial \Delta t} =f^{\prime}\left(E(\mathbf{u}) \Delta t\right) E(\mathbf{u})=f^{\prime}\left[f^{-1}(\mathbf{I})\right] E(\mathbf{u})=\frac{E(\mathbf{u})}{\left[f^{-1}\right]^{\prime}(\mathbf{I})} = \frac{1}{g^{\prime}(\mathbf{I}) \Delta t} 其中,最后一个等式是因为前面定义的逆响应函数g(I)=lnf1(I) g(\mathbf{I})=\ln f^{-1}(\mathbf{I}) 那么对gg求导有g(I)=[f1](I)f1(I)=[f1](I)E(u)Δt g^{\prime}(\mathbf{I})=\frac{\left[f^{-1}\right]^{\prime}(\mathbf{I})}{f^{-1}(\mathbf{I})} =\frac{\left[f^{-1}\right]^{\prime}(\mathbf{I})}{E(\mathbf{u}) \Delta t} 将上式变形代入即可。因为g(I)g^{\prime}(\mathbf{I})在一开始就介绍了如何通过采样拟合求得,那么最终G()Δt\frac{\partial G(\cdot)}{\partial \Delta t}就可求得,就可以通过梯度下降法求得极大值。以Msoftperc (p)M_{\text {softperc }}(p)为例,我们可以求得其关于曝光时间Δt\Delta t的导数有:Msoftperc Δt=i[0,S]Wi th GithΔt \frac{\partial M_{\text {softperc }}}{\partial \Delta t}=\sum_{i \in[0, S]} W_{i \text { th }} \frac{\partial G_{i t h}}{\partial \Delta t} 那么曝光时间的更新方式为:Δtnext =Δt+γMsoftpere Δt \Delta t_{\text {next }}=\Delta t+\gamma \frac{\partial M_{\text {softpere }}}{\partial \Delta t} 关于自动曝光的部分这篇文章就介绍到这里,文章后面一部分是进行曝光补偿,这个稍后介绍

4.2 Gradient-based Camera Exposure Control for Outdoor Mobile Platforms,2018

这篇文章中提出要解决的问题一方面是如何进行基于图像梯度的自动曝光控制,另一方面是如果实现多相机联合曝光

首先解决第一个问题,这篇文章提出,自然场景图像的梯度分布满足重尾分布,也就是场景中大部分梯度的幅值较小,大幅值梯度分布得少而离散。为了平衡梯度幅值与数量之间的关系,本文提出使用一个对数函数梯度进行映射:mˉi={1Nlog(λ(miδ)+1) for miδ0 for mi<δ \bar{m}_{i}=\left\{\begin{array}{ccc} {\frac{1}{N} \log \left(\lambda\left(m_{i}-\delta\right)+1\right)} & {\text { for }} & {m_{i} \geq \delta} \\ {0} & {\text { for }} & {m_{i}<\delta} \end{array}\right. 其中,N=log(λ(1δ)+1)N=\log (\lambda(1-\delta)+1)mim_i是像素 ii 处的梯度幅值,可以看到当梯度小于阈值 δ\delta 时,梯度幅值映射为零。λ\lambda是一个控制映射趋势的参数。mˉi\bar{m}_{i}就代表梯度信息中和梯度幅值相关的部分,其输出控制在0011。那么图像的曝光评价标准就定义为:M=mˉi M=\sum \bar{m}_{i} MM越大说明曝光效果越好,图像中包含的信息越多。接下来说明如何进行曝光控制,在利用当前曝光时间获得的图像进行γ\gamma矫正,即Iout=IinγI_{o u t}=I_{i n}^{\gamma},并求取最优的矫正值γ^\hat{\gamma}γ^=argmaxγM(Iinγ) \hat{\gamma}=\arg \max _{\gamma} M\left(I_{i n}^{\gamma}\right) 具体操作是在γ=[11.9,1.9]\gamma=\left[\frac{1}{1.9}, 1.9\right]的范围里选取一些列的γ\gamma值对当前图像进行γ\gamma矫正,并计算矫正后的曝光程度MM,然后利用五阶的多项式函数对这一系列离散值进行拟合,组中求得最优的矫正值γ^\hat{\gamma},如下图所示:在这里插入图片描述获得最优矫正值γ^\hat{\gamma}后通过更新函数就可以直接对曝光时间进行更新,更新函数分为线性更新函数和非线性更新函数:
线性更新函数:Et+1=(1+αKp(1γ^))Et,α={1/2 for γ^11 for γ^<1 E_{t+1}=\left(1+\alpha K_{p}(1-\hat{\gamma})\right) E_{t}, \alpha=\left\{\begin{array}{ccc} {1 / 2} & {\text { for }} & {\hat{\gamma} \geq 1} \\ {1} & {\text { for }} & {\hat{\gamma}<1} \end{array}\right. 非线性更新函数:Et+1=(1+αKp(R1))Et s.t. R=dtan{(2γ^)arctan(1d)arctan(1d)}+1 \begin{aligned} E_{t+1} &=\left(1+\alpha K_{p}(R-1)\right) E_{t} \\ \text { s.t. } \quad R &=d \cdot \tan \left\{(2-\hat{\gamma}) \cdot \arctan \left(\frac{1}{d}\right)-\arctan \left(\frac{1}{d}\right)\right\}+1 \end{aligned} 其中EtE_{t}tt时刻的曝光水平,更新的最终结果是使得最优矫正值γ^\hat{\gamma}趋近与11,这样就完成了整个反馈的控制。在此基础上,这篇文章一个很重要的贡献是实现了多相机的联合自动曝光,这里简单介绍下:Et+1i=argminXαiEu(i,t)+1αiNjG(i)Ep(i,j,t) s.t. Eu(i,t)=XEt+1i2Ep(i,j,t)=XrijEti2rij=median(mean(pj)mean(pi)+ϵ),pP \begin{aligned} E_{t+1}^{i *}=& \underset{X}{\arg \min } \quad \alpha^{i} \cdot \mathcal{E}_{u}(i, t)+\frac{1-\alpha^{i}}{N} \sum_{j \in G(i)} \mathcal{E}_{p}(i, j, t) \\ \text { s.t. } & \mathcal{E}_{u}(i, t)=\left\|X-E_{t+1}^{i}\right\|^{2} \\ & \mathcal{E}_{p}(i, j, t)=\left\|X-r^{i j} \cdot E_{t}^{i *}\right\|^{2} \\&r^{i j}=\operatorname{median}\left(\frac{\operatorname{mean}\left(p^{j}\right)}{\operatorname{mean}\left(p^{i}\right)+\epsilon}\right), \quad p \in P \end{aligned} 其中,优化函数将所需曝光量视为一元项Eu(i,t)\mathcal{E}_{u}(i, t),并将重叠区域中的亮度相似度作为成对项Ep(i,j,t)\mathcal{E}_{p}(i, j, t)PP是两个相机之间的重叠区域,通过以上式为最优化目标进行优化操作就可以获得各个相机在联合自动曝光下最优曝光水平。

目前就看了这十篇左右的论文,看着看着又发现了近几年出的几篇很好的文章,是和SLAM相关的,考虑如何更好地将自动曝光算法和SLAM结合起来以提高系统的性能,很有意思。这篇博客就先写这么多,写得太长了自己都要看吐了,接下来把新的几篇论文看完再重新总结下,有问题欢迎交流~

2016-05-31 14:25:31 u011043491 阅读数 4455

曝光

真实世界是什么样子的?
好吧,真实世界的某些位置非常暗:山洞、煤炭、夜晚的床底;有些地方不是那么地暗:傍晚、沙滩、人的脸;有些部分是亮的:白衬衫、雪、风帆、灯泡;有些物体真的很亮:聚光灯、天空、太阳和激光。

计算机图形学看起来是什么样子的?
展示到电脑屏幕上的图像的亮度范围是有限的,通常从0(深灰色)到256(暗淡的白色)。

知道了这些,我们怎么可能用计算机屏幕来逼真地展示真实世界中的图像?大自然有着如此大的亮度范围,而计算机屏幕只能展示其中非常小的一个波段。你也许试过将大自然的亮度缩放到屏幕可以表达的范围内,如将黑暗的洞穴表示为0,将太阳表示为255。但是如果你真的这样做,你会发现在表示诸如人脸这类事物的时候非常不准确。它们看起来非常地暗。下图中的太阳亮度为255,虽然能很好地展示太阳,但天空显得有点暗,而大地简直看起来像是处在黑暗之中。
这里写图片描述

另一种方法就是限制亮度值。例如:任何比雪更亮的东西以亮度255来表示。下图中的图像由这种方式来表示。地面看起来挺不错,但是天空看起来比地面亮很多,而且差不多是全白的。你看不到太阳,而云就好像是一大片浅蓝色背景中的一团白色。
这里写图片描述

现在来提问:
相片看起来是什么样子的?
好比电脑屏幕,相片本身也只有一段较窄的亮度范围。相片上最暗的物体看起来不是那么地暗,最亮的部分也并不比我们所看到的真实环境中的那部分更亮。
那么,相片为何似乎并没有遇到电脑屏幕那样的显示问题呢?好吧,事实上它也会遇到这个问题,只是相片能更好地处理大范围的光强。并且有曝光控制。了解相片是如何拍摄的会有利于帮助我们理解这两点。
拍照
拍照时,相机中的快门短时间内开启来接收被拍摄场景内的光照。光照被相机镜头聚焦到一片感光胶片上。胶片上含有的化学物质在遇到光照时分解,如此,场景被记录下来。被分解的化学物质越多,成像相片就会看起来越亮。
进入相机镜头的光强能通过控制相机快门开启的时长(曝光)和快门光圈(fstop)来调节。
接下来,停止胶片的感光性能并成像得到负片。再让光线通过负片照射相纸来生成最重的相片。
这个过程和计算机图形又有什么关系呢?
刚才那个简单的拍照模型告诉我们为什么相机更擅长处理大范围的光强。现在把最初的曝光胶片想象成场景中的光。假设在胶片的一小部分区域内有100个感光化学物质的分子。光线照射到这片区域,其中一半的分子分解了。那么得到的图像将有着一半的亮度。假设同样分量的光再次照射到这片区域,那么还没分解的分子中又会有一半的分子分解,即还剩下1/4未分解。得到的图像将有着75%的亮度。随着越来越多的光线照射到这片区域,越来越多的分子被分解。另一方面,随着胶片上的未分解分子越来越少,你将需要越来越多的光照来分解等量的分子。
这张图表示了随着光线逐渐增多,胶片上的可分解物质的剩余情况。最初化学物在迅速分解,然而,随着越来越少的未分解分子剩余,分解速度也迅速降低,最后当所有分子都被分解后,即停下。
这里写图片描述

事实上,这是一个相当简单的图表。真实的曲线是不会如此平滑简单的。事实上,当胶片接收大量光照时,曲线将再次正增长。这种现象就是所谓的“曝光过度”。
这里写图片描述

你可能看出来了这是一个指数曲线,没错,就是指数曲线。下降速率和未分解化学物质的量成比例。
因此,参考上一个图表,第二幅图只是简单地将第一幅图翻转即可得到当一定量的光照射到透明胶片时它会变成什么样子和最终的相片会有多亮。
这里写图片描述
总之,上述可表示为一个简单的公式:
brightness = 1 - e-light
现在你知道了这个简单的公式,你现在有足够的能力来处理大范围亮度场景并让它在电脑屏幕上显示良好。不相信我?看看以下图片。这和之前的两张图是一样的场景,不过实施了曝光函数(Exposure Function)。太阳比天空更亮,天空比地面更亮,理应如此。所有的细节仍可见,没有失去的部分。
曝光控制(Exposure Control)
当然,相机如果没有曝光控制就会变得毫无用处,这个函数也是如此。击中胶片的光强量被简单表示为进入相机的光线、光圈和快门开启的时长的乘积。更进一步简化,我们忽略光圈和快速速度的区别,把它们简单表示为变量K,K越小,成像亮度约暗,反之亦然。
(light hitting film) = (light entering camera) * K

brightness = 1 - e-(light hitting film)
最后,我们来看一段伪代码:
function Expose(float light, float exposure)

return ( 1 - exp(light * exposure) )  *  255

end function

main procedure:

for every pixel on the screen

  calculate the amount of light entering the camera for this pixel, by Ray Tracing or whatever
  call this value L        

  PixelValue = Expose(L, K)

  plot this pixel with a brightness of PixelValue      

end of loop

end of main procedure

很神奇这个函数并没有大量使用。即便是诸如3D studio这种强大的渲染引擎,也不允许创建亮度大于255的光照。这种限制非常滑稽,因为在现实生活中,光线的亮度范围是很大的。
参考:
The Virtual Darkroom http://tangle.seas.gwu.edu/~geigel/vdr/
一个以相机胶片处理原理来生成黑白计算机图像的系统。该系统比上述简单的算法更高级,并且模拟grain和曝光过程。

原文地址:http://freespace.virgin.net/hugo.elias/graphics/x_posure.htm

2018-06-02 15:55:00 weixin_33985679 阅读数 480

众所周知,

图像方面的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

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

图像处理算法工程师

阅读数 14578