自動曝光修復算法 附完整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
再來一個效果前後對比:
以上,權當拋磚引玉。
若有其他相關問題或者需求也可以郵件聯系俺探討。
郵箱地址是:
[email protected]
自動曝光修復算法 附完整C代碼