3D Lut 电影级调色算法 附完整C代码
在前面的文章,我提到过VSCO Cam 的胶片滤镜算法实现是3d lut。
那么3d lut 到底是个什么东西呢?
或者说它是用来做什么的?
长话短说,3d lut(全称 : 3D Lookup table )它是通过建立一个颜色映射表,对图像的色调进行重调的算法。
有用于摄像机的效果美化润色,例如一些所谓的数码相机之类的。
也有用于影视后期调色,渲染影视作品的颜色基调等等。
简单的说,你想要把图片上的一些颜色通过你自己的预设给替换掉。
例如红色换成白色,白色换成绿色。
当然这在现实中操作起来非常复杂。
因为 RGB888(8+8+8=24位色):
(2^8)*(2^8)*(2^8)=
256*256*256=16777216
有16M 种颜色,如果采用手工操作的方式一个一个颜色地换,那人还活不活了。
所以就有通过建立映射表进行插值达到逼近这种效果的算法。
它就是3d lut,当然也有2d lut,1d lut。
精度不一,效果不一。
例如:
调节亮度 可以认为是1d lut.
调节对比度 可以认为是 2d lut.
而调节整体的色调最佳肯定是3d lut.
当然2d lut 也是可以做到,但是精度就没有那么高了。
我之前也提到过,市面有不少app是采用2d LUT,毕竟精度不需要那么高。
2d够用了。
但是在摄影界,影视后期这一行当里,3d lut是标配。
相关资料可以参阅:
在VSCO Cam APP中滤镜效果每一档都是一个17*17*17的3d lut预设。
先上个图,大家感受一下。
只是一个例子,效果是看做预设的功底的。
那么3d lut 的实现具体是什么算法呢?
当然据我所知,Trilinear_interpolation 是用得最广泛的一种。
之前做APP滤镜的时候,调研过不少资料。
但是当时发现一些开源项目的实现是有问题的,插值算错坐标之类的。
有一次心血来潮,去翻了翻FFmpeg的代码,居然发现了它也有实现3d lut算法。
嗯,站在巨人的肩膀上。
抽了点时间对FFmpeg中的3d lut 进行了整理。
提取出它的算法,并编写示例。
当然未经过严格验证,应该存在一些小Bugs。
完整示例代码献上:
/*
* Copyright (c) 2013 Clément Bœsch
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* 3D Lookup table filter
*/
#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
#ifdef _MSC_VER
#endif
#ifndef MIN
#define MIN(a, b) ( (a) > (b) ? (b) : (a) )
#endif
#ifndef _NEAR
#define _NEAR(x) ( (int) ( (x) + .5) )
#endif
#ifndef PREV
#define PREV(x) ( (int) (x) )
#endif
#ifndef NEXT
#define NEXT(x) (MIN( (int) (x) + 1, lut3d->lutsize - 1 ) )
#endif
#ifndef R
#define R 0
#endif
#ifndef G
#define G 1
#endif
#ifndef B
#define B 2
#endif
#ifndef A
#define A 3
#endif
#ifndef MAX_LEVEL
#define MAX_LEVEL 64
#endif enum interp_mode {
INTERPOLATE_NEAREST,
INTERPOLATE_TRILINEAR,
INTERPOLATE_TETRAHEDRAL,
NB_INTERP_MODE
}; struct rgbvec {
float r, g, b;
}; /* 3D LUT don't often go up to level 32 */ typedef struct LUT3DContext {
uint8_t rgba_map[];
int step;
struct rgbvec lut[MAX_LEVEL][MAX_LEVEL][MAX_LEVEL];
int lutsize;
} LUT3DContext;
#ifdef _MSC_VER
int strcasecmp(const char *s1, char *s2) {
while (toupper((unsigned char)*s1) == toupper((unsigned char)*s2++))
if (*s1++ == 0x00)
return ();
return (toupper((unsigned char)*s1) - toupper((unsigned char) *--s2));
}
#endif static inline float lerpf(float v0, float v1, float f) {
return (v0 + (v1 - v0) * f);
} static inline struct rgbvec lerp(const struct rgbvec *v0, const struct rgbvec *v1, float f) {
struct rgbvec v = {
lerpf(v0->r, v1->r, f), lerpf(v0->g, v1->g, f), lerpf(v0->b, v1->b, f)
};
return (v);
} /**
* Get the nearest defined point
*/
static inline struct rgbvec interp_nearest(const LUT3DContext *lut3d,
const struct rgbvec *s) {
return (lut3d->lut[_NEAR(s->r)][_NEAR(s->g)][_NEAR(s->b)]);
} /**
* Interpolate using the 8 vertices of a cube
* @see https://en.wikipedia.org/wiki/Trilinear_interpolation
*/
static inline struct rgbvec interp_trilinear(const LUT3DContext *lut3d,
const struct rgbvec *s) {
const int prev[] = {PREV(s->r), PREV(s->g), PREV(s->b)};
const int next[] = {NEXT(s->r), NEXT(s->g), NEXT(s->b)};
const struct rgbvec d = {s->r - prev[], s->g - prev[], s->b - prev[]};
const struct rgbvec c000 = lut3d->lut[prev[]][prev[]][prev[]];
const struct rgbvec c001 = lut3d->lut[prev[]][prev[]][next[]];
const struct rgbvec c010 = lut3d->lut[prev[]][next[]][prev[]];
const struct rgbvec c011 = lut3d->lut[prev[]][next[]][next[]];
const struct rgbvec c100 = lut3d->lut[next[]][prev[]][prev[]];
const struct rgbvec c101 = lut3d->lut[next[]][prev[]][next[]];
const struct rgbvec c110 = lut3d->lut[next[]][next[]][prev[]];
const struct rgbvec c111 = lut3d->lut[next[]][next[]][next[]];
const struct rgbvec c00 = lerp(&c000, &c100, d.r);
const struct rgbvec c10 = lerp(&c010, &c110, d.r);
const struct rgbvec c01 = lerp(&c001, &c101, d.r);
const struct rgbvec c11 = lerp(&c011, &c111, d.r);
const struct rgbvec c0 = lerp(&c00, &c10, d.g);
const struct rgbvec c1 = lerp(&c01, &c11, d.g);
const struct rgbvec c = lerp(&c0, &c1, d.b);
return (c);
} /**
* Tetrahedral interpolation. Based on code found in Truelight Software Library paper.
* @see http://www.filmlight.ltd.uk/pdf/whitepapers/FL-TL-TN-0057-SoftwareLib.pdf
*/
static inline struct rgbvec interp_tetrahedral(const LUT3DContext *lut3d,
const struct rgbvec *s) {
const int prev[] = {PREV(s->r), PREV(s->g), PREV(s->b)};
const int next[] = {NEXT(s->r), NEXT(s->g), NEXT(s->b)};
const struct rgbvec d = {s->r - prev[], s->g - prev[], s->b - prev[]};
const struct rgbvec c000 = lut3d->lut[prev[]][prev[]][prev[]];
const struct rgbvec c111 = lut3d->lut[next[]][next[]][next[]];
struct rgbvec c;
if (d.r > d.g) {
if (d.g > d.b) {
const struct rgbvec c100 = lut3d->lut[next[]][prev[]][prev[]];
const struct rgbvec c110 = lut3d->lut[next[]][next[]][prev[]];
c.r = ( - d.r) * c000.r + (d.r - d.g) * c100.r + (d.g - d.b) * c110.r + (d.b) * c111.r;
c.g = ( - d.r) * c000.g + (d.r - d.g) * c100.g + (d.g - d.b) * c110.g + (d.b) * c111.g;
c.b = ( - d.r) * c000.b + (d.r - d.g) * c100.b + (d.g - d.b) * c110.b + (d.b) * c111.b;
} else if (d.r > d.b) {
const struct rgbvec c100 = lut3d->lut[next[]][prev[]][prev[]];
const struct rgbvec c101 = lut3d->lut[next[]][prev[]][next[]];
c.r = ( - d.r) * c000.r + (d.r - d.b) * c100.r + (d.b - d.g) * c101.r + (d.g) * c111.r;
c.g = ( - d.r) * c000.g + (d.r - d.b) * c100.g + (d.b - d.g) * c101.g + (d.g) * c111.g;
c.b = ( - d.r) * c000.b + (d.r - d.b) * c100.b + (d.b - d.g) * c101.b + (d.g) * c111.b;
} else {
const struct rgbvec c001 = lut3d->lut[prev[]][prev[]][next[]];
const struct rgbvec c101 = lut3d->lut[next[]][prev[]][next[]];
c.r = ( - d.b) * c000.r + (d.b - d.r) * c001.r + (d.r - d.g) * c101.r + (d.g) * c111.r;
c.g = ( - d.b) * c000.g + (d.b - d.r) * c001.g + (d.r - d.g) * c101.g + (d.g) * c111.g;
c.b = ( - d.b) * c000.b + (d.b - d.r) * c001.b + (d.r - d.g) * c101.b + (d.g) * c111.b;
}
} else {
if (d.b > d.g) {
const struct rgbvec c001 = lut3d->lut[prev[]][prev[]][next[]];
const struct rgbvec c011 = lut3d->lut[prev[]][next[]][next[]];
c.r = ( - d.b) * c000.r + (d.b - d.g) * c001.r + (d.g - d.r) * c011.r + (d.r) * c111.r;
c.g = ( - d.b) * c000.g + (d.b - d.g) * c001.g + (d.g - d.r) * c011.g + (d.r) * c111.g;
c.b = ( - d.b) * c000.b + (d.b - d.g) * c001.b + (d.g - d.r) * c011.b + (d.r) * c111.b;
} else if (d.b > d.r) {
const struct rgbvec c010 = lut3d->lut[prev[]][next[]][prev[]];
const struct rgbvec c011 = lut3d->lut[prev[]][next[]][next[]];
c.r = ( - d.g) * c000.r + (d.g - d.b) * c010.r + (d.b - d.r) * c011.r + (d.r) * c111.r;
c.g = ( - d.g) * c000.g + (d.g - d.b) * c010.g + (d.b - d.r) * c011.g + (d.r) * c111.g;
c.b = ( - d.g) * c000.b + (d.g - d.b) * c010.b + (d.b - d.r) * c011.b + (d.r) * c111.b;
} else {
const struct rgbvec c010 = lut3d->lut[prev[]][next[]][prev[]];
const struct rgbvec c110 = lut3d->lut[next[]][next[]][prev[]];
c.r = ( - d.g) * c000.r + (d.g - d.r) * c010.r + (d.r - d.b) * c110.r + (d.b) * c111.r;
c.g = ( - d.g) * c000.g + (d.g - d.r) * c010.g + (d.r - d.b) * c110.g + (d.b) * c111.g;
c.b = ( - d.g) * c000.b + (d.g - d.r) * c010.b + (d.r - d.b) * c110.b + (d.b) * c111.b;
}
}
return (c);
} /**
* Locale-independent conversion of ASCII isspace.
*/
int _isspace(int c) {
return (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' ||
c == '\v');
} /**
* Clip a signed integer value into the 0-65535 range.
* @param a value to clip
* @return clipped value
*/
static uint16_t clip_uint16(int a) {
if (a & (~0xFFFF))
return ((~a) >> );
else return (a);
} /**
* Clip a signed integer value into the 0-255 range.
* @param a value to clip
* @return clipped value
*/
static uint8_t clip_uint8(int a) {
if (a & (~0xFF))
return ((~a) >> );
else return (a);
} static unsigned clip_uintp2(int a, int p) {
if (a & ~(( << p) - ))
return (-a >> & (( << p) - ));
else return (a);
} #define DEFINE_INTERP_FUNC_PLANAR(name, nbits, depth) \
static int interp_ ## nbits ## _ ## name ## _p ## depth( const LUT3DContext * lut3d, uint8_t * indata_g, uint8_t * indata_b, uint8_t * indata_r, uint8_t * indata_a, uint8_t * outdata_g, uint8_t * outdata_b, uint8_t * outdata_r, uint8_t * outdata_a, int width, int height, int linesize ) \
{ \
int x, y; \
int direct = (outdata_g == indata_g); \
uint8_t *grow = outdata_g ; \
uint8_t *brow = outdata_b ; \
uint8_t *rrow = outdata_r ; \
uint8_t *arow = outdata_a ; \
const uint8_t *srcgrow = indata_g ; \
const uint8_t *srcbrow = indata_b ; \
const uint8_t *srcrrow = indata_r ; \
const uint8_t *srcarow = indata_a ; \
const float scale = (.f / ( ( << (depth) ) - ) ) * (lut3d->lutsize - ); \
for ( y = ; y < height; y++ ) { \
uint ## nbits ## _t * dstg = (uint ## nbits ## _t *)grow; \
uint ## nbits ## _t * dstb = (uint ## nbits ## _t *)brow; \
uint ## nbits ## _t * dstr = (uint ## nbits ## _t *)rrow; \
uint ## nbits ## _t * dsta = (uint ## nbits ## _t *)arow; \
const uint ## nbits ## _t *srcg = (const uint ## nbits ## _t *)srcgrow; \
const uint ## nbits ## _t *srcb = (const uint ## nbits ## _t *)srcbrow; \
const uint ## nbits ## _t *srcr = (const uint ## nbits ## _t *)srcrrow; \
const uint ## nbits ## _t *srca = (const uint ## nbits ## _t *)srcarow; \
for ( x = ; x < width; x++ ) { \
const struct rgbvec scaled_rgb = { srcr[x] * scale, \
srcg[x] * scale, \
srcb[x] * scale }; \
struct rgbvec vec = interp_ ## name( lut3d, &scaled_rgb ); \
dstr[x] = clip_uintp2( vec.r * (float) ( ( << (depth) ) - ), depth ); \
dstg[x] = clip_uintp2( vec.g * (float) ( ( << (depth) ) - ), depth ); \
dstb[x] = clip_uintp2( vec.b * (float) ( ( << (depth) ) - ), depth ); \
if ( !direct && linesize ) \
dsta[x] = srca[x]; \
} \
grow += linesize; \
brow += linesize; \
rrow += linesize; \
arow += linesize; \
srcgrow += linesize; \
srcbrow += linesize; \
srcrrow += linesize; \
srcarow += linesize; \
} \
return ; \
} DEFINE_INTERP_FUNC_PLANAR(nearest, , ) DEFINE_INTERP_FUNC_PLANAR(trilinear, , ) DEFINE_INTERP_FUNC_PLANAR(tetrahedral, , ) DEFINE_INTERP_FUNC_PLANAR(nearest, , ) DEFINE_INTERP_FUNC_PLANAR(trilinear, , ) DEFINE_INTERP_FUNC_PLANAR(tetrahedral, , ) DEFINE_INTERP_FUNC_PLANAR(nearest, , ) DEFINE_INTERP_FUNC_PLANAR(trilinear, , ) DEFINE_INTERP_FUNC_PLANAR(tetrahedral, , ) DEFINE_INTERP_FUNC_PLANAR(nearest, , ) DEFINE_INTERP_FUNC_PLANAR(trilinear, , ) DEFINE_INTERP_FUNC_PLANAR(tetrahedral, , ) DEFINE_INTERP_FUNC_PLANAR(nearest, , ) DEFINE_INTERP_FUNC_PLANAR(trilinear, , ) DEFINE_INTERP_FUNC_PLANAR(tetrahedral, , ) DEFINE_INTERP_FUNC_PLANAR(nearest, , ) DEFINE_INTERP_FUNC_PLANAR(trilinear, , ) DEFINE_INTERP_FUNC_PLANAR(tetrahedral, , ) #define DEFINE_INTERP_FUNC(name, nbits) \
static int interp_ ## nbits ## _ ## name( LUT3DContext * lut3d, const uint8_t * indata, uint8_t * outdata, int width, int height, int linesize ) \
{ \
int x, y; \
const int direct = outdata == indata; \
const int step = lut3d->step; \
const uint8_t r = lut3d->rgba_map[R]; \
const uint8_t g = lut3d->rgba_map[G]; \
const uint8_t b = lut3d->rgba_map[B]; \
const uint8_t a = lut3d->rgba_map[A]; \
uint8_t *dstrow = outdata; \
const uint8_t *srcrow = indata; \
const float scale = (.f / ( ( << nbits) - ) ) * (lut3d->lutsize - ); \
\
for ( y = ; y < height; y++ ) { \
uint ## nbits ## _t * dst = (uint ## nbits ## _t *)dstrow; \
const uint ## nbits ## _t *src = (const uint ## nbits ## _t *)srcrow; \
for ( x = ; x < width * step; x += step ) { \
const struct rgbvec scaled_rgb = { src[x + r] * scale, \
src[x + g] * scale, \
src[x + b] * scale }; \
struct rgbvec vec = interp_ ## name( lut3d, &scaled_rgb ); \
dst[x + r] = clip_uint ## nbits( vec.r * (float) ( ( << nbits) - ) ); \
dst[x + g] = clip_uint ## nbits( vec.g * (float) ( ( << nbits) - ) ); \
dst[x + b] = clip_uint ## nbits( vec.b * (float) ( ( << nbits) - ) ); \
if ( !direct && step == ) \
dst[x + a] = src[x + a]; \
} \
dstrow += linesize; \
srcrow += linesize; \
} \
return ; \
} DEFINE_INTERP_FUNC(nearest, ) DEFINE_INTERP_FUNC(trilinear, ) DEFINE_INTERP_FUNC(tetrahedral, ) DEFINE_INTERP_FUNC(nearest, ) DEFINE_INTERP_FUNC(trilinear, ) DEFINE_INTERP_FUNC(tetrahedral, ) static int skip_line(const char *p) {
while (*p && _isspace(*p))
p++;
return (!*p || *p == '#');
} #ifndef NEXT_LINE
#define NEXT_LINE(loop_cond) do { \
if ( !fgets( line, sizeof(line), f ) ) { \
printf( "Unexpected EOF\n" ); fclose( f ); if ( lut3d ) free( lut3d ); \
return NULL; \
} \
} while ( loop_cond )
#endif #ifndef MAX_LINE_SIZE
#define MAX_LINE_SIZE 512
#endif /* Basically r g and b float values on each line, with a facultative 3DLUTSIZE
* directive; seems to be generated by Davinci */
LUT3DContext *parse_dat(char *filename) {
FILE *f = fopen(filename, "r");
if (f == NULL) return NULL;
LUT3DContext *lut3d = NULL;
char line[MAX_LINE_SIZE];
int i, j, k, size; int lutsize = size = ; NEXT_LINE(skip_line(line));
if (!strncmp(line, "3DLUTSIZE ", )) {
size = strtol(line + , NULL, );
if (size < || size > MAX_LEVEL) {
printf("Too large or invalid 3D LUT size\n");
fclose(f);
return (NULL);
}
lutsize = size;
NEXT_LINE(skip_line(line));
}
if (size != && lut3d == NULL) {
lut3d = (LUT3DContext *) calloc(, sizeof(LUT3DContext));
}
lut3d->lutsize = lutsize;
for (k = ; k < size; k++) {
for (j = ; j < size; j++) {
for (i = ; i < size; i++) {
struct rgbvec *vec = &lut3d->lut[k][j][i];
if (k != || j != || i != )
NEXT_LINE(skip_line(line));
if (sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != ) {
fclose(f);
free(lut3d);
return (NULL);
}
}
}
}
fclose(f);
return (lut3d);
} LUT3DContext *parse_cube(char *filename) {
FILE *f = fopen(filename, "r");
if (f == NULL) return NULL;
char line[MAX_LINE_SIZE];
float min[] = {0.0, 0.0, 0.0};
float max[] = {1.0, 1.0, 1.0};
int lutsize = ;
LUT3DContext *lut3d = NULL;
while (fgets(line, sizeof(line), f)) {
if (!strncmp(line, "LUT_3D_SIZE ", )) {
int i, j, k;
const int size = strtol(line + , NULL, );
if (size < || size > MAX_LEVEL) {
printf("Too large or invalid 3D LUT size\n");
fclose(f);
return (NULL);
}
lutsize = size;
if (size != && lut3d == NULL) {
lut3d = (LUT3DContext *) calloc(, sizeof(LUT3DContext));
}
lut3d->lutsize = lutsize;
for (k = ; k < size; k++) {
for (j = ; j < size; j++) {
for (i = ; i < size; i++) {
struct rgbvec *vec = &lut3d->lut[i][j][k]; do {
try_again:
NEXT_LINE();
if (!strncmp(line, "DOMAIN_", )) {
float *vals = NULL;
if (!strncmp(line + , "MIN ", ))
vals = min;
else if (!strncmp(line + , "MAX ", ))
vals = max;
if (!vals) {
fclose(f);
free(lut3d);
return (NULL);
} sscanf(line + , "%f %f %f", vals, vals + , vals + );
//printf("min: %f %f %f | max: %f %f %f\n", min[0], min[1], min[2], max[0], max[1], max[2]);
goto try_again;
}
} while (skip_line(line));
if (sscanf(line, "%f %f %f", &vec->r, &vec->g, &vec->b) != ) {
fclose(f);
free(lut3d);
return (NULL);
}
vec->r *= max[] - min[];
vec->g *= max[] - min[];
vec->b *= max[] - min[];
}
}
}
break;
}
}
fclose(f);
return (lut3d);
} /* Assume 17x17x17 LUT with a 16-bit depth */
LUT3DContext *parse_3dl(char *filename) {
FILE *f = fopen(filename, "r");
if (f == NULL) return NULL;
char line[MAX_LINE_SIZE];
int i, j, k;
const int size = ;
const float scale = * * ; int lutsize = size;
LUT3DContext *lut3d = (LUT3DContext *) calloc(, sizeof(LUT3DContext)); lut3d->lutsize = lutsize;
NEXT_LINE(skip_line(line));
for (k = ; k < size; k++) {
for (j = ; j < size; j++) {
for (i = ; i < size; i++) {
int r, g, b;
struct rgbvec *vec = &lut3d->lut[k][j][i]; NEXT_LINE(skip_line(line));
if (sscanf(line, "%d %d %d", &r, &g, &b) != ) {
fclose(f);
free(lut3d);
return (NULL);
}
vec->r = r / scale;
vec->g = g / scale;
vec->b = b / scale;
}
}
}
fclose(f);
return (lut3d);
} /* Pandora format */
LUT3DContext *parse_m3d(char *filename) {
FILE *f = fopen(filename, "r");
if (f == NULL) return NULL;
float scale;
int i, j, k, size, in = -, out = -;
char line[MAX_LINE_SIZE];
uint8_t rgb_map[] = {, , }; while (fgets(line, sizeof(line), f)) {
if (!strncmp(line, "in", ))
in = strtol(line + , NULL, );
else if (!strncmp(line, "out", ))
out = strtol(line + , NULL, );
else if (!strncmp(line, "values", )) {
const char *p = line + ;
#define SET_COLOR(id) do { \
while ( _isspace( *p ) ) \
p++; \
switch ( *p ) { \
case 'r': rgb_map[id] = ; break; \
case 'g': rgb_map[id] = ; break; \
case 'b': rgb_map[id] = ; break; \
} \
while ( *p && !_isspace( *p ) ) \
p++; \
} \
while ( )
SET_COLOR();
SET_COLOR();
SET_COLOR();
break;
}
} if (in == - || out == -) {
printf("in and out must be defined\n");
fclose(f);
return (NULL);
}
if (in < || out < ||
in > MAX_LEVEL * MAX_LEVEL * MAX_LEVEL ||
out > MAX_LEVEL * MAX_LEVEL * MAX_LEVEL) {
printf("invalid in (%d) or out (%d)\n", in, out);
fclose(f);
return (NULL);
}
for (size = ; size * size * size < in; size++);
{}
int lutsize = size;
scale = .f / (out - );
LUT3DContext *lut3d = NULL;
if (size != ) {
lut3d = (LUT3DContext *) calloc(, sizeof(LUT3DContext));
}
lut3d->lutsize = lutsize;
for (k = ; k < size; k++) {
for (j = ; j < size; j++) {
for (i = ; i < size; i++) {
struct rgbvec *vec = &lut3d->lut[k][j][i]; float val[];
NEXT_LINE();
if (sscanf(line, "%f %f %f", val, val + , val + ) != ) {
fclose(f);
free(lut3d);
return (NULL);
}
vec->r = val[rgb_map[]] * scale;
vec->g = val[rgb_map[]] * scale;
vec->b = val[rgb_map[]] * scale;
}
}
}
fclose(f);
return (lut3d);
} LUT3DContext *lut3d_load(char *filename) {
int ret = ;
const char *ext;
if (!filename) {
return ();
}
LUT3DContext *lut3d = NULL; ext = strrchr(filename, '.');
if (!ext) {
printf("Unable to guess the format from the extension\n");
goto end;
}
ext++; if (!strcasecmp(ext, "dat")) {
lut3d = parse_dat(filename);
} else if (!strcasecmp(ext, "3dl")) {
lut3d = parse_3dl(filename);
} else if (!strcasecmp(ext, "cube")) {
lut3d = parse_cube(filename);
} else if (!strcasecmp(ext, "m3d")) {
lut3d = parse_m3d(filename);
} else {
printf("Unrecognized '.%s' file type\n", ext);
ret = -;
}
if (!ret && !lut3d->lutsize) {
printf("3D LUT is empty\n");
}
end:
return (lut3d);
} typedef int (action_planar_func)(const LUT3DContext *lut3d, uint8_t *indata_g, uint8_t *indata_b, uint8_t *indata_r,
uint8_t *indata_a, uint8_t *outdata_g, uint8_t *outdata_b, uint8_t *outdata_r,
uint8_t *outdata_a, int width, int height, int linesize); static int apply_planar_lut(char *filename, uint8_t *indata_g, uint8_t *indata_b, uint8_t *indata_r,
uint8_t *indata_a, uint8_t *outdata_g, uint8_t *outdata_b, uint8_t *outdata_r,
uint8_t *outdata_a, int width, int height, int linesize, int depth, int interpolation
) {
action_planar_func *interp_func = ; LUT3DContext *lut3d = lut3d_load(filename);
if (lut3d == NULL)
return (-);
lut3d->step = depth;
int planar = ; \ #define SET_PLANAR_FUNC(name) do { \
if ( planar ) { \
switch ( depth ) { \
case : interp_func = interp_8_ ## name ## _p8; break; \
case : interp_func = interp_16_ ## name ## _p9; break; \
case : interp_func = interp_16_ ## name ## _p10; break; \
case : interp_func = interp_16_ ## name ## _p12; break; \
case : interp_func = interp_16_ ## name ## _p14; break; \
case : interp_func = interp_16_ ## name ## _p16; break; \
} \
} \
} while ( ) switch (interpolation) {
case INTERPOLATE_NEAREST:
SET_PLANAR_FUNC(nearest);
break;
case INTERPOLATE_TRILINEAR:
SET_PLANAR_FUNC(trilinear);
break;
case INTERPOLATE_TETRAHEDRAL:
SET_PLANAR_FUNC(tetrahedral);
break;
default:
assert();
}
interp_func(lut3d, indata_g, indata_b, indata_r,
indata_a, outdata_g, outdata_b, outdata_r,
outdata_a, width, height, linesize);
return ();
} typedef int (action_func)(LUT3DContext *lut3d, const uint8_t *indata, uint8_t *outdata, int width, int height,
int linesize); static int
apply_lut(char *filename, const uint8_t *indata, uint8_t *outdata, int width, int height, int linesize, int depth,
int interpolation, int is16bit) {
action_func *interp_func = ; LUT3DContext *lut3d = lut3d_load(filename);
if (lut3d == NULL)
return (-);
lut3d->rgba_map[0] = 0;
lut3d->rgba_map[1] = 1;
lut3d->rgba_map[2] = 2;
lut3d->rgba_map[3] = 3;
lut3d->step = depth; #define SET_FUNC(name) do { \
if ( is16bit ) { interp_func = interp_16_ ## name; \
} else { interp_func = interp_8_ ## name; } \
} while ( ) switch (interpolation) {
case INTERPOLATE_NEAREST:
SET_FUNC(nearest);
break;
case INTERPOLATE_TRILINEAR:
SET_FUNC(trilinear);
break;
case INTERPOLATE_TETRAHEDRAL:
SET_FUNC(tetrahedral);
break;
default:
assert();
}
interp_func(lut3d, indata, outdata, width, height, linesize);
free(lut3d);
return ();
} 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 main(int argc, char **argv) {
printf("lut 3d demo\n ");
printf("blog:http://cpuimage.cnblogs.com/ \n "); if (argc < ) {
printf("usage: %s 3dlut image \n ", argv[]);
printf("eg: %s 3DLUT d:\\image.jpg \n ", argv[]); return ();
}
char *lutfile = argv[];
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, Width * Channels * Height);
} else {
printf("load: %s fail!\n ", szfile);
}
startTime = now(); int is16bit = ;
// INTERPOLATE_NEAREST
// INTERPOLATE_TRILINEAR
// INTERPOLATE_TETRAHEDRAL
int interp_mode = INTERPOLATE_TETRAHEDRAL;
apply_lut(lutfile, inputImage, outputImg, Width, Height, Width * Channels, Channels, interp_mode,
is16bit);
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/Lut3D
命令行参数:
lut3d 3d预设文件 图片路径
例如: lut3d ../god.cube ../sample.jpg
用cmake即可进行编译示例代码,详情见CMakeLists.txt。
算法细节就不展开说了,
若有其他相关问题或者需求也可以邮件联系俺探讨。
邮箱地址是:
gaozhihan@vip.qq.com
3D Lut 电影级调色算法 附完整C代码的更多相关文章
- 音频降噪算法 附完整C代码
降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法和音频算法 都有其共通点. 图像是偏向 空间 处理,例如图片中的某个区域. 图像很多时候是以二维数据 ...
- mser 最大稳定极值区域(文字区域定位)算法 附完整C代码
mser 的全称:Maximally Stable Extremal Regions 第一次听说这个算法时,是来自当时部门的一个同事, 提及到他的项目用它来做文字区域的定位,对这个算法做了一些优化. ...
- 基于RNN的音频降噪算法 (附完整C代码)
前几天无意间看到一个项目rnnoise. 项目地址: https://github.com/xiph/rnnoise 基于RNN的音频降噪算法. 采用的是 GRU/LSTM 模型. 阅读下训练代码,可 ...
- 音频自动增益 与 静音检测 算法 附完整C代码
前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...
- 音频自动增益 与 静音检测 算法 附完整C代码【转】
转自:https://www.cnblogs.com/cpuimage/p/8908551.html 前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用 ...
- 自动曝光修复算法 附完整C代码
众所周知, 图像方面的3A算法有: AF自动对焦(Automatic Focus)自动对焦即调节摄像头焦距自动得到清晰的图像的过程 AE自动曝光(Automatic Exposure)自动曝光的是为了 ...
- 基于傅里叶变换的音频重采样算法 (附完整c代码)
前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲, ...
- 磨皮美颜算法 附完整C代码
前言 2017年底时候写了这篇<集 降噪 美颜 虚化 增强 为一体的极速图像润色算法 附Demo程序> 这也算是学习过程中比较有成就感的一个算法. 自2015年做算法开始到今天,还有个把月 ...
- 图片文档倾斜矫正算法 附完整c代码
2年前在学习图像算法的时候看到一个文档倾斜矫正的算法. 也就是说能将一些文档图像进行旋转矫正, 当然这个算法一般用于一些文档扫描软件做后处理 或者用于ocr 文字识别做前处理. 相关的关键词: 抗倾斜 ...
随机推荐
- MySQL学习笔记_10_MySQL高级操作(下)
MySQL高级操作(下) 五.MySQL预处理语句 1.设置预处理stmt,传递一个数据作为where的判断条件 prepare stmt from "select * from table ...
- 【Android 应用开发】分析各种Android设备屏幕分辨率与适配 - 使用大量真实安卓设备采集真实数据统计
.主要是为了总结一下 对这些概念有个直观的认识; . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/198 ...
- mysql 备份和恢复的两条命令
压缩备份: 1.mysqldump -h localhost -u root -p dbname | gzip > dbname.sql.gz 压缩恢复: 1.gunzip < dbnam ...
- MFC中使用SDL播放音频没有声音的解决方法
本文所说的音频是指的纯音频,不包含视频的那种. 在控制台中使用SDL播放音频,一般情况下不会有问题. 但是在MFC中使用SDL播放音频的时候,会出现没有声音的情况.经过长时间探索,没有找到特别好的解决 ...
- cocos2d 从v1.x升级到v2.x需要注意的几个地方
首先v1.x一些CCNode定位函数实现的有问题,导致返回的CCPoint的x坐标不正确(超出320后无变化),怀疑是其对屏幕旋转判断的不正确;而且这种现象在iOS 7.1之前的模拟器中运行都正常,在 ...
- C语言之鞍点的查找
鞍点(Saddle point)在微分方程中,沿着某一方向是稳定的,另一条方向是不稳定的奇点,叫做鞍点.在泛函中,既不是极大值点也不是极小值点的临界点,叫做鞍点.在矩阵中,一个数在所在行中是最大值,在 ...
- iOS中判断照片和相机权限
1.照片权限判断 在iOS6之后,app中使用照片(即自带相册)需要用户权限验证,所以我们可以做一个权限判断给出友好的提示或者界面效果. 相册判断需要导入 <AssetsLibrary/Asse ...
- LeetCode(41)-Rectangle Area
题目: Find the total area covered by two rectilinear rectangles in a 2D plane. Each rectangle is defin ...
- Oracle创建视图view权限不足问题剖析
问题: 使用USER1等其他用户登录Oracle以后,创建视图,提示"权限不够",怎么解决? 这是因为USER1这个帐户目前没有创建视图的权限. 解决方法为: 首先使用system ...
- .net framework 4 线程安全概述
线程安全:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的.早期的时候, ...