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

3D LUT(3D Lookup Table)是一种颜色映射技术,常用于图像和视频调色。通过建立颜色映射表进行插值逼近,简化大量颜色替换的操作。本文介绍了3D LUT的用途,如摄像机效果美化和影视后期调色,并提及在VSCO Cam中的应用。重点讨论了Trilinear_interpolation作为最常用的插值算法,并分享了一个基于FFmpeg的3D LUT算法实现示例代码,项目地址在https://github.com/cpuimage/Lut3D。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在前面的文章,我提到过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是标配。

相关资料可以参阅:

3D LUT调色:单反如何实现电影级调色。

 在VSCO Cam APP中滤镜效果每一档都是一个17*17*17的3d lut预设。

先上个图,大家感受一下。

只是一个例子,效果是看做预设的功底的。

那么3d lut 的实现具体是什么算法呢?

Nearest_interpolation

Trilinear_interpolation

Tetrahedral interpolation

 当然据我所知,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[4];
    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 (0);
    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[0], s->g - prev[1], s->b - prev[2]};
    const struct rgbvec c000 = lut3d->lut[prev[0]][prev[1]][prev[2]];
    const struct rgbvec c001 = lut3d->lut[prev[0]][prev[1]][next[2]];
    const struct rgbvec c010 = lut3d->lut[prev[0]][next[1]][prev[2]];
    const struct rgbvec c011 = lut3d->lut[prev[0]][next[1]][next[2]];
    const struct rgbvec c100 = lut3d->lut[next[0]][prev[1]][prev[2]];
    const struct rgbvec c101 = lut3d->lut[next[0]][prev[1]][next[2]];
    const struct rgbvec c110 = lut3d->lut[next[0]][next[1]][prev[2]];
    const struct rgbvec c111 = lut3d->lut[next[0]][next[1]][next[2]];
    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[0], s->g - prev[1], s->b - prev[2]};
    const struct rgbvec c000 = lut3d->lut[prev[0]][prev[1]][prev[2]];
    const struct rgbvec c111 = lut3d->lut[next[0]][next[1]][next[2]];
    struct rgbvec c;
    if (d.r > d.g) {
        if (d.g > d.b) {
            const struct rgbvec c100 = lut3d->lut[next[0]][prev[1]][prev[2]];
            const struct rgbvec c110 = lut3d->lut[next[0]][next[1]][prev[2]];
            c.r = (1 - d.r) * c000.r + (d.r - d.g) * c100.r + (d.g - d.b) * c110.r + (d.b) * c111.r;
            c.g = (1 - d.r) * c000.g + (d.r - d.g) * c100.g + (d.g - d.b) * c110.g + (d.b) * c111.g;
            c.b = (1 - 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[0]][prev[1]][prev[2]];
            const struct rgbvec
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值