Metal每日分享,LUT查找滤镜效果

news2025/1/11 11:02:08

本案例的目的是理解如何用Metal实现LUT颜色查找表滤镜,通过将颜色值存储在一张表中,在需要的时候通过索引在这张表上找到对应的颜色值,将原有色值替换成查找表中的色值;

总结就是一种针对色彩空间的管理和转换技术,LUT 就是一个 RGB 组合到另一个 RGB 组合的映射关系表;


Demo

  • HarbethDemo地址

实操代码

// LUT查找滤镜
let filter = C7LookupTable.init(image: R.image("lut_abao"))

// 方案1:
let dest = BoxxIO.init(element: originImage, filter: filter)
ImageView.image = try? dest.output()

dest.filters.forEach {
    NSLog("%@", "\($0.parameterDescription)")
}

// 方案2:
ImageView.image = try? originImage.make(filter: filter)

// 方案3:
ImageView.image = originImage ->> filter

实现原理

  • 过滤器

这款滤镜采用并行计算编码器设计.compute(kernel: "C7LookupTable"),参数因子[intensity]

对外开放参数

  • intensity: 强度,其实就是调整mix混合平均值。
/// LUT映射滤镜
public struct C7LookupTable: C7FilterProtocol {
    
    public let lookupImage: C7Image?
    public let lookupTexture: MTLTexture?
    public var intensity: Float = 1.0
    
    public var modifier: Modifier {
        return .compute(kernel: "C7LookupTable")
    }
    
    public var factors: [Float] {
        return [intensity]
    }
    
    public var otherInputTextures: C7InputTextures {
        return lookupTexture == nil ? [] : [lookupTexture!]
    }
    
    public init(image: C7Image?) {
        self.lookupImage = image
        self.lookupTexture = image?.cgImage?.mt.newTexture()
    }
    
    public init(name: String) {
        self.init(image: R.image(name))
    }
}
  • 着色器

1、用蓝色值计算正方形的位置,得到quad1和quad2;
2、根据红色值和绿色值计算对应位置在整个纹理的坐标,得到texPos1和texPos2;
3、根据texPos1和texPos2读取映射结果newColor1和newColor2,再用蓝色值的小数部分进行mix操作;

kernel void C7LookupTable(texture2d<half, access::write> outputTexture [[texture(0)]],
                          texture2d<half, access::read> inputTexture [[texture(1)]],
                          texture2d<half, access::sample> lookupTexture [[texture(2)]],
                          constant float *intensity [[buffer(0)]],
                          uint2 grid [[thread_position_in_grid]]) {
    const half4 inColor = inputTexture.read(grid);
    const half blueColor = inColor.b * 63.0h; // 蓝色部分[0, 63] 共64种
    
    // 通过蓝色计算两个方格quad1,quad2
    half2 quad1;
    quad1.y = floor(floor(blueColor) / 8.0h);
    quad1.x = floor(blueColor) - (quad1.y * 8.0h);
    
    half2 quad2;
    quad2.y = floor(ceil(blueColor) / 8.0h);
    quad2.x = ceil(blueColor) - (quad2.y * 8.0h);
    
    const float A = 0.125;
    const float B = 0.5 / 512.0;
    const float C = 0.125 - 1.0 / 512.0;
    
    float2 texPos1; // 计算颜色(r,b,g)在第一个正方形中对应位置
    texPos1.x = A * quad1.x + B + C * inColor.r;
    texPos1.y = A * quad1.y + B + C * inColor.g;
    
    float2 texPos2;
    texPos2.x = A * quad2.x + B + C * inColor.r;
    texPos2.y = A * quad2.y + B + C * inColor.g;
    
    constexpr sampler quadSampler(mag_filter::linear, min_filter::linear);
    const half4 newColor1 = lookupTexture.sample(quadSampler, texPos1);
    const half4 newColor2 = lookupTexture.sample(quadSampler, texPos2);
    
    const half4 newColor = mix(newColor1, newColor2, fract(blueColor));
    const half4 outColor = half4(mix(inColor, half4(newColor.rgb, inColor.a), half(*intensity)));
    
    outputTexture.write(outColor, grid);
}

