ISP 笔记(1):一个 ISP 项目的流程梳理

通过学习 mushfiqulalam/isp 项目整理一下 ISP 的流程。虽然这类博文很多,但不自己写一下总感觉不扎实,而且很多博文没有代码只是概述,更觉得不扎实。

基本的印象

镜片阴影校正(Lens shading correction),话说最开始接触这个概念,还是在看介绍 Jinc 函数的 pdf 里。

最后的降噪和锐化,是我很熟悉的。降噪用了 sigma filter,代码还没有读,盲猜是 Gaussian?锐化用了 USM 锐化,或许我可以,用一些我熟悉的效果更明显的降噪方法。

流程梳理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.raw文件 →
1. black level correction | 黑电平校正(BLC,或称 OBC)
2. lens shading correction | 镜片阴影校正(LSC)
3. bad pixel correction | 坏点校正
4. white balance | 白平衡(AWB)
5. bayer denoise | bayer降噪
6. demosaic | 去马赛克
7. local color ratio | 锐化方法/去马赛克的一种后处理
8. median filter | 中值滤波
9. color correction | 颜色校正(有明显的颜色变化,能看到更多颜色/细节)
10.gamma | gamma 校正(照片由暗变明,直观上的“正常”图像)
11.chromatic aberration
correction | 色差校正(即去除紫色/绿色条纹)
12.tone mapping | 色调映射(图像变亮,类似SDR->HDR的感觉)
13.memory color enhancement |
14.noise reduction | 降噪
15.sharpening | 锐化
16.distortion correction | 失真校正

全程在 32bit 下处理。其实原项目并不是这样,是每个处理单元内部使用 32bit 处理,而单元间的数据传递似乎还是 16bit。在之前的经验中,是不要反复转换位深;但看了下代码,32bit float 与 16bit int 间的转换无非就是np.float32()np.clip(image, 0, 65536)的转换,好像也没有什么精度损失。

去马赛克(Demosaicing)

包含了后处理 local color ratio,用以增强锐度,因为插值之后的图像缺少锐度(文献 1:10.1109/TCSVT.2004.828316)。

我用简单的插值做去马赛克,效果和项目的中差别较大(我做得更亮),倒是和OpenCV自带的xxxxtorgb()效果几乎一致。

Gamma校正

分两步进行,亮度调整(Luma adjustment)、量化(Equation)(另外两个by_value()by_table()函数没有启用)。

亮度调整部分就是我们熟悉的加个系数、进行Gamma校正的过程,也就是提高亮度(Brightening)。项目给定的系数是log10(80),即1.903。

说起来平时做 Resize,需要 Gamma 校正的时候就只做这一步,亮度增强。有没有必要也模仿这样做后续的处理。另外,根据 AreaResize 的经验,在 32bit 做 Resize 不需要做 Gamma 校正/亮度增强,因为精度已经足够(对哦…在这里本来就是为了提高亮度,不处理是黑的…和处理精度没关系)。

色调映射(Tone Mapping)

项目包含了两种独立的方法,每个 ISP 流程只执行一种方法。

  • 非线性mask(Nonlinear Masking)
  • 动态范围压缩(Dynamic Range Compression)

在中文 wiki 中介绍了 Photographic 算法(英文 wiki 中没有对应的内容),从字面和算法含义两个方面,都可以理解成“摄影算法”,这种色调映射方法借助了摄影 or 照片冲洗的技巧。但我直观地看上去,就是动态范围压缩的路数。

通用的基础操作

限制像素值范围

不能让像素值越界。

在 C++ 中

在 C++17 中引入了std::clamp()函数,用法和下面 Python 中的np.clip()一样。不用 C++17 的话就自己定义一个~~

在 Python 中

1
2
import numpy as np
dst = np.clip(dst, range_min, range_max)

数据类型

在 VapourSynth 内,16bit int 的像素值范围是[0, 65535],32bit float YUV 的像素值范围是[0.0, 1.0],32bit float RGB [-1.0, 1.0]。而在直接用 Python 和 NumPy 处理图像数据时,把输入数据升至float32,只是单纯地数据类型的转变,其范围并没有变啊。别用 VapourSynth 用多了,习惯于高度封装的环境,结果忘了最基础的数据类型。

所以才需要量化(逃…