「这是我参加2022初次更文挑战的第5天,活动详情检查:2022初次更文挑战」
本案例的意图是理解如何用Metal实现LUT色彩查找表滤镜,经过将色彩值存储在一张表中,在需求的时分经过索引在这张表上找到对应的色彩值,将原有色值替换成查找表中的色值;
总结便是一种针对色彩空间的办理和转化技能,LUT 便是一个 RGB 组合到另一个 RGB 组合的映射关系表;
Demo
- HarbethDemo地址
- iDay每日共享文档地址
实操代码
// 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图是一张512512巨细的图片,分为64个88的小区域,每个小区域对应一个B值(0 ~ 255,间隔4),小区域内的每个像素点对应一组R和G值(0 ~ 255,间隔为4)。
运用时,获取原图某个像素点的值,经过色彩查找,替换为对应的滤镜色彩值。
从图能够看出:
- 8×8的方块组成
- 全体上看每个方块左上角从左上往右下由黑变蓝
- 单独每个方块的右上角是赤色为主
- 单独每个方块的左下角是绿色为主
这是一个64x64x64颗粒度的LUT规划,总的方格巨细为512×512,8×8=64个方格,所以每个方格巨细为64×64;
64个方格,每个方格巨细为64×64,所以叫做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: 方格巨细为512×512,方位为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的数据量大概是3×2^10bit,3D LUT便是(3×2^10)^3bit
由此能够看出3D LUT的数据量比1D LUT多了一个指数级,所以3D LUT的精度比1D LUT高了许多,因为3D LUT的数据量太大,所以是经过罗列节点的办法进行数据存储;
参考文章:www.jianshu.com/p/f054464e1…
补白: 在相机捕获时实时烘托每一帧图片的时分,就会有显著的功能差别,尤其是 iPhone 8 Plus 相机捕获的每一帧巨细几乎都是最终几种状况那么大(4032×3024)
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地址
- 喜爱的老板们能够点个星,谢谢各位老板!!!
✌️.