1、通过蓝色计算两个方格quad1,quad2

half2 quad1;
quad1.y = floor(floor(blueColor) / 8.0h);
quad1.x = floor(blueColor) - (quad1.y * 8.0h);

half2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0h);
quad2.x = ceil(blueColor) - (quad2.y * 8.0h);

--------------
比如 inColor(0.4, 0.6, 0.2), 先确定第一个方格:
    
inColor.b = 0.2,blueColor = 0.2 * 63 = 12.6
即为第12个,第13个方格,但是我们要计算它坐在行和列,
floor(12.6) = 12, floor(12 / 8.0h) = 1,即第一行;
floor(blueColor) - (quad1.y * 8.0h) = floor(12.6) - (1 * 8) = 4,即第4列;

同理可以算出第二个方格为第1行,第5列
//ceil 向下取整,ceil(12.6) = 13, 
解决跨行时计算问题,比如blueColor = 7.6,则取第7,8个方格,他们不在同一行

2、计算映射后颜色所在两个方格的位置的归一化纹理坐标

const float A = 0.125;
const float B = 0.5 / 512.0;
const float C = 0.125 - 1.0 / 512.0;

float2 texPos1; // 计算颜色(r,b,g)在第一个正方形中对应位置
texPos1.x = A * quad1.x + B + C * inColor.r;
texPos1.y = A * quad1.y + B + C * inColor.g;

float2 texPos2;
texPos2.x = A * quad2.x + B + C * inColor.r;
texPos2.y = A * quad2.y + B + C * inColor.g;

--------------
(quad1.x * 0.125)表示行归一化的坐标,
(quad1.y * 0.125)表示列归一化的坐标,一共8行,每一行的长度为1/8 = 0.125,一共8列,每一列的长度为1/8 = 0.125;
(inColor.r * 0.125)表示一个方格里红色的位置,因为一个方格长度为0.125,r从0~1;绿色同理;

需要留意的是这里有个0.5/512 和 1.0/512;
0.5/512 是为了取点的中间值,一个点长度为1,总长度512,取点的中间值,即为0.5/512;
1.0/512 是因为计算texPos2.x时,单独对于一个方格来说,是从0~63,所以为63/512,即0.125 - 1.0 / 512;

3、计算映射后颜色

// 使用GPU采样器对纹理采样,取出LUT基准图上对于的 R G 色值
constexpr sampler quadSampler(mag_filter::linear, min_filter::linear);
const half4 newColor1 = lookupTexture.sample(quadSampler, texPos1);
const half4 newColor2 = lookupTexture.sample(quadSampler, texPos2);

4、混合颜色

// 线性取一个平均值,mix 方法根据 b 分量进行两个像素值的混合
const half4 newColor = mix(newColor1, newColor2, fract(blueColor));
// mix(x, y, a); 取x,y的线性混合,x(1-a)+ya
const half4 outColor = half4(mix(inColor, half4(newColor.rgb, inColor.a), half(*intensity))); 

LUT图介绍

LUT图是一张512×512大小的图片,分为64个8×8的小区域,每个小区域对应一个B值(0 ~ 255,间隔4),小区域内的每个像素点对应一组R和G值(0 ~ 255,间隔为4)。

使用时,获取原图某个像素点的值,通过颜色查找,替换为对应的滤镜颜色值。

lut_abao.png

从图可以看出:

  • 8x8的方块组成
  • 整体上看每个方块左上角从左上往右下由黑变蓝
  • 单独每个方块的右上角是红色为主
  • 单独每个方块的左下角是绿色为主

这是一个64x64x64颗粒度的LUT设计,总的方格大小为512x512,8x8=64个方格,所以每个方格大小为64x64;

64个方格,每个方格大小为64x64,所以叫做64x64x64颗粒度的设计。因为颜色值的范围为0~255,即256个取值,将256个取值归化到64;

从左上到右下(可以想作z方向),越来越蓝,蓝色值B从0~255,代表用来查找的B,即LUT(R1,G1,B1) = (R2,G2,B2)中的B1;
每一个方格里,从左往右(x方向),红色值R从0~255,代表用来查找的R,即LUT(R1,G1,B1) = (R2,G2,B2)中的R1;
每一个方格里,从上往下(y方向),绿色值G从0~255,代表用来查找的G,即LUT(R1,G1,B1) = (R2,G2,B2)中的G1;

因为一个颜色分量是0~255,所以一个方格表示的蓝色范围为4,比如最左上的方格蓝色为0~4,
查找时,如果有某个像素的蓝色值在0~4之间,则一定是在第一个方格里查找其映射后的颜色;

Example:

  • 查找像素点归一化后的纯蓝色(0,0,1)的映射后的颜色;
  • 使用蓝色B定位方格数
n = 1(B值) * 63(一共64个方格,从第0个算起) = 63

Answer: 定位的方格n是第63个

  • 定位在方格里的位置,使用R,G定位位置x,y
x = 0(R值) * 63(每个方格大小为 64 * 64) = 0
y = 0(G值) * 63(每个方格大小为 64 * 64) = 0

Answer: 方格的(0,0)位置为要定位的x,y

  • 定位在整个图中位置
Py = floor(n/8) * 64 + y = 7 * 64 + 0 = 448;
Px = [n - floor(n/8)*8] * 64 + x = [63-7*8] * 64 + 0 = 448;
P1 = (448, 448)

其中floor(n/8)代表位置所在行,每一行的长度为64,y为方格里的G定位的位置;
[n - floor(n/8) * 8]代表位置所在列数,每一列的长度为64,x为方格里的R定位的位置;
floor为向下取整(解决跨行时计算问题),ceil为向上取整。比如2.3, floor(2.3) = 2; ceil(2.3) = 3;

Answer: 方格大小为512x512,位置为P = (448, 448), 归一化后为(7/8, 7/8)
So: 颜色值(0, 0, 1)的位置确实在第63个方格的左上角;

查找方式

LUT分为1D和3D,本质的区别在于索引的输出所需要的索引数

用公式形式看看区别,先设置Ri、Gi、Bi为输入值,Ro、Go、Bo为输出值,LUT标准的转换方法为FuncLUT;

  • 1D LUT公式
    Ro = FuncLUT(Ri)
    Go = FuncLUT(Gi)
    Bo = FuncLUT(Bi)

从公式可以看出,各个数值之间独立

  • 3D LUT公式
    Ro = FuncLUT(Ri, Gi, Bi)
    Go = FuncLUT(Ri, Gi, Bi)
    Bo = FuncLUT(Ri, Gi, Bi)

在3D LUT中,数值之间会互相影响

从公式对比中我们可以看出来,如果在色深为10位的系统中,1D LUT的数据量大概是3x2^10bit,3D LUT就是(3x210)3bit

由此可以看出3D LUT的数据量比1D LUT多了一个指数级,所以3D LUT的精度比1D LUT高了很多,因为3D LUT的数据量太大,所以是通过列举节点的方式进行数据存储;

参考文章:https://www.jianshu.com/p/f054464e1b40

备注: 在相机捕获时实时渲染每一帧图片的时候,就会有显著的性能差别,尤其是 iPhone 8 Plus 相机捕获的每一帧大小几乎都是最后几种情况那么大(4032x3024)

Harbeth功能清单

  • 支持ios系统和macOS系统
  • 支持运算符函数式操作
  • 支持多种模式数据源 UIImage, CIImage, CGImage, CMSampleBuffer, CVPixelBuffer.
  • 支持快速设计滤镜
  • 支持合并多种滤镜效果
  • 支持输出源的快速扩展
  • 支持相机采集特效
  • 支持视频添加滤镜特效
  • 支持矩阵卷积
  • 支持使用系统 MetalPerformanceShaders.
  • 支持兼容 CoreImage.
  • 滤镜部分大致分为以下几个模块:
    • Blend:图像融合技术
    • Blur:模糊效果
    • Pixel:图像的基本像素颜色处理
    • Effect:效果处理
    • Lookup:查找表过滤器
    • Matrix: 矩阵卷积滤波器
    • Shape:图像形状大小相关
    • Visual: 视觉动态特效
    • MPS: 系统 MetalPerformanceShaders.

最后

  • 关于LUT查找滤镜介绍与设计到此为止吧。
  • 慢慢再补充其他相关滤镜,喜欢就给我点个星🌟吧。
  • 滤镜Demo地址,目前包含100+种滤镜,同时也支持CoreImage混合使用。
  • 再附上一个开发加速库KJCategoriesDemo地址
  • 再附上一个网络基础库RxNetworksDemo地址
  • 喜欢的老板们可以点个星🌟,谢谢各位老板!!!

✌️.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/76547.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL】深入理解隔离性

文章目录多版本并发控制(MVCC)如何解决读-写并发undo 日志模拟MVCC过程select读取版本隔离性的实现为什么要有隔离级别快照(read view)可重复读(RR&#xff09;与读提交(RC&#xff09;的本质区别多版本并发控制(MVCC) 多版本并发控制(MVCC)是一种用来解决读写冲突的无锁并发控…

[附源码]计算机毕业设计教务管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java银行管理系统275d1

要开始我们毕业设计的第一步的关键就是选好我们的课题&#xff0c;有的同学开始选题的时候想着按照传统的课题延续下去&#xff0c;在设计题目时&#xff0c;不要过于笼统广泛&#xff0c;选择题目其实并不难&#xff0c;要多从自身的角度出发&#xff0c;要结合你们当前所处的…

Briefings in Bioinformatics2021 | DLGN+:基于GAN和强化学习的分子从头双目标性质生成

论文标题&#xff1a;De novo generation of dual-target ligands using adversarial training and reinforcement learning 论文地址&#xff1a;https://academic.oup.com/bib/article/22/6/bbab333/6354720 代码&#xff1a;https://github.com/lllfq/DLGN 一、模型结构 …

数学基础从高一开始3、集合的基本运算

目录 复习内容&#xff1a; 并集的概念 你能用符号语言和图形语言表示并集这个集合吗? 例1:求并集 例2&#xff1a;求并集 符号解析&#xff1a; 例3&#xff1a; 交集的概念 例4&#xff1a; 例5&#xff1a; 例6&#xff1a; 思考题&#xff1a; 作业&#xff…

20221209在Ubuntu22.04下读取苹果分区APFS的步骤

20221209在Ubuntu22.04下读取苹果分区APFS的步骤 缘起&#xff1a;公司的新来的美工要清理MAC电脑。 由于忘记管理员密码&#xff1f;于是备份文件&#xff0c;重装系统&#xff01; 于是通过固态硬盘盒子将2TB的M2接口的固态硬盘SSD格式化为APFS&#xff0c;这样MAC电脑就可…

2022下半年软考成绩即将公布,预约查分提醒,查分快人一步

距离2022下半年考试已经过去一个多月了&#xff0c;大家都在焦急的等待软考成绩查询。根据往年情况来看&#xff0c;软考成绩查询时间并不是固定的一个时间点&#xff0c;不过可以大致预测下应该是在12月中/下旬左右开放成绩查询&#xff0c;具体情况以官方公告为准。 历年软考…

玩以太坊链上项目的必备技能(类型-引用类型-Solidity之旅三)

在前文我们讲述了值类型&#xff0c;也就说再修改值类型的时候&#xff0c;每次都有一个独立的副本&#xff0c;如&#xff1a;string 类型的状态变量&#xff0c;其值是无法修改&#xff0c;而是拷贝出一份该状态的变量&#xff0c;将新值存起来。对于处理稍微复杂地值类型时&…

2022最新性能测试面试题(带答案)

一、性能测试开展过程&#xff1a; 答&#xff1a;第一步&#xff1a;找产品沟通哪些接口需要压测&#xff0c;需要达到什么样的预期值(TPS和响应时间) 第二步&#xff1a;编写测试计划&#xff0c;人员、时间周期、工具 第三步&#xff1a;环境搭建 第四步&#xff1a;造数…

计算机操作系统

并行和并发的区别与联系&#xff1f; 【并发】 多个任务交替执行 计算机在运行过程中&#xff0c;有很多指令会涉及 I/O 操作&#xff0c;而 I/O 操作又是相当耗时的&#xff0c;速度远远低于 CPU&#xff0c;这导致 CPU 经常处于空闲状态&#xff0c;只能等待 I/O 操作完成后…

springboot项目如何启用arthas

Arthas 是Alibaba开源的Java诊断工具&#xff0c;深受开发者喜爱。当你遇到以下类似问题而束手无策时&#xff0c;Arthas可以帮助你解决&#xff1a; 这个类从哪个 jar 包加载的&#xff1f;为什么会报各种类相关的 Exception&#xff1f;我改的代码为什么没有执行到&#xff…

HTML网页设计:爱护动物题材——保护动物大象(6页) HTML网页设计结课作业 web课程设计网页规划与设计 网页设计成品DW静态网页

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

scConverter 文档转换 DLL / SDK

scConverter 转换 DLL / SDK scConverter 是一个DLL&#xff0c;可以将PDF、DWF、Gerber、CGM、TIFF、CALS、PLT、PNG和JPEG文件转换为大量输出格式。可用的输出格式列表包括Adob​​e PDF、PDF/A、DXF、DWF、CALS、TIFF、PLT和PNG。您将在下面找到所有可用输入和输出格式的完整…

R语言用向量自回归(VAR)进行经济数据脉冲响应研究分析

自从Sims&#xff08;1980&#xff09;发表开创性的论文以来&#xff0c;向量自回归模型已经成为宏观经济研究中的关键工具。最近我们被客户要求撰写关于向量自回归&#xff08;VAR&#xff09;的研究报告&#xff0c;包括一些图形和统计输出。这篇文章介绍了VAR分析的基本概念…

Java高级——后端编译与优化

后端编译与优化解释器和编译器编译器即时编译器分层编译热点代码热点探测计数器编译过程查看及分析即时编译结果提前编译器jaotc的提前编译后端编译优化总览优化演示方法内联&#xff08;最重要的优化技术之一&#xff09;逃逸分析&#xff08;最前沿的优化技术之一&#xff09…

15. 过拟合和欠拟合

1. 过拟合和欠拟合 当数据比较简单时&#xff0c;使用模型容量低的模型更好&#xff0c;否则使用高的会出现过拟合。如果是复杂的数据用到简单模型上会出现欠拟合&#xff0c;用到复杂模型上是正常的。 2. 模型容量 模型容量&#xff1a;拟合各种函数的能力 低容量的模型难以…

Springboot+Easyexcel:导出excel表格

常规导出 常规导出excel有两种&#xff0c;个人比较推荐第一种&#xff1a; 1、新建一个导出数据的实体类&#xff0c;用ExcelProperty()注解标明excel中列的中文名称&#xff1b;如果实体的类某些列不想导出&#xff0c;可以使用ExcelIgnore进行忽略就可以了。 2、使用easyexc…

彻底理解Python中浅拷贝和深拷贝的区别

目录 前言 1. 浅拷贝和深拷贝的概念 2. is和的区别 3. 赋值操作 4. copy模块里面的copy()方法 5. copy模块里面的deepcopy()方法 6.字典自带的copy方法 7.切片表达式拷贝 前言 Python 的所有变量其实都是指向内存中的对象的一个指针&#xff0c;这确实和之前学过的强类…

JDBC基本使用(第一个jdbc程序)

在web开发中&#xff0c;不可避免的地要使用数据库来存储和管理数据。为了在java语言中提供数据库访问的支持&#xff0c;Sun公司于1996年提供了一套访问数据的标准Java类库&#xff0c;即JDBC。 JDBC的全称是Java数据库连接(Java Database connect)&#xff0c;它是一套用于执…

Web3中文|AI机器人ChatGPT如何看待DeFi?

如果还没有玩过OpenAI最新的聊天机器人ChatGPT&#xff0c;那您真的应该体验一下。 从电影推介到编程查询&#xff0c;ChatGPT几乎可以对您向它提出的任何提示做出类似人类的逻辑响应。这种新奇的感觉就像乔布斯第一次滑动解锁iPhone屏幕时那样。 与加密货币一样&#xff0c;…