普通视图

发现新文章,点击刷新页面。
昨天以前RidiQulous

Image quality evaluation of HDR displays

作者 Jueqin
2017年4月8日 16:53

Paper (473K) Contents (901K)

Introduction

The demand of accurate and pleasant image reproduction for displays has been important in recent years. Being limited by their small dynamic range, however, the usual displays can hardly reproduce the actual luminance of real scenes effectively. In response to the requirement of displaying image contents with higher dynamic range and better luminance accuracy, the high dynamic range (HDR) displays have been developed. Differing from the traditional standard dynamic range (SDR) displays, the HDR displays generally have higher peak brightness and lower minimum luminance level, thus providing a wider dynamic range to reproduce more details of the presented images or videos. To reproduce HDR contents, both HDR signal source and HDR display device are necessary. The former provides the scene’s real luminance information when being captured, and the latter uses a device-independent electro-optical transfer function (EOTF), namely perceptual quantizer (PQ), to convert the electric source signal to the optical output signal, producing the same luminance as recorded in the HDR image contents. The previous studies have pointed out that the HDR image contents have obvious advantages over the SDR image contents. On the other hand, the large-size OLED HDR displays have been developed, which can present very low blackness and rather high perceptual contrast. Thereby it is desiderated to investigate and further compare the performances of HDR displays with different coloring technologies as well as the external and internal factors that affect the display quality.

In this project, a series of psychophysical experiments were carried out to evaluate the image quality of three HDR TVs with different luminous mechanisms and panel technologies, and further to discuss how the image attributes and viewing conditions impact the overall preference of the observers for the HDR displays.

Figures

Figure 1. The color gamuts of the three test displays as well as sRGB and DCI-P3 for comparisons at CIE1976 u’v’ diagram.
Figure 2. Setup for subjective experiment (top view).
Figure 3. Overview of the involved test images. The rows of images correspond to the attribute of (a) Peak brightness, (b) Blackness, (c) Colorfulness, (d) Gradation, in which the former 3 are for High gradation and the others are for Low gradation, (e) Contrast, (f) Reality, and (g) Artifacts.
Figure 4. The overall results of the static image test (scale value method). The figures represent viewing conditions of (a) dark, front view, (b) dark, side view, (c) 200 lx ambient lighting, front view, and (d) 200 lx ambient lighting, side view.
Figure 5. The CV (coefficient of variation) values of image attributes and overall preference between individual and average in the condition of dark and front view.
Figure 6. The grand total scale value scores of the static image test for 3 displays: (a) the average image attributes scores, and (b) the overall preference scores.
Figure 7. scale value results of image attributes and overall preference for individual test images in the condition of dark and front view.

Publication

Reference

  • S. Miller, M. Nezamabadi, and S. Daly, “Perceptual signal coding for more efficient usage of bit codes,” J. SMPTE Motion Imaging, vol. 122, no. 4, pp. 52–59, 2013.
  • P. Hanhart, P. Korshunov, T. Ebrahimi, Y. Thomas, and H. Hoffmann, “Subjective quality evaluation of high dynamic range video and display for future TV,” J. SMPTE Motion Imaging, vol. 124, no. 4, pp. 1–6, 2015.
  • “EG 432-1:2010 – SMPTE engineering guideline – digital source processing #x2014; Color processing for D-cinema,” SMPTE EG 432-12010, pp. 1–81, 2010.
  • IEC 61966-4, “Colour measurement and management in multimedia systems and equipment,” International Electrotechnical Commission, 2000.
  • IEC 62341-6-1, “Ed. 1: Organic light emtting diode (OLED) displays,” International Electrotechnical Commission, 2007.
  • More…
Feel free to contact me with any suggestions/corrections/comments.

谈谈 CDP (Contrast Detection Probability)

作者 Jueqin
2021年3月7日 23:37

背景

众所周知,在传统图像处理领域,不同的处理任务都有各自的一套评价标准,比如降噪任务中的 PSNR 和 SSIM,自动白平衡中的角度差,颜色还原中的色差,等等。但是,当我们把应用场景切换到 CV 领域后,照搬这套传统的图像质量评价(Image Quality Assessment, IQA)体系来评价一个成像系统的性能,很有可能无法得到客观、公正的结果。

举个例子,在目标检测任务中,假设一个检测模型的训练样本全部来自于一台 360P 的古董监控摄像头,那么在部署这个模型时,即使喂给它一张 4K HDR 图像,其检测精度往往可能还不如另一张同样来自这个 360P 摄像头的图像——并不是说 4K HDR 图像的质量不好,但是在当前这一场景下,它与已经训练好的数据驱动的 deep learning 模型并不适配。因此,在 CV 任务的场景下,当一张图像的观察主体从人眼+人脑的组合切换为某个 DL 模型时,我们有必要对原有的图像质量评价体系进行重新定义。

在针对 CV 任务提出的 IQA 方案(以下称为 CV-IQA)中,用户注重的不再是解析度、信噪比、颜色准确性这些面向人眼的图像质量指标,而是会更加关注于物理世界中的信号在数字域中的可复现性(Reproducibility)和复现可靠性(Reproduction Reliability)。

以下图为例,左边是一块限速牌在物理世界中真实的亮度分布(用单位面积辐射出的光子数表示),右边是使用某台相机拍摄下的该限速牌的图像。由于任何光学系统和半导体器件都存在一些固有缺陷,因此在最终的图像中,限速牌各个部分的对比度,或者说信号的可观测差异,要比物理世界中的真实限速牌差得多。

左:真实亮度;右:图像中的像素值

差得多,那么究竟差了多少呢?我们试图从传统 IQA 指标中去寻找一个能够表征这种差异程度的指标:PSNR?量纲都对不上,PSNR 肯定没法计算,而且实际环境下这两张图也很难实现像素级的对齐;对比度?右边这张图像里有那么多的像素,要计算谁和谁之间的对比度?想了一圈发现,在这种实验设置下,传统指标似乎总是差了那么一点意思。

什么是 CDP

CDP(Contrast Detection Probability)指标是 IEEE P2020 小组(Automotive Image Quality Working Group)针对自动驾驶场景提出的一套 CV-IQA 方案,旨在克服传统 IQA 方案(如 IEEE Std 1858、EMVA1288、ISO 12233)用于车载影像质量评价时存在的一些缺陷。

用一句话概括 CDP 存在的意义:CDP 表征了成像系统对物理世界辐射信号差异复现能力(原文:CDP is a metric to describe the performance of an imaging system to reproduce contrasts in the physical scenes)。

划两个重点:

第一,在 CV 任务中,我们希望成像系统能够重点关注物理信号间的差异,而非信号本身的绝对大小。例如对于物理世界里亮度为 $(500\mathrm{cd/m^2}, 600\mathrm{cd/m^2})$ 的两个物体,我们关心的是在最终的输出图像中这两个物体能否进行区分,而并不关心具体的灰度值大小,它们可以是 $(200, 255)$,也可以是 $(50, 64)$。这一点很好理解,因为一旦这两个物体在图像中无法区分,其梯度便退化为零,基于特征提取的 CV 模型自然无法从中提取出任何差异化信息,不管是分类还是检测还是分割任务,统统歇菜。

第二,CDP 考量的是成像系统能否对信号差异进行准确复现。所谓准确复现,即是说物理世界里一组信号之间的差异有多大,在最终输出图像中,这个差异应该还是有多大。信号的差异在经过成像系统前后需要维持在一个相同的水平,既不能放大,也不能减小。还是上面那个例子,假如我们用 Weber 对比度作为计算信号差异的指标,那么物理世界中这两个信号之间的差异为 $K_{\mathrm{world}}=(\frac{600}{500}-1)=0.2$。如果有两个成像系统 A 和 B,在它们的输出图像 $\mathcal{I}_A$ 和 $\mathcal{I}_B$ 中,这两个物体的灰度值分别为 $(200, 255)$ 和 $(55, 64)$,那么有:

\begin{equation*} K_{A}=(\frac{255}{200}-1)=0.275\,,\quad K_{B}=(\frac{64}{50}-1)=0.28\,, \end{equation*}

这时我们就说成像系统 A 有着更好的信号差异复现能力,因为 0.275 相比 0.28 更接近物理世界中的真实对比度值——0.2。

从直觉上来说,我们往往会希望成像系统能够把真实世界中的信号差异进行一定程度的放大,比如 ISP 里的边缘增强、对比度增强等模块,都是为了突出和强调信号间的差异。但是在 CV 的语境下,我们希望成像系统能够尽量做到信息(即信号差异)的忠实还原,因此 B 系统虽然对信号差异进行了放大,但是其 CDP 指标却要低于 A 系统。这个例子也直观体现了 CV-IQA 与传统 IQA 之间的设计思想差异。

铺垫了这么长,那么为什么 CDP 这个指标可以针对自动驾驶场景下(或者说得更泛一点,大部分 CV 场景下)成像系统的质量进行评价呢?我个人的理解是,图像不同区域之间信号的可分辨性(distinguishability)是 CV 模型执行正确计算的必要条件,因此可以用 CDP 来衡量一个成像系统对物理世界中信号的检测能力的上限——如果一个成像系统的 CDP 很高,其检测能力未必很好,但是如果 CDP 很低,其检测能力一定不好。换句话说,CDP 并不是一个万能的 CV-IQA 方案,它只是为我们提供了一个相对客观的评价维度,同时补足了传统 IQA 中存在的一些短板。

技术细节

这一节重点介绍 CDP 这个指标究竟如何对成像系统的质量进行定量评价,以及给定一台相机,我们要如何计算出最终的 CDP 结果。

继续沿用上节中的例子。

假设物理世界中有两块亮度分别为 $500\mathrm{cd/m^2}$ 和 $600\mathrm{cd/m^2}$ 的理想漫反射平面:

上一节中我们已经计算出了它们之间的 Weber 对比度为 $K_\mathrm{world}=0.2$。同时,使用它们的亮度平均值 $\frac{500+600}{2}=550\mathrm{cd/m^2}$ 作为这一组信号的物理亮度 $L$。

现在我们使用一台相机分别去拍摄这两块平面,假设得到这样两幅图像:

从两张图像中各自随机抽取一个像素,如下红色小方框所示。不妨假设它们在图像中的灰度值分别为160和206。

CDP 要求我们在计算这两个像素的对比度之前,先把它们从图像灰度值域(digital-number domain)映射回物理亮度域(luminance domain)。显然,这时候我们并不清楚160和206的灰度值到底对应多少 $\mathrm{cd/m^2}$ 的物理亮度值,因此在执行这步操作之前,我们需要为当前成像系统构建一个输出 $\rightarrow$ 输入的反映射函数(inverse system function)。

P2020 中并没有明确说明如何构建这个反映射函数,我个人采用的方法是,先在横坐标为物理亮度、纵坐标为灰度值的平面内绘制出输入 $\rightarrow$ 输出曲线(即我们平时说的系统响应曲线),然后把当前要计算的灰度值作为 $y$ 坐标,用 bilinear 插值的方式计算出它在曲线中对应的 $x$ 坐标,以此作为该像素对应的物理亮度值。

输入 $\rightarrow$ 输出曲线的绘制可以预先通过标定实验来完成。我们假设当前相机的灰度值与物理亮度之间满足如下近似于 Gamma 曲线的关系:

对于160和206的灰度值,我们在这条曲线上可以内插出其对应的物理亮度大约为 $505\mathrm{cd/m^2}$ 和 $640\mathrm{cd/m^2}$。换句话说,对于这台相机,digital-number domain 中的160 (206) 数值,大约对应到 luminance domain 中的 $505\ (640)\mathrm{cd/m^2}$:

有了 luminance domain 中的一组亮度值之后,我们可以计算它们的 Weber 对比度:

\begin{equation*} K=\left(\frac{640}{505}-1\right)\approx{}0.267 \end{equation*}

除了可以按照如上两个红色小方框进行像素选取之外,我们一共有 $M\times{}N$ 种方式从两个 RoI 中各自选取一个像素构成一组 pixel-pair,其中 $M$ 和 $N$ 分别代表两个 RoI 中的像素数。如果我们对这 $M\times{}N$ 组 pixel-pair 都计算一次 Weber 对比度,则可以得到一个关于对比度的分布直方图。

两个平面在图像中的 RoI 都是 $32\times{}32$ 的正方形($M=N=32^2=1,024$),因此我们一共可以得到 $1,024\times{}1,024\,\approx{}10^{6}$ 个对比度值,它们构成的直方图如下所示:

对这个直方图进行归一化,我们就得到了一组 RoI-pair 之间的对比度概率分布函数 $p(K)$:

(到此终于解释了为什么 CDP 中的那个 P 是 probability……)

在这个例子中,物理世界里两个平面之间的信号差异为 $K_\mathrm{world}=0.2$,而由于噪声以及非线性的存在,在成像系统记录下的信号中,它们之间的差异不再是一个具体的数值,而是一组关于对比度的概率分布函数 $p(\cdot)$

在正式给出 CDP 的计算公式之前,需要先引入置信度的概念。

回想一下上文中我们对 CDP 的定义:CDP 用来衡量成像系统对物理信号差异的复现准确性。对于 $K_\mathrm{world}=0.2$ 的输入信号,理想情况下,我们当然希望成像系统能够分毫不差地复现出这个对比度数值,这时上图中的概率分布理应是一个落在 $K=0.2$ 处的 Dirac 函数。然而在实际系统中,$p(\cdot)$ 必然有一定的带宽,我们也会适当地放松对「准确复现」这一概念的定义,即,对于成像系统复现出的一组信号,只要它们的对比度落在 $\left[K_\mathrm{world}(1-\epsilon), \ K_\mathrm{world}(1+\epsilon)\right]$ 区间之内,我们就将其视为一次「准确复现」。这个 $\epsilon$,正是一个预设的置信度值。

回到上面的例子中,假设我们设定 $\epsilon=10\%$,这时对于任意一组 pixel-pair,只要它们的对比度位于 $[0.18,\ 0.22]$ 区间之内,我们都认为这组 pixel-pair 成功地对物理对比度进行了准确复现。在上面的概率分布函数中画出0.18和0.22所在的位置,其包围的区域与 $p(\cdot)$ 取交集得到的面积,相对于 $p(\cdot)$ 与 $K$ 轴包围的面积之比,即为最终的 CDP 值:

\begin{equation*} \mathrm{CDP}(K_\mathrm{world};\,L, \epsilon) = \frac{\displaystyle\int_{K_\mathrm{world}(1-\epsilon)}^{K_\mathrm{world}(1+\epsilon)}\ p(K)\,\mathrm{d}K}{\displaystyle\int_{-\infty}^{+\infty}\ p(K)\,\mathrm{d}K} \end{equation*}
阴影面积除以整个红色曲线下面积即为 CDP 值

根据上图阴影部分占整个曲线下面积(AUC)的比例,CDP 值大约在 30% 左右。

我们注意到公式中的 $\mathrm{CDP}$ 是一个关于 $L$ 的函数,这是因为,这一计算结果仅对平均亮度 $L=\mathrm{550cd/m^2}$ 的一组输入信号成立,当任一平面的物理亮度发生改变时(这时两个平面的平均亮度也会改变),我们又需要重新构建一次 $p(\cdot)$,再计算一个新的 CDP 值。

对于上面的例子,我们使用如下定义对系统的信号复现能力进行描述:

给定 10% 的置信度($\epsilon=0.1$),在 $550\mathrm{cd/m^2}$ 的亮度水平下,当前成像系统有 30% 的概率准确复现出物理世界中对比度为 $K_\mathrm{world}=0.2$ 的一组信号。

相比于传统的 IQA 指标,CDP 在对成像系统的性能进行评价时把外界的信息也作为了自变量之一,从而实现了被测系统输入(辐射信号)与输出(数字信号)之间的互动。

计算示例

让我们来看一个具体的示例。

假设有216个不同物理亮度的色块,它们当中最亮亮度约为 $5\times{}10^4\mathrm{cd/m^2}$,最暗的约为 $7.5\times{}10^{-2}\mathrm{cd/m^2}$,按下图所示排布。(这一排布方式参考了 Image Engineering 的 DTS 仪器

现在我们使用一个基于长中短曝光三帧合成的 HDR 相机对这个场景进行拍摄,得到如下 RAW 图像(实际是 16bit RAW,这里为了能够正常在屏幕上显示所以做了一个16bit $\rightarrow$ 8bit 的线性压缩):

使用 DOL-HDR 技术的相机在特定的亮度拼接区域会出现明显的 SNR drop,在这个例子中如果我们把图像右上角进行放大,可以看到某些色块对应的噪声强度明显大于其他色块:

SNR drop 的存在导致某些亮色块的噪声反而高于暗色块的噪声,例如第4行第3列色块 VS 第4行第4列色块

我们把图像中每个色块单独提取出来并计算其平均灰度值(这张图里每个色块大约占 $40\times{}40$ 个像素),可以得到216个不同的灰度值。把色块的物理亮度值作为横坐标,把图像灰度值作为纵坐标,可以绘制出这个相机的响应曲线(这是一个仿真的相机,实际相机的曲线当然不可能这么规整 😳 ):

有了响应曲线,就可以使用上一节提到的插值的方法来构建图像灰度值域到物理亮度域的反映射函数。

我们每次从这216个色块中任意选取两个色块作为一组 RoI-pair,然后按照上一节的方法可以计算得到一个 CDP 值。对于216个色块,一共可以不重复地选出 $C_{216}^{\,2}=23,220$ 组 RoI-pairs,因此最终一共将得到23,220个 CDP 值,以及23,220个物理对比度 $K_\mathrm{world}$。

在实际的应用中,我们通常只关心某些物理对比度 $K_\mathrm{world}$ 下的 CDP 情况,因此通常只会对这23,220组 RoI-pairs 中满足 $K^\ast (1-\delta)\le{}K_\mathrm{world}\le{}K^\ast(1+\delta)$ 的那些个 pairs 进行 CDP 的计算,其中 $K^\ast$ 表示用户指定的目标对比度,$\delta$ 是一个允许的 tolerance,一般取0.1即可。

为什么不直接筛选出满足 $K_\mathrm{world}=K^\ast$ 的 RoI-pairs,而是要设定一个容限 $\delta\,$?这是因为在有限个 RoI-pairs 当中,通常很难找到恰好等于目标对比度的 RoI-pairs,因此需要适当放宽一些筛选条件以确保获得足够数量的有效样本。

在这一节的例子中,当我们指定目标对比度 $K^\ast=0.2$、$\delta=0.1$ 时,一共可以得到291对满足 $0.18\le{}K_\mathrm{world}\le{}0.22$ 的 RoI-pairs。如果我们把这291个 CDP 值绘制出来,并以物理亮度 $L$ 作为横坐标,可以得到这么一个分布情况:

物理亮度越低时相机的信噪比越低,因此低亮度场景下的 CDP 值要明显低于高亮度场景。换句话说,实际亮度越低,相机越难对真实的信号差异进行准确复现

这张图完整表征了被测相机对于指定的目标对比度(0.2),在不同环境亮度下对物理信号进行准确复现的能力

类似地,我们还可以绘制出 $K^\ast=0.06$、$K^\ast=0.1$ 以及 $K^\ast=0.3$ 下的 CDP 分布情况(这三个数值是 Image Engineering 推荐的目标对比度值):

当目标对比度减小时,CDP 的分布也逐渐下降,这表明了真实世界中信号的差异越小,成像系统越难进行准确的复现。此外,对于 $K^\ast=0.06$ 和 $K^\ast=0.1$ 的两种情况,CDP 的散点分布在 $L=300\mathrm{cd/m^2}$ 和 $L=4000\mathrm{cd/m^2}$ 附近出现了两个明显的低谷,这对应了 DOL-HDR 相机的两处 SNR drop(这张图左上角的图注里 $C$ 应改为 $K^\ast$,我懒得重新作图了)

考虑到一个成像系统往往需要在不同的场景下工作,因此在使用 CDP 对成像系统的性能进行评价时,一般都会给出某个(或某几个)目标对比度下 CDP 关于物理亮度的散点分布,而不是固定一档亮度下的单个 CDP 数值。这一背景也导致了 CDP 无法以一个简单指标的形式向用户进行交付,同时也不容易对两个系统的性能差异进行定量比较(P2020 小组并没有提到如何评判两组 $L-\mathrm{CDP}$ 散点图之间孰优孰劣),这也许也是导致 CDP 始终没有得到普遍应用的原因之一吧。

Reference

  • Geese M, Seger U, and Paolillo A, Detection Probabilities: Performance Prediction for Sensors of Autonomous Vehicles. 2018
  • Artmann U, Geese M, Gäde M, Contrast Detection Probability – Implementation and Use Cases. 2019
  • Imatest Documentation: Contrast Detection Probability. https://www.imatest.com/docs/cdp/
  • ISO 12232:2019 Photography — Digital still cameras — Determination of exposure index, ISO speed ratings, standard output sensitivity, and recommended exposure index.
  • Robin B. Jenkin, Comparison of Detectability Index and Contrast Detection Probability. 2019
  • Lukas Ebbert, Implementierung von CDP: Entwicklung eines Programmiercodes in Python zur Untersuchung und Messung von CDP bei Fahrerassistenzkameras. 2018

从 Non-local Means 看 PyTorch 显存优化的奇技淫巧

作者 Jueqin
2020年12月4日 21:20

背景

最近的一个项目中需要用 PyTorch 实现一套可微分的 Non-local Means (NLM) 降噪算法,并通过样本学习建立一个图像内容与降噪强度参数 $h$ 的映射模型。

在 PyTorch 框架下,实现传统图像处理算法往往有着不止一种的写法,例如,为了实现 $3\times3$ 的均值滤波,我们既可以使用一个归一化的 box filter 对图像进行卷积,也可以对图像进行9次位移(shift/roll)并逐像素求和,最后再将每个像素值除以9。在模型进行正向推断(forward)时,不同的写法在显存开销上或许不存在太大的区别,但是当我们使用 Autograd 进行逐层的梯度反向传播(backward)时,不同的写法往往对应了截然不同的显存占用。以 NLM 为例,在我的实验中,对于同一张 $512\times{}512$ 的输入图像,不同的 tensor 操作写法可以带来180%以上的训练显存差异。当然,在代码设计没有明显缺陷的情况下,节省显存必然意味着运算效率的下降,不过对于我的这个项目来说,训练阶段的耗时并不是瓶颈所在,batch size 才是影响模型性能的主要因素,因此利用时间换取空间仍然是一个非常划算的选择。

先简单地回顾一下 Non-local Means 算法:给定一幅噪声图像,对于图像中的每个像素 $x$,以其为中心,在一个 $w\times{}w$ 的搜索窗口(下文代码中以 search_window 命名)内,遍历所有的可能的图像块(一般是一个 $k\times{}k$ 的方形区域,且有 $k<w$,下文代码中以 patch 命名),并计算该图像块与以 $x$ 为中心的那个图像块之间的相似度。

在 $w\times{}w$ 的搜索窗口内,一共存在 $w^2$ 个可重叠的图像块,对于第 $i$ 个图像块,假设其与 $x$ 所在图像块之间的相似度为 $s_i$(注意:是图像块之间的相似度,而不是像素与像素之间的相似度!),且该图像块中心像素的像素值为 $y_i$,那么对于 $x$ 像素,其经过 NLM 降噪后的像素值可以表示为:

\begin{equation*} \hat{x}_\mathrm{\scriptstyle NLM}=\sum_{i=1}^{w^2} f(s_i)y_i \end{equation*}

其中 $f(\cdot)$ 为一单调递增函数,即,若第 $i$ 个图像块与 $x$ 所在图像块的相似度越高,其在加权平均时所占的比重就越大。实际计算中一般会采用两个图像块之间的欧式距离 $d$ 作为相似度度量,即 $f(\cdot)=\exp\left(-\frac{d}{h}\right)$,其中 $h$ 是一个用于控制降噪强度的超参数

一点准备工作

为简化起见,假设现在给定了一张噪声图像和一张与之对应的无噪声参考图像(ground-truth),我们希望对该噪声图像求得一个最佳的 $h$ 参数,使之经过 NLM 降噪后与参考图像之间的 PSNR 最大化。

在这个全监督任务中,我们可以直接使用 MSE loss 作为目标函数进行迭代优化:

# train.py

import torch
import torch.nn as nn

from .nlm import NonLocalMeans
from .utils import gen_image_pair


dev = torch.device('cuda')
clean_rgb, noisy_rgb = gen_image_pair('Lenna.png', device=dev, sigma=0.05)

denoiser = NonLocalMeans(h0=0.05).to(dev)
denoiser.train()
optimizer = torch.optim.Adam(params=denoiser.parameters(), lr=0.0001, weight_decay=0.0001)
loss = nn.MSELoss()

for iter_num in range(100):
    denoised_rgb = denoiser(noisy_rgb)
    mse = loss(clean_rgb, denoised_rgb)

    mse.backward()
    optimizer.step()

    psnr = 10 * torch.log10(1.0 / mse)
    print('step{}: PSNR={:.3f} (h={:.5f})'.format(iter_num, psnr.item(), float(denoiser.h.item())))

其中 gen_image_pair 用于生成一对干净图像和噪声图像,为了不影响阅读的连续性,我把它的具体实现放在了文末的附录中;NonLocalMeans 模块接收一个 $N\times{}3\times{}H\times{}W$ 的张量作为输入,返回一个同尺寸的降噪后的张量。NonLocalMeans 的具体实现将放在下文中展开,这里我们只需确保该模块中包含一个可训练的 self.h 参数:

# nlm.py

import torch
import torch.nn as nn


class NonLocalMeans(nn.Module):
    def __init__(self, h0, search_window_size, patch_size):
        super().__init__()
        self.h = nn.Parameter(torch.tensor([float(h0)]), requires_grad=True)
        pass

    def forward(self, rgb):
        pass

NonLocalMeans v1

在第一版的 PyTorch NLM 中,我很自然地想到了空间换时间的方法,即,对于每个像素,将其搜索窗口中的 $w\times{}w$ 个像素拍扁并放置到一个新的维度中。以单通道图像为例,假设输入图像尺寸为 $1\times{}H\times{}W$,搜索窗口宽度 $w=11$,那么可以创建一个尺寸为 $1\times{}H\times{}W\times{}121$ 的张量,其中第 $(1,i,j,k)$ 个元素表示的是在以原图 $(i,j)$ 像素为中心的搜索窗口中第 $k$ 个像素的像素值。

有了这个张量之后,我们就可以将其与原图相减得到一个差值张量(相减之前需要将原图 unsqueeze 出一个新的维度用于 broadcast),并在这个差值张量上利用局部求和+开方的方法得到图像块之间的欧氏距离(出于偷懒的原因,图像块之间相似度的计算仅在亮度通道进行,下同。rgb_to_luminance 的实现见附录):

# nlm_v1.py

from .utils import rgb_to_luminance, ShiftStack, BoxFilter

EPSILON = 1E-12


class NonLocalMeans(nn.Module):
    def __init__(self, h0, search_window_size=11, patch_size=5):
        super().__init__()
        self.h = nn.Parameter(torch.tensor([float(h0)]), requires_grad=True)

        self.gen_window_stack = ShiftStack(window_size=search_window_size)
        self.box_sum = BoxFilter(window_size=patch_size, reduction='sum')

    def forward(self, rgb):
        y = rgb_to_luminance(rgb)  # (N, 1, H, W)

        rgb_window_stack = self.gen_window_stack(rgb)  # (N, 3, H, W, w*y)
        y_window_stack = self.gen_window_stack(y)  # (N, 1, H, W, w*y)

        distances = torch.sqrt(self.box_sum((y.unsqueeze(-1) - y_window_stack) ** 2))  # (N, 1, H, W, w*y)
        weights = torch.exp(-distances / (torch.relu(self.h) + EPSILON))  # (N, 1, H, W, w*y)

        denoised_rgb = (weights * rgb_window_stack).sum(dim=-1) / weights.sum(dim=-1)  # (N, 3, H, W)

        return torch.clamp(denoised_rgb, 0, 1)  # (N, 3, H, W)

其中的 self.gen_window_stack 方法用来将搜索窗口内的像素拍扁并放到一个新的维度中(ShiftStacknn.Unfold 类似,但是其实现比 Unfold 更省显存);self.box_sum 方法用来计算 $k\times{}k$ 窗口内的各像素值之和。它们的具体实现同样放在了文末附录中。

让我们先看下迭代过程中的 PSNR 和 $h$ 参数的变化,确保训练过程可以正常收敛:

step0: PSNR=28.600 (h=0.05050)
step1: PSNR=28.675 (h=0.05098)
step2: PSNR=28.747 (h=0.05146)
step3: PSNR=28.818 (h=0.05194)
...
step97: PSNR=32.882 (h=0.10972)
step98: PSNR=32.888 (h=0.11034)
step99: PSNR=32.894 (h=0.11096)

再看下降噪效果,确保这段代码能够正常实现 NLM 算法:

Ground-truth

+AWGN ($\sigma=0.05$)

NLM with $h=0.11096$ (PSNR=32.9)

在这个版本的 NLM 中,对于单张 $512\times{}512$ 的 RGB 图像,一次 forward 大概需要占用 2.3G 显存(已使用 with torch.no_grad() 关闭梯度计算),且训练阶段(forward+backward,见上文第一段代码块)的显存占用与 forward 基本持平:

forward:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:0D:00.0  On |                  N/A |
| 39%   57C    P2    90W / 120W |   2305MiB /  6069MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+

forward + backward:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:0D:00.0  On |                  N/A |
| 43%   59C    P2    96W / 120W |   2307MiB /  6069MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+

显然,这个版本中,中间变量 rgb_window_stacky_window_stackdistancesweights 均是5维张量,当使用默认的搜索窗口宽度 $w=11$ 时,为了存下它们,PyTorch 至少需要额外申请121倍图像大小的显存。

NonLocalMeans v2

在 NLM 算法中,用来计算相似度的图像块的尺寸 $k$ 通常要小于搜索窗口的尺寸 $w$,因此一个很容易想到的显存优化策略就是,将额外多出来的那个维度用来存储图像块内的各个像素值,而非搜索窗口内的各个像素值:

# nlm_v2.py

class NonLocalMeans(nn.Module):
    def __init__(self, h0, search_window_size=11, patch_size=5):
        super().__init__()
        self.h = nn.Parameter(torch.tensor([float(h0)]), requires_grad=True)

        self.gen_patch_stack = ShiftStack(window_size=patch_size)
        self.r = search_window_size // 2

    def forward(self, rgb):
        batch_size, _, height, width = rgb.shape
        weights = torch.zeros((batch_size, 1, height, width)).float().to(rgb.device)  # (N, 1, H, W)
        denoised_rgb = torch.zeros_like(rgb)  # (N, 3, H, W)

        y = rgb_to_luminance(rgb)  # (N, 1, H, W)

        y_patch_stack = self.gen_patch_stack(y)  # (N, 1, H, W, k*k)

        for x_shift in range(-self.r, self.r + 1):
            for y_shift in range(-self.r, self.r + 1):
                shifted_rgb = torch.roll(rgb, shifts=(y_shift, x_shift), dims=(2, 3))  # (N, 3, H, W)

                shifted_y_patch_stack = \
                    torch.roll(y_patch_stack, (y_shift, x_shift), dims=(2, 3))  # (N, 1, H, W, k*k)

                distance = torch.sqrt(((y_patch_stack - shifted_y_patch_stack) ** 2).sum(dim=-1))  # (N, 1, H, W)
                weight = torch.exp(-distance / (torch.relu(self.h) + EPSILON))  # (N, 1, H, W)

                denoised_rgb += shifted_rgb * weight  # (N, 3, H, W)
                weights += weight  # (N, 1, H, W)

        return torch.clamp(denoised_rgb / weights, 0, 1)  # (N, 3, H, W)

这个版本中,我使用两层循环完成对搜索窗口中每个像素的遍历,因此无论搜索窗口设定得多大,其影响的只是运行速度,而非显存。当图像块宽度 $k=5$,搜索窗口宽度 $w=11$ 时,forward 阶段显存开销理论上可以降低至 v1 版本的 $\frac{25}{121}\approx{}21\%$。

实测发现,对于同样尺寸的输入图像,这个版本的 NLM 一次 forward 只需要占用大约 600M 显存,而训练阶段则是 1.2G:

forward:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:0D:00.0  On |                  N/A |
| 45%   61C    P2    92W / 120W |    603MiB /  6069MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+

forward + backward:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:0D:00.0  On |                  N/A |
| 40%   57C    P2    96W / 120W |   1185MiB /  6069MiB |     99%      Default |
+-------------------------------+----------------------+----------------------+

NonLocalMeans v3

再极端一点,如果我们连用来存储图像块内的各个像素值的中间变量都不想创建呢?显然,可以在 v2 版本的基础上,再加两层循环完成图像块相似度计算(BoxFilter 实际上也是利用两层循环来实现卷积的效果),这个时候,没有任何新的维度需要添加到中间过程的张量中,所有操作都可以原址完成:

# nlm_v3.py

class NonLocalMeans(nn.Module):
    def __init__(self, h0, search_window_size=11, patch_size=5):
        super().__init__()
        self.h = nn.Parameter(torch.tensor([float(h0)]), requires_grad=True)

        self.box_sum = BoxFilter(window_size=patch_size, reduction='sum')
        self.r = search_window_size // 2

    def forward(self, rgb):
        batch_size, _, height, width = rgb.shape
        weights = torch.zeros((batch_size, 1, height, width)).float().to(rgb.device)  # (N, 1, H, W)
        denoised_rgb = torch.zeros_like(rgb)  # (N, 3, H, W)

        y = rgb_to_luminance(rgb)  # (N, 1, H, W)

        for x_shift in range(-self.r, self.r + 1):
            for y_shift in range(-self.r, self.r + 1):
                shifted_rgb = torch.roll(rgb, shifts=(y_shift, x_shift), dims=(2, 3))  # (N, 3, H, W)
                shifted_y = torch.roll(y, shifts=(y_shift, x_shift), dims=(2, 3))  # (N, 1, H, W)

                distance = torch.sqrt(self.box_sum((y - shifted_y) ** 2))  # (N, 1, H, W)
                weight = torch.exp(-distance / (torch.relu(self.h) + EPSILON))  # (N, 1, H, W)

                denoised_rgb += shifted_rgb * weight  # (N, 3, H, W)
                weights += weight  # (N, 1, H, W)

        return torch.clamp(denoised_rgb / weights, 0, 1)  # (N, 3, H, W)

在这个版本里,一次 forward 和一次 backward 分别只占用了大约 470M 和 1.1G 显存:

forward:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:0D:00.0  On |                  N/A |
| 43%   60C    P2    92W / 120W |    473MiB /  6069MiB |    100%      Default |
+-------------------------------+----------------------+----------------------+

forward + backward:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.81                 Driver Version: 384.81                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:0D:00.0  On |                  N/A |
| 43%   59C    P2    72W / 120W |   1113MiB /  6069MiB |     99%      Default |
+-------------------------------+----------------------+----------------------+

这也是我能想到的针对 Non-local Means 算法最显存友好的一种实现了。

Reference

附录

gen_image_pair

# utils.py

import numpy as np
import skimage.io
import torch
import torch.nn as nn


def gen_image_pair(image_path, device, sigma):
    # :return: two tensors with shape (1, 3, H, W) in [0, 1] range
    clean_rgb = skimage.io.imread(image_path).astype(np.float32) / 255.0
    # additive white gaussian noise
    awgn = np.random.normal(0, scale=sigma, size=rgb.shape).astype(np.float32)
    noisy_rgb = np.clip(clean_rgb + awgn, 0, 1)

    clean_rgb = torch.from_numpy(rgb.transpose(2, 0, 1)).unsqueeze(0).to(device)
    noisy_rgb = torch.from_numpy(noisy_rgb.transpose(2, 0, 1)).unsqueeze(0).to(device)
    return clean_rgb, noisy_rgb

rgb_to_luminance

# utils.py
    
def rgb_to_luminance(rgb_tensor):
    # :param rgb_tensor: torch.Tensor(N, 3, H, W, ...) in [0, 1] range
    # :return: torch.Tensor(N, 1, H, W, ...) in [0, 1] range
    assert rgb_tensor.min() >= 0.0 and rgb_tensor.max() <= 1.0
    return 0.299 * rgb_tensor[:, :1, ...] + 0.587 * rgb_tensor[:, 1:2, ...] + 0.114 * rgb_tensor[:, 2:, ...]

ShiftStack

# utils.py
    
class ShiftStack(nn.Module):
    """
    Shift n-dim tensor in a local window and generate a stacked
    (n+1)-dim tensor with shape (*orig_shapes, w*y), where wx
    and wy are width and height of the window
    """
    def __init__(self, window_size):
        # :param window_size: Int or Tuple(Int, Int) in (win_width, win_height) order
        super().__init__()
        wx, wy = window_size if isinstance(window_size, (list, tuple)) else (window_size, window_size)
        assert wx % 2 == 1 and wy % 2 == 1, 'window size must be odd'
        self.rx, self.ry = wx // 2, wy // 2

    def forward(self, tensor):
        # :param tensor: torch.Tensor(N, C, H, W, ...)
        # :return: torch.Tensor(N, C, H, W, ..., w*y)
        shifted_tensors = []
        for x_shift in range(-self.rx, self.rx + 1):
            for y_shift in range(-self.ry, self.ry + 1):
                shifted_tensors.append(
                    torch.roll(tensor, shifts=(y_shift, x_shift), dims=(2, 3))
                )

        return torch.stack(shifted_tensors, dim=-1)

BoxFilter

# utils.py
    
class BoxFilter(nn.Module):
    def __init__(self, window_size, reduction='mean'):
        # :param window_size: Int or Tuple(Int, Int) in (win_width, win_height) order
        # :param reduction: 'mean' | 'sum'
        super().__init__()
        wx, wy = window_size if isinstance(window_size, (list, tuple)) else (window_size, window_size)
        assert wx % 2 == 1 and wy % 2 == 1, 'window size must be odd'
        self.rx, self.ry = wx // 2, wy // 2
        self.area = wx * wy
        self.reduction = reduction

    def forward(self, tensor):
        # :param tensor: torch.Tensor(N, C, H, W, ...)
        # :return: torch.Tensor(N, C, H, W, ...)
        local_sum = torch.zeros_like(tensor)
        for x_shift in range(-self.rx, self.rx + 1):
            for y_shift in range(-self.ry, self.ry + 1):
                local_sum += torch.roll(tensor, shifts=(y_shift, x_shift), dims=(2, 3))

        return local_sum if self.reduction == 'sum' else local_sum / self.area

LEGO 42083 Bugatti Chiron with PF+RC 乐高布加迪奇龙

作者 Jueqin
2019年11月1日 16:53

我的拖延症又犯了,五月份拍的照片,一直到现在才把它们整理出来 😥 这盒布加迪也是我最近三年以来买的唯一一盒乐高了(好像还有 21309)。

这次的 Bugatti Chiron with PF+RC 当然是在官方套装 LEGO 42083 的基础上改装的啦,搭下来感觉中规中矩,仅仅是老老实实地使用 PF 件接替了动力、转向、变速箱的控制而已,而且我翻了翻官方的搭建手册,似乎原始结构设计中还有不少可玩性很高的细节?反正我在搭建这个 PF+RC 版本的过程中是没有感受到任何惊喜之处…… 更多具体的介绍和玩家评测可以到 Brickset 去参观,我这里仅把一些基本参数列一下,其他就不再啰嗦啦。

在开始贴照片前先放几张 Bugatti Chiron 的实物图吧 ↓

© SAUD AL-OLAYAN
© SAUD AL-OLAYAN
© Alex Holyoake

当然还有那段非常有名的「0-400-0 km/h」视频:


好了,背景交代完了,接下来我们就正式开始吧。以下图片有裁剪及压缩,原图请跳转至我的 Flickr

不能免俗的盒子照片 ▲▼
轮毂特写以及装上轮胎后的样子 ▲▼
说明书中在搭建手册之前的若干页宣传照 ▲▼
后桥模组
前桥+部分底盘。最前面的伺服舵机用于转向控制,替换了官方套装中的手动转向零件。前桥和底盘构成了一个整体,不像后桥那样可以模块化装卸
后桥+部分底盘。两个 XL 电机用于动力输出,四驱,不过并没有直接传递到四个轮子上,而是经过一个2速的变速器进行齿轮比调节
变速器部分,由一个手动拨杆控制齿轮比切换。不知道为什么对于这样一台超跑模型作者却选择把 XL 电机的功率全用在扭矩上了,即使是高速档也没比之前的 Marauder 快上多少
引擎部分。近端的那两个 L 型支架是我为了摆拍临时加上的
车身的前半部分。能看到大致的车舱框架了
车身的后半部分。四轮独立悬挂看得很清楚啦
前后部分组合完成
装上电池仓和两个红外接收器(图中被挡住了)。我手上没有原作中采用的 SBrick,只好用两个红外进行替代了
W16引擎的俯视角度
车屁股。尾翼可以手动抬升和降下。那一根红色的橡胶棍用来模拟真车的尾灯还是蛮传神的 ▲▼
正后方角度
座椅的拟真花了不少篇幅
装上前脸和车灯部分,外观基本就搭建完成了 ▲▼
个人感觉尾部的设计比前脸更加精彩 ▲▼
几张前后45度的定妆照 ▲▼
驾驶室的细节。可以看见方向盘下方的一组红外接收器

42083 本身就属于那种颜值大于结构的套装嘛,所以在入手之前我其实就已经对它的机械结构设计不抱太多的期望了。不过,就冲这个外形和配色,我还是给它4.5/5分的评价吧~

完。

我的餐厅打卡清单

作者 Jueqin
2019年11月1日 09:39

这个页面主要用来记录我到访过的一些餐厅以及相应的菜品水平(连锁快餐之类的就不写啦),不定时更新。当然,对一家餐厅的喜好与否是一件完全主观的事情,因此这份清单并不接受任何反驳 🙄

我会从口味、环境、服务、人均价格以及综合满意程度这五个指标对每家餐厅进行评价,其中综合满意程度的标准为:

  •  0分 :没什么可评价的,只能祝您早日关门大吉吧
  •  2分 :影响心情的用餐体验,同时也会竭力劝阻任何朋友来吃
  •  4分 :随便吃吃,仅限于饱腹
  •  6分 :比较愉快的用餐体验,会考虑再次登门
  •  8分 :同类型餐厅中的上乘水准,值得排队等候
  •  10分 :顶级水准,值得为之特意安排一趟行程

此外,对于吃过两次以上的餐厅,我还会加上一个菜品稳定程度的指标:0分表示两次用餐体验之间有着天壤之别,10分表示稳如磐石。

生活已经那么辛苦,吃顿好饭应该是为数不多的快乐来源了吧!Bon Appétit!


以下清单按照城市(杭州深圳广州龙岩)以及最后到店日期进行索引。当然你也可以按分数来直接搜索最好和最差的餐厅(Ctrl+F)。

杭州

虎哥私房菜

  • 类型:中餐
  • 最近到店日期:2020.04
  • 口味:5分
  • 环境:4分
  • 服务:3分
  • 人均:60–100元
  • 综合满意度: 5分 

栈前

  • 类型:粤菜
  • 最近到店日期:2020.01
  • 口味:3分
  • 环境:6分
  • 服务:3分
  • 人均:70–100元
  • 综合满意度: 3分 

北纬30°舟山海鲜面

  • 类型:简餐
  • 最近到店日期:2020.01
  • 口味:6分
  • 环境:5分
  • 服务:4分
  • 人均:30–50元
  • 综合满意度: 6分 

  • 类型:日料
  • 最近到店日期:2020.01
  • 口味:5分
  • 环境:5分
  • 服务:7分
  • 人均:140–180元
  • 综合满意度: 5分 

叶马茶楼

  • 类型:江浙菜
  • 最近到店日期:2020.01
  • 口味:4分
  • 环境:6分
  • 服务:6分
  • 人均:140–170元
  • 综合满意度: 4分 

鱼见亭

  • 类型:日料
  • 最近到店日期:2020.01
  • 口味:4分
  • 环境:6分
  • 服务:6分
  • 人均:80–120元
  • 综合满意度: 4分 

黄鱼炙夜

  • 类型:烧烤
  • 最近到店日期:2019.12
  • 口味:6分
  • 环境:4分
  • 服务:5分
  • 人均:60–100元
  • 综合满意度: 5分 

星洲海南鸡饭

  • 类型:中餐
  • 最近到店日期:2019.11
  • 口味:7分
  • 环境:4分
  • 服务:7分
  • 菜品稳定性:4分(>5次)
  • 人均:40–70元
  • 综合满意度: 7分 

院子餐厅

  • 类型:中餐
  • 最近到店日期:2019.12
  • 口味:5分
  • 环境:5分
  • 服务:2分
  • 人均:80–120元
  • 综合满意度: 3分 

寿司颠

  • 类型:日料
  • 最近到店日期:2019.12
  • 口味:8分
  • 环境:6分
  • 服务:6分
  • 人均:220–280元
  • 综合满意度: 7分 

荣先森

  • 类型:闽南菜
  • 最近到店日期:2019.12
  • 口味:5分
  • 环境:6分
  • 服务:5分
  • 人均:80–120元
  • 备注:他家的白灼小管可以打8分(跟以前的好吃温岭持平,应该是我在杭州吃过食材最好的海鲜了),可惜其他几道菜严重拉低了总分
  • 综合满意度: 5分 

满满初牛

  • 类型:西餐
  • 最近到店日期:2019.12
  • 口味:6分
  • 环境:6分
  • 服务:7分
  • 菜品稳定性:4分(2次)
  • 人均:160–260元
  • 备注:牛肉的标号应该不假,但是噱头成分居多,性价比偏低
  • 综合满意度: 4分 

明豪

  • 类型:中餐
  • 最近到店日期:2019.12
  • 口味:5分
  • 环境:6分
  • 服务:5分
  • 人均:300–400元
  • 综合满意度: 4分 

Narai Thai

  • 类型:东南亚菜
  • 最近到店日期:2019.12
  • 口味:6分
  • 环境:6分
  • 服务:5分
  • 人均:120–150元
  • 综合满意度: 5分 

群乐饭店

  • 类型:中餐
  • 最近到店日期:2019.12
  • 口味:7分
  • 环境:4分
  • 服务:5分
  • 菜品稳定性:5分(>5次)
  • 人均:50–80元
  • 备注:19年下半年的菜品质量略有下降,原材料也不如以前厚道了,所以我把菜品稳定性从7分降到了5分
  • 综合满意度: 7分 

小狗面馆

  • 类型:简餐
  • 最近到店日期:2019.11
  • 口味:5分
  • 环境:4分
  • 服务:4分
  • 人均:25–40元
  • 综合满意度: 5分 

HELLO 27

  • 类型:简餐
  • 最近到店日期:2019.11
  • 口味:7分
  • 环境:6分
  • 服务:6分
  • 菜品稳定性:8分(>3次)
  • 人均:40–70元
  • 综合满意度: 7分 

王记粉都

  • 类型:简餐
  • 最近到店日期:2019.10
  • 口味:6分
  • 环境:3分
  • 服务:5分
  • 菜品稳定性:7分(>15次)
  • 人均:15–25元
  • 综合满意度: 6分 

鹿松

  • 类型:糕点
  • 最近到店日期:2019.10
  • 口味:7分
  • 环境:-
  • 服务:7分
  • 菜品稳定性:9分(>15次)
  • 人均:10–30元
  • 备注:部分单品价格略高,推荐法棍和蒜香芝士
  • 综合满意度: 7分 

勇寿司

  • 类型:日料
  • 最近到店日期:2019.10
  • 口味:8分
  • 环境:6分
  • 服务:7分
  • 人均:140–200元
  • 综合满意度: 8分 

来一个烧饼

  • 类型:简餐
  • 最近到店日期:2019.10
  • 口味:8分
  • 环境:-
  • 服务:-
  • 人均:6–10元
  • 备注:是因为太饿了吗?为什么梅干菜烧饼这么好吃?
  • 综合满意度: 7分 

胡膳房

  • 类型:简餐
  • 最近到店日期:2019.10
  • 口味:4分
  • 环境:5分
  • 服务:4分
  • 人均:25–40元
  • 备注:缺斤少两,黄牛炒饭里只有一丢丢牛肉屑,不会考虑再来
  • 综合满意度: 2分 

南妈 stay

  • 类型:东南亚菜
  • 最近到店日期:2019.10
  • 口味:6分
  • 环境:7分
  • 服务:7分
  • 菜品稳定性:7分(>5次)
  • 人均:70–110元
  • 备注:香料比较地道,对我而言口味略微偏重
  • 综合满意度: 6分 

张三遵义羊肉粉

  • 类型:简餐
  • 最近到店日期:2019.10
  • 口味:6分
  • 环境:4分
  • 服务:6分
  • 菜品稳定性:7分(>5次)
  • 人均:18–30元
  • 综合满意度: 6分 

热血兄弟

  • 类型:烤肉
  • 最近到店日期:2019.10
  • 口味:5分
  • 环境:5分
  • 服务:6分
  • 人均:100–150元
  • 综合满意度: 4分 

咬强羊肉面

  • 类型:简餐
  • 最近到店日期:2019.09
  • 口味:6分
  • 环境:4分
  • 服务:4分
  • 人均:40–50元
  • 综合满意度: 6分 

Home Thai

  • 类型:东南亚菜
  • 最近到店日期:2019.09
  • 口味:7分
  • 环境:6分
  • 服务:5分
  • 人均:160–220元
  • 综合满意度: 6分 

菲滋披萨

  • 类型:简餐
  • 最近到店日期:2019.09
  • 口味:5分
  • 环境:5分
  • 服务:4分
  • 菜品稳定性:7分(3次)
  • 人均:30–60元
  • 综合满意度: 4分 

名家岭鲜

  • 类型:中餐
  • 最近到店日期:2019.09
  • 口味:7分
  • 环境:6分
  • 服务:6分
  • 菜品稳定性:6分(>5次)
  • 人均:100–160元
  • 备注:原来叫好吃温岭,自从搬到商场中后感觉质量略微下降了一些,价格却上涨了不少。所有的海鲜都可以放心点,推荐蛏子、望潮和炒粉丝,其他菜点不太稳定,偶尔会踩雷
  • 综合满意度: 7分 

青蜂

  • 类型:西餐
  • 最近到店日期:2019.09
  • 口味:6分
  • 环境:7分
  • 服务:5分
  • 菜品稳定性:7分(>3次)
  • 人均:80–120元
  • 综合满意度: 6分 

阿拉礼

  • 类型:新疆菜
  • 最近到店日期:2019.09
  • 口味:6分
  • 环境:5分
  • 服务:4分
  • 人均:60–100元
  • 综合满意度: 6分 

问粥道

  • 类型:中餐
  • 最近到店日期:2019.08
  • 口味:7分
  • 环境:4分
  • 服务:4分
  • 人均:15–30元
  • 综合满意度: 6分 

勇乌冬

  • 类型:日式
  • 最近到店日期:2019.08
  • 口味:8分
  • 环境:5分
  • 服务:7分
  • 人均:50–100元
  • 综合满意度: 8分 

闽龙王

  • 类型:中餐
  • 最近到店日期:2019.07
  • 口味:6分
  • 环境:6分
  • 服务:6分
  • 人均:60–100元
  • 综合满意度: 6分 

MOJAR

  • 类型:墨西哥菜
  • 最近到店日期:2019.07
  • 口味:6分
  • 环境:5分
  • 服务:6分
  • 菜品稳定性:7分(>5次)
  • 人均:40–70元
  • 综合满意度: 6分 

巡山令马瓢黄牛肉

  • 类型:火锅
  • 最近到店日期:2019.06
  • 口味:7分
  • 环境:4分
  • 服务:5分
  • 人均:60–100元
  • 综合满意度: 6分 

潮中人

  • 类型:中餐
  • 最近到店日期:2019.06
  • 口味:6分
  • 环境:5分
  • 服务:4分
  • 菜品稳定性:7分(2次)
  • 人均:80–110元
  • 综合满意度: 5分 

新曜记

  • 类型:粤菜
  • 最近到店日期:2019.04
  • 口味:4分
  • 环境:5分
  • 服务:4分
  • 菜品稳定性:6分(2次)
  • 人均:30–60元
  • 综合满意度: 4分 

澳门陈光记

  • 类型:粤菜
  • 最近到店日期:2019.04
  • 口味:4分
  • 环境:4分
  • 服务:3分
  • 人均:40–60元
  • 备注:烧味和冻柠茶都低于市面平均水平,严重怀疑是家山寨店
  • 综合满意度: 3分 

百家鸡味馆

  • 类型:中餐
  • 最近到店日期:2019.04
  • 口味:7分
  • 环境:5分
  • 服务:5分
  • 人均:25–50元
  • 综合满意度: 6分 

鹈鹕野餐

  • 类型:西餐
  • 最近到店日期:2019.04
  • 口味:7分
  • 环境:6分
  • 服务:7分
  • 菜品稳定性:7分(2次)
  • 人均:140–180元
  • 综合满意度: 7分 

疆小羊

  • 类型:新疆菜
  • 最近到店日期:2019.04
  • 口味:6分
  • 环境:4分
  • 服务:5分
  • 菜品稳定性:4分(2次)
  • 人均:50–100元
  • 综合满意度: 5分 

粤顺

  • 类型:粤菜
  • 最近到店日期:2019.04
  • 口味:7分
  • 环境:6分
  • 服务:5分
  • 菜品稳定性:6分(3次)
  • 人均:120–160元
  • 综合满意度: 6分 

阿郭冰室

  • 类型:简餐
  • 最近到店日期:2019.04
  • 口味:5分
  • 环境:3分
  • 服务:3分
  • 人均:30–50元
  • 综合满意度: 4分 

山外山

  • 类型:中餐
  • 最近到店日期:2019.03
  • 口味:7分
  • 环境:6分
  • 服务:6分
  • 菜品稳定性:7分(>3次)
  • 人均:120–180元
  • 综合满意度: 6分 

深圳

蛇口德记烧腊饭店

  • 类型:粤菜
  • 最近到店日期:2020.04
  • 口味:7分
  • 环境:5分
  • 服务:3分
  • 人均:80–120元
  • 备注:典型的老字号粤菜馆:出色的菜品+臭脸的店员
  • 综合满意度: 7分 

百草堂祖传凉茶

  • 类型:饮品甜点
  • 最近到店日期:2020.04
  • 口味:7分
  • 环境:5分
  • 服务:4分
  • 人均:10–30元
  • 综合满意度: 7分 

卢记蟹黄小笼包

  • 类型:快餐
  • 最近到店日期:2020.04
  • 口味:5分
  • 环境:4分
  • 服务:5分
  • 人均:15–25元
  • 综合满意度: 5分 

金翠皇宫

  • 类型:粤菜
  • 最近到店日期:2020.04
  • 口味:7分
  • 环境:6分
  • 服务:5分
  • 人均:40–80元
  • 综合满意度: 7分 

广州

陶陶居

  • 类型:粤菜
  • 最近到店日期:2019.10
  • 口味:5分
  • 环境:6分
  • 服务:4分
  • 人均:100–150元
  • 综合满意度: 5分 

超记煲仔饭

  • 类型:简餐
  • 最近到店日期:2019.10
  • 口味:5分
  • 环境:2分
  • 服务:4分
  • 人均:25–40元
  • 备注:名不副实的网红店,味道平平无奇,就餐需要抢位和拼桌
  • 综合满意度: 4分 

熹雀冰室

  • 类型:饮品甜点
  • 最近到店日期:2019.10
  • 口味:2分
  • 环境:-
  • 服务:-
  • 人均:15–20元
  • 综合满意度: 2分 

松记打边炉

  • 类型:火锅
  • 最近到店日期:2019.10
  • 口味:9分
  • 环境:5分
  • 服务:7分
  • 人均:130–160元
  • 备注:食材质量非常高,强推
  • 综合满意度: 9分 

龙岩

洋古墩清汤粉

  • 类型:简餐
  • 最近到店日期:2019.10
  • 口味:7分
  • 环境:4分
  • 服务:5分
  • 菜品稳定性:6分(>10次)
  • 人均:8–15元
  • 备注:吃了十多年的老店了,原来可以打8分,今年不知道为什么水准突然变得不太稳定了
  • 综合满意度: 7分 

森山仙草

  • 类型:饮品甜点
  • 最近到店日期:2019.10
  • 口味:6分
  • 环境:5分
  • 服务:5分
  • 菜品稳定性:7分(>10次)
  • 人均:15–30元
  • 综合满意度: 6分 

罗桥牛杂

  • 类型:中餐
  • 最近到店日期:2019.06
  • 口味:8分
  • 环境:3分
  • 服务:5分
  • 菜品稳定性:8分(>10次)
  • 人均:50–100元
  • 备注:属于那种典型的本地老饕餐厅,又吵又闹,环境也很一般,但是味道是真的好啊,炒牛肉炒百叶是我目前吃过最好吃的。每天下班时间都人满为患
  • 综合满意度: 8分 

中文网页字体设置方案:从 font-family 到 font-display

作者 Jueqin
2019年10月25日 21:44

从13年刚开始搭建博客的时候我就在关注中文的 webfont 解决方案了,可惜那时候几乎找不到一套成熟的跨平台字体设置方案,Google Fonts 和 Typekit 也远没有现在这么完善,Windows 下微软雅黑丑出天际,我们能做的无非就是老老实实设置好 fallback 来确保不同操作系统下都能显示出我们希望显示的字体。

到了2019年就不一样啦,现在的 Google Fonts 和 Adobe Typekit 都提供了傻瓜式的第三方字体托管,同时 CDN 的普及也确保了不同地域的用户都能够(比较)迅速地加载非本地字体。更关键的是,今天即使是移动设备的带宽也允许用户在1–2秒内完成一套中文字体的加载。我们已经完全有理由抛弃微软雅黑而采用更灵活的中文网页字体设置方案。

font-family

font-family 的填写原则在网络上可以搜出无数文章,这里我仅仅简单说一下我自己的一些总结:

  • 英文字体在前,中文字体在后。原因很简单,中文字体包中通常包含了英文字符,反之则不成立,如果先设置了中文字体,那么英文字体根本轮不到 fallback
  • 操作系统方面,Apple 系列(macOS + iOS)字体在前(直接用 -apple-system 即可,见下文),Windows 字体在后。这里解释了原因,即,macOS 下的用户有一定概率安装了 Windows 字体(因为安装了 Office),而 Windows 下安装 macOS 字体的用户却少之又少,所以如果 Windows 在前,那么在 macOS 下很有可能就出现「放着苹方不用却用微软雅黑」这种坑爹情况
  • 根据访问网站的用户群体不同,可以考虑放弃对于老旧操作系统和浏览器的支持,比如我在使用思源宋体的时候,仅提供了 .woff 和 .woff2 格式的字体文件,完全放弃了老版本的 IE 和 Safari
  • 仅对 Windows 用户使用自定义字体,比如思源系列,macOS、iOS、Linux 就使用系统自带字体就好。原因嘛当然是因为 Windows 自带的中文字体实在找不到好看的,而主动安装字体的用户毕竟还是少数(嗯这条是私货 🙄 )
  • 衬线还是非衬线完全看个人审美喜好,比如我就不觉得思源黑体系列好看(所以你现在看到的思源宋体,如果你是 Windows 用户的话)。同时,中文衬线搭配英文非衬线,或者反过来,中文非衬线搭配英文衬线,也完全没有问题

我目前使用两套 font-family 设置。正文(Main text)部分是:

font-family: Georgia, -apple-system, 'Nimbus Roman No9 L', 'PingFang SC', 'Hiragino Sans GB', 'Noto Serif SC', 'Microsoft Yahei', 'WenQuanYi Micro Hei', 'ST Heiti', sans-serif

标题(Headings)和页面导航栏(Navigation)部分是:

font-family: 'Josefin Sans', -apple-system, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft Yahei', 'WenQuanYi Micro Hei', 'ST Heiti', sans-serif

其中 Georgia-apple-system'Nimbus Roman No9 L' 分别对应 Windows、macOS/iOS、Linux 下三种系统内置英文字体(其实 Georgia 就已经覆盖三种系统了,后面两个只是出于保险起见),'PingFang SC'(苹方)、'Hiragino Sans GB'(冬青黑体)、'Microsoft Yahei'(微软雅黑)对应三种系统内置中文字体(按照上文说法,Apple 系列在前,Windows 在最后),'Josefin Sans''Noto Serif SC'(思源宋体)则是两套允许免费使用的第三方字体,已经被我存储在本地。

理论上来说,一旦在 font-family 中放置了类似思源宋体这种第三方字体,那么浏览器就一定会加载该字体文件,因此也就没有必要再考虑后续的 fallback 字体。但是就像在 font-display 里面说到的,为了避免浏览器在完成加载第三方字体文件之前显示一片空白,我们仍然有必要设置 fallback 字体作为备胎。

根据 Google 的标准,如果在第三方字体完成加载之前页面没有显示出任何文字,那么在 PageSpeed Insights 等网页测试工具中都是要扣分的。

Google Fonts 的本地化

在 Google Fonts 的官方仓库中,中文字体包通常会被拆分为几十上百不等的若干个字体文件,从而确保打开某个网页时仅加载该页面中需要的字符。这种思路和 font-spider 这类的字体压缩工具类似,但是把加载哪些字体文件完全交给 CSS 判断,因此压缩比自然远不能与后者相比。除此之外,还有下下几点因素让我决定放弃使用 Google Fonts 进行字体在线托管,而选择将其部署在本地:

  • font-weight: 400 的思源宋体为例(Google Fonts 里的名字叫做 Noto Serif SC),对于单个阅读时间在3分钟左右的中文页面,需要加载18–22个左右的 .woff2 字体文件,总的体积在 900KB–1.1MB 左右。而如果选择将完整的字体包存储在本地的话,也仅需要 2MB 不到的空间而已。在我看来,用请求数的大幅增加(20 vs 1)来换取体积的减少(1MB vs 2MB)似乎并不怎么划算
  • 将字体数据分散在20来个字体文件中,并为每个文件都设置 font-display: swap 属性(Google Fonts 的默认策略,后文会解释),会导致整个页面由于字体的切换而产生一种不断闪烁的错觉,我个人无法接受这种情况
  • 虽然 gstatic.com 目前没有被墙(也有一说是直接被解析到国内服务器上了),但是我总觉得还是本地存储比较保险一些,然后再使用 CDN 来保证加载速度

要把 Google Fonts 中的第三方字体部署在本地也再容易不过了,只需要在 google-webfonts-helper 中选择想要的字体进行下载并存储在本地 FTP 指定目录下,然后把其提供的 CSS 代码添加到网页原有的样式后。以 WordPress 为例,可以把这段 CSS 代码复制到主题对应的 style.css 中,或者也可以新建一个 CSS 文件然后利用 wp_enqueue_stylewp_register_style 函数使得这个 CSS 文件可以被 WordPress 访问到。由于我使用的主题已经内置了 Google Fonts 的功能,所以我直接在 functions.php 中使用了一个同名函数覆盖掉了原有的从 fonts.googleapis.com/css 获取样式的函数:

function generate_google_fonts_link() {
    // replace remote stylesheet in fonts.googleapi.com with 
    // the local file
    return '/assets/google-fonts.css'; 
}

function load_google_font() {
if ( $fonts_link = generate_google_fonts_link() ) {
    wp_enqueue_style( 'google-fonts', $fonts_link, false );
}

add_action( 'wp_enqueue_scripts', 'load_google_font' );

其中的 /assets/google-fonts.css 就是 .woff 和 .woff2 字体文件所在的相对路径。

我的 google-fonts.css 的具体内容为:

/* josefin-sans-regular - latin_latin-ext_vietnamese */
@font-face {
  font-family: 'Josefin Sans';
  font-style: normal;
  font-weight: 400;
  src: local('Josefin Sans Regular'), local('JosefinSans-Regular'),
       url('/assets/fonts/josefin-sans-v14-latin_latin-ext_vietnamese-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
       url('/assets/fonts/josefin-sans-v14-latin_latin-ext_vietnamese-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
  font-display: swap;
}

/* noto-serif-sc-300 - chinese-simplified_latin */
@font-face {
  font-family: 'Noto Serif SC';
  font-style: normal;
  font-weight: 300;
  src: local('Noto Serif SC Light'), local('NotoSerifSC-Light'),
       url('/assets/fonts/noto-serif-sc-v6-chinese-simplified_latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
       url('/assets/fonts/noto-serif-sc-v6-chinese-simplified_latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
  font-display: swap;
}

/* noto-serif-sc-900 - chinese-simplified_latin */
@font-face {
  font-family: 'Noto Serif SC';
  font-style: normal;
  font-weight: 900;
  src: local('Noto Serif SC Black'), local('NotoSerifSC-Black'),
       url('/assets/fonts/noto-serif-sc-v6-chinese-simplified_latin-900.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
       url('/assets/fonts/noto-serif-sc-v6-chinese-simplified_latin-900.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
  font-display: swap;
}

font-display

如果你仔细一点看上面的 CSS 代码的话就会发现,不同于从 google-webfonts-helper 中直接复制出来的代码,我还为每个字体指定了一个 font-display 字段。font-display 是一个比较新的 CSS 属性,用来控制某一字体在尚未成功加载时采用何种方式显示文本。如果不明确指定 font-display,浏览器一般会采用「block」的方式进行显示,即,在该字体完全加载成功之前啥都不显示。

对于英文字体来说即使使用 block 问题也不大,因为哪怕是非本地的字体文件也就几十 KB,完全可以在几百甚至几十毫秒内完成加载,对用户几乎不会造成任何影响。但是对于中文字体来说这个问题就比较严重了(特别是对于我这种使用单个大体积 .woff2 文件的情况),因为在完成字体加载之前,通常会有一段肉眼可见的页面空白时间,对用户非常不用好:

font-display 默认为 block,会导致字体加载完成(第 650ms 左右)之前页面一片空白。加粗的字体较早显示出来说明 noto-serif-sc-900.woff2 文件早于 noto-serif-sc-300.woff2 加载完毕。左上角时间为真实的页面加载时间,动画为 x7 慢放,单击可查看大图

为了改善这个情况,我们在使用第三方中文字体时,非常有必要把 font-display 设置为 swap 或者 fallback,前者会在当前字体文件加载时使用 font-family 中当前字体的后备字体进行文本显示,而后者则允许一个停顿时间(比如100ms),该停顿时间内先显示空白,如果过了这个时间阈值当前字体仍然没有完成加载,则采用和 swap 相同的策略进行文本显示。比如我们在 CSS 中对正文文本使用 font-family: 'Noto Serif SC', 'Microsoft Yahei', sans-serif,那么当思源宋体的字体文件还没加载完成时,浏览器会先用微软雅黑显示当前的正文文本(本机中找不到微软雅黑的话继续 fallback 到系统默认的非衬线字体),待思源宋体加载完毕后再立即切换过去:

font-display 换成 swap 后的效果。思源宋体的字体文件在第 550ms 左右下载完成,在这之前使用微软雅黑作为 fallback 进行显示,然后立刻切换为思源宋体。左上角时间为真实的页面加载时间,动画为 x7 慢放,单击可查看大图

除了 blockfallbackswap 之外,font-display 还有 optional 这个选项,即让浏览器自己做决定是否要对字体进行切换,对于一些网速比较慢的网页,第三方字体加载完成可能需要花费很长时间,这种情况下即使字体文件已经完成了加载浏览器也可能不再进行切换,而是继续以 fallback 字体进行显示。


最后总结一下我认为的目前可接受的中文 webfont 解决方案:

  1. 字体文件在本地存储,用 CDN 进行加速
  2. 合理设置 font-family,做好 fallback
  3. 使用 font-display: swap 属性(optional 也可)

Reference

数字图像传感器的噪声模型及标定

作者 Jueqin
2019年10月20日 01:26
这篇文章里有超多公式,建议在横向分辨率大于 1920px 的屏幕上阅读。

目录

0. 写在前面

这篇文章应该是博客里到目前为止最枯燥的一篇了……

写这篇文章的原因有二:一是在我的学位论文中有一部分内容需要涉及图像噪声模型,正好这几天读完 Radiometric CCD Camera Calibration and Noise Estimation,权当记录备忘;二是因为搜了一圈发现目前网上基本没有对相机传感器噪声的数学模型进行详尽介绍的中文资料,所以也算是做一点微小的工作,填补这一块的空白吧 🙄

实际上,对于 CV 领域内大部分应用来说,$f=\mu+\epsilon$ 这种经典的模型已经足以对图像的像素值进行表征了。但是对于一些更偏工程、需要与相机硬件直接打交道的应用,往往还需要对成像过程中各阶段引入的噪声进行更加具体的建模和分析,从而将其最大程度抑制。正因如此,我默认阅读这篇文章的读者都具备辐射度学、相机成像原理、图像处理以及统计学的相关基础知识。

此外,图像噪声这一领域并不是我的研究方向,文中难免存在一些瑕疵或错误,欢迎指正。

1. 相机响应值构成模型

在分析信号中的噪声之前,我们当然首先需要搞明白信号究竟是如何构成的。

不管是 CMOS 也好 CCD 也好,图像传感器的本质都是将光信号转换为电信号。对于图像传感器上的单个感光单元(有叫作 collection site 的,或者 photosensory cell,或者 sensor element,总之指的是「单个像素」)来说,其感光后由光电效应释放出的电子个数 $I$ 可以表示为

\begin{equation} I = T\int_\lambda\int_x\int_y\,E(x,y,\lambda)\,S(x,y)\,q(\lambda)\,\textrm{d}x\,\textrm{d}y\,\textrm{d}\lambda \label{eq:electrons_num} \end{equation}

其中 $x$、$y$ 表示该单元的二维位置坐标,$\lambda$ 是波长,$T$ 为积分时间(假设所有的参数在快门帘开启的这段时间内均不随时间变化),$E(x,y,\lambda)$ 为该单元内光敏面处的光谱辐照度(incident spectral irradiance),$S(x,y)$ 表示感光单元的灵敏度空间波动,$q(\lambda)$ 为相机的光电转换函数。

从量纲角度分析会更好理解一些:积分时间 $T$ 的单位为 $s$,光谱辐照度 $E$ 的单位为 $W/(m^2\cdot{}nm)$,$S$ 无量纲,光电转换函数 $q$ 的单位为 $e^-/(J\cdot{}nm)$(在指定波长上每吸收1焦耳能量能释放出的电子个数),因此有 $s\cdot{}(J\cdot{}s^{-1}\cdot{}m^{-2})\cdot{}(e^-\cdot{}J^{-1})\cdot{}nm^{-1}\cdot{}m^2\cdot{}nm = e^-$。

我们注意到,式 \eqref{eq:electrons_num} 中的 $x$、$y$ 均被限制在了单个感光单元内,在这么小的面积内,可以认为各个参数均不随位置变化而变化,因此位置坐标 $x$、$y$ 可以被省略。式 \eqref{eq:electrons_num} 被简化为

\begin{equation} I = T\overline{S}A\int_\lambda\,E(\lambda)\,q(\lambda)\,\textrm{d}\lambda \label{eq:electrons_num_simple} \end{equation}

其中 $\overline{S}$ 表示 $S(x,y)$ 在单个感光单元内的期望值(均值),$A$ 表示单个感光单元的有效感光面积。

此外,感光单元内光谱辐照度和被摄物体表面的光谱辐亮度(spectral radiance)之间还满足如下关系:

\begin{equation} E(\lambda) = \frac{\pi}{4F^2}UL(\lambda)t(\lambda) \label{eq:radiance2irradiance} \end{equation}

其中 $F$ 是相机的相对孔径(也就是摄影领域中的那个光圈数 F),$L(\lambda)$ 为物方平面上被摄物体表面的光谱辐亮度,$t(\lambda)$ 为整个相机系统的光谱透过率函数(包括镜头、低通滤镜、Bayer filters 等等),$U$ 是一个由 vignetting、shading 等因素导致的空间非均匀性调制函数,无量纲。

式 \eqref{eq:electrons_num_simple} 的原理不展开说了,感兴趣的话可以简单参考一下下图。
单透镜理想光学系统成像模型
$\mathrm{d}A_o$ 表示物体表面微元, $\mathrm{d}A_s$ 表示 CMOS 平面上对应的像方微元

所以,联立式 \eqref{eq:electrons_num_simple} 与 \eqref{eq:radiance2irradiance},有:

\begin{equation} I = \frac{\pi}{4F^2}TS_rAU\int_\lambda\,L(\lambda)\,t(\lambda)\,q(\lambda)\,\textrm{d}\lambda \label{eq:formation} \end{equation}

式 \eqref{eq:formation} 就是数字相机将空间光信号转化为电荷信号的理想物理模型。

式 \eqref{eq:radiance2irradiance} 里的 $U$ 显然是一个偷懒的简化。

实际上,很多文献里都会提到,$L(\lambda)$ 与 $E(\lambda)$ 之间满足那个著名的 cos 四次方定理:

\begin{equation*} E(\lambda)\approx\frac{\pi}{4F^2}cos^4(\alpha)L(\lambda)t(\lambda) \label{eq:cos4} \end{equation*}

其中 $F$ 是光圈数(焦距/光阑直径),$\alpha$ 是某个像素对应的主光线相对于光轴的偏离角。这个照度的四次方衰减定律一般也称为自然渐晕(natural vignetting)。

然而,对于一个真实的相机系统来说,CMOS 平面上的照度非均匀性可不仅仅是 cos 四次方衰减那么简单,必然还包含了各种复杂的原因,比如光学渐晕(optical vignetting)、镜头设计时人为加入的照度调制等等(参考下图)。所以为了不失一般性(好吧说实话,其实是因为这种复杂的非均匀性基本不可能给出解析表达式),我直接简单粗暴地把 $cos^4$ 以及其他的非均匀调制统一丢到这个 $U$ 里去了。
Luminance shading

在这之后,感光单元内产生的电荷信号将经由放大电路(调 ISO 其实就是在调节放大电路的增益系数)进行放大,并经 ADC 转换为数字信号缓存在 RAM 中,待后续存储或调用:

\begin{equation} D = round\left(\frac{g^\prime{}I + V_\mathrm{offset}}{\eta}\right) \label{eq:amplify} \end{equation}

其中 $g^\prime$ 表示模拟电路的增益倍数,$V_\mathrm{offset}$ 是人为加入的一个偏置电压,通常可以通过这种方式来避免小于零的输出信号被 ADC 截零从而改变噪声分布的对称性(一些相机的暗电流就是这么来的),$\eta$ 是 ADC 中的量化步长(quantization step),单位为 $volts/DN$(DN 就是 digital number,表示数字信号的数值,理论上是无量纲的,但是为了表述方便通常会人为给它加上一个量纲),与相机所使用的位深(bit-depth,平时我们说一幅图像是8位的或者12位的或者14位的,指的就是这个位深)有关,$round(\cdot)$ 则表示 ADC 的取整过程。

最后这个输出的数字信号 $D$,我习惯称之为相机的原始响应值(raw response),也就是相机 raw 图像中可以直接读出的那个数字大小。一些资料里也习惯把它称作 DN (digital number) 或 ADU (analog-to-digital units)。

感光单元释放出的电荷如何进入 ADC 这一细节有待确定。老一些的相机,尤其是 CCD 相机,似乎会为传感器阵列的每一行配备一个寄存器,每个寄存器接收一整行感光单元传来的电荷后再统一传给放大器,各行间并行处理;但是对于新一些的 CMOS,甚至会为每一个像素单独配置一个寄存器和放大器,所有信号的处理以像素为单位并行,从而大幅缩短两次采集之间的间隔。此外,现在的一些相机还会为传感器配置两组具有不同电容的感光单元电荷输出电路,从而在电信号进入放大电路之前就有两组电压值可供切换,即所谓的 Dual Native ISO 技术。

最后,联立式 \eqref{eq:formation} 和 \eqref{eq:amplify},我们就得到了完整的光信号到数字信号的理想转换模型,或者说是理想的数字信号构成模型(response formation model):

\begin{equation} D = round\left[g\,TS_rAU\int_\lambda\,L(\lambda)\,t(\lambda)\,q(\lambda)\,\textrm{d}\lambda + D_\mathrm{offset}\right] \label{eq:response_model} \end{equation}

显然,这里有 $g=g^\prime/\eta$,$D_\mathrm{offset}=V_\mathrm{offset}/\eta$,我把它们分别叫作综合增益数字偏置量($D_\mathrm{offset}$ 基本就是相机的暗电流数值啦)。

2. 图像噪声来源

第一节中讲的一大堆内容都是理想情况下的成像过程,而在实际情况中,以上光信号 $\rightarrow$ 电信号 $\rightarrow$ 数字信号的过程中必然存在着噪声。

由于图像噪声中既存在空间无关噪声也存在空间相关噪声,所以在对图像传感器的噪声进行建模之前,我们需要把各感光单元所处的位置坐标也考虑进来。将式 \eqref{eq:response_model} 扩展到整个传感器平面上,可以写作〔其实就是在式 \eqref{eq:response_model} 的每个符号后面加上位置坐标 $(i,j)$ 而已~〕:

\begin{equation} \begin{split} D(i,j) = round\bigg[&Tg(i,j)S_r(i,j)A(i,j)U(i,j)\int_\lambda\,L(\lambda)\,t(i,j,\lambda)\,q(i,j,\lambda)\,\textrm{d}\lambda\bigg.\\[.5em] \bigg.& + D_\mathrm{offset}(i,j)\bigg] \end{split} \label{eq:spatial_response_model} \end{equation}

接下来,我们来看一下相机成像过程中存在的几类主要噪声:

● 像素响应非均匀性

由于制造加工过程中的一些原因,传感器各个感光单元之间在有效感光面积、光电转换效率、非均匀性、滤色片光谱透过率等指标上往往存在一定的差异。这种像素之间的差异通常被称为像素响应非均匀性(pixel response non-uniformity, PRNU)。严格意义上 PRNU 应该属于「系统误差」而不是「噪声」,但是出于表述方便的需求我仍然把它划分到传感器噪声的范畴里啦。

稍微做一些简化,如果不考虑波长 $\lambda$ 上的偏差,那么对于某一个感光单元,可以使用非均匀性分布函数

\begin{equation} K(i,j) = \frac{g(i,j)}{g_0}\frac{\overline{S}(i,j)}{\overline{S}_0}\frac{A(i,j)}{A_0}\frac{t(i,j)}{t_0}\frac{q(i,j)}{q_0} \label{eq:prnu} \end{equation}

来表征该感光单元的整体制造误差,其中 $g_0$ 表示一个理想的、无任何制造误差的感光单元所对应的综合增益,$\overline{S}_0$、$A_0$、$t_0$、$q_0$ 同理。在现有的半导体制造工艺下, $K$ 的数值仅在单位强度附近很小的一段区间内波动,因此有

\begin{equation} \overline{E}(K) = 1\,,\qquad\overline{\mathrm{var}}(K)\ll 1 \label{eq:prnu_prop} \end{equation}

这里的 $\overline{E}(\cdot)$ 和 $\overline{\mathrm{var}}(\cdot)$ 分别表示在空间维度上计算均值(mean)与样本方差(sample variance),下同。

💡 各个像素的 $K(i,j)$ 其实在相机出厂时就已经确定了,并不是一个随机变量,所以只能计算均值与样本方差,不存在数学期望或者方差之说;而后文中的一些噪声属于随机变量,因此可以计算数学期望与方差。期望与均值、方差与样本方差之间的区别需要特别注意一下。

把式 \eqref{eq:prnu} 代入式 \eqref{eq:spatial_response_model} 后,有

\begin{equation} D(i,j)=round\left[g_0K(i,j)U(i,j)I_0+D_\mathrm{offset}(i,j)\right] \label{eq:spatial_response_model_with_prnu} \end{equation}

其中

\begin{equation} I_0 = \frac{\pi{}T}{4F^2}\overline{S}_0A_0\int_\lambda\,L(\lambda)t_0(\lambda)q_0(\lambda)\,\mathrm{d}\lambda \label{eq:I0} \end{equation}

表示一个理想的、无任何制造误差的感光单元理论上释放出的电子数。这个 $I_0$ 在后文中会多次出现。

● 热噪声

光电转换器件的热效应将产生一定数量的服从泊松分布的自由电子,从而使信号出现一定程度的波动,因此此类噪声也被称为热噪声(thermal noise)$N_{Th}$ 。热噪声的强度仅取决于曝光时间 $T$ 以及环境温度,而与光电效应产生的电子数 $I$ 无关。

热噪声是一个随机变量,根据泊松分布的性质有

\begin{equation} E(N_{Th}) = \mathrm{var}(N_{Th}) = \mu_{Th} \label{eq:thermal_noise_prop} \end{equation}

其中 $\mu_{Th}$ 是一个与曝光时间及环境温度有关的变量,$E(\cdot)$、$\mathrm{var}(\cdot)$ 分别表示计算数学期望(expectation)与方差(variance),下同。

把热噪声考虑进来后,式 \eqref{eq:spatial_response_model_with_prnu} 就变成了

\begin{equation} D(i,j)=round\big\{g_0\left[K(i,j)U(i,j)I_0+N_{Th}(i,j)\right]+D_\mathrm{offset}(i,j)\big\} \label{eq:spatial_response_model_with_thermal_noise} \end{equation}

● 散粒噪声

根据量子理论,从感光单元中释放出的电子在数量上存在一个随机的涨落,该涨落即为散粒噪声(photon shot noise)$N_S$。散粒噪声是由大量单个事件的统计不确定性引起的,其中主要包括输入光子散粒噪声、光生电流散粒噪声与热效应散粒噪声。散粒噪声的存在使得感光单元最终释放出的有效电子数服从泊松分布:$(I^\ast+N_S)\sim{}Poisson(I^\ast)$,其中 $I^\ast=KUI_0+N_{Th}$ 表示在不考虑散粒噪声的情况下该感光单元应释放出的电子个数。

由于泊松分布有 $E(x)=\mathrm{var}(x)=\lambda$ 的性质,那么对于加入散粒噪声后的信号 $(I^\ast+N_S)$ 而言,有

\begin{equation} E(I^\ast+N_S) = \mathrm{var}(I^\ast+N_S) = I^\ast = KUI_0+N_{Th} \label{eq:signal_with_shot_noise_charact} \end{equation}

因此对于散粒噪声自身而言,有

\begin{equation} E(N_S) = 0\,,\qquad{}\mathrm{var}(N_S) = I^\ast = KUI_0+N_{Th} \label{eq:shot_noise_charact} \end{equation}

把散粒噪声考虑进来后,式 \eqref{eq:spatial_response_model_with_thermal_noise} 就变成了

\begin{equation} \begin{split} D(i,j)=round\Big\{&g_0\big[K(i,j)U(i,j)I_0+N_{Th}(i,j)+N_S(i,j)\big]\Big.\\[.6em] \Big.& + D_\mathrm{offset}(i,j)\Big\} \end{split} \label{eq:spatial_response_model_with_shot_noise} \end{equation}

● 固定模式噪声

对于 CMOS 来说,其各个感光单元所对应的模拟电路中有可能存在一定的噪声,此类噪声通常被称为固定模式噪声(fixed pattern noise, FPN)$N_{FP}$。固定模式噪声与像素响应非均匀性类似,均属于系统误差,两者的区别在于,固定模式噪声是由电路的制造误差引起的,与光电效应无关,且通常呈现出比较明显的空间分布规律。

把固定模式噪声考虑进来后,式 \eqref{eq:spatial_response_model_with_shot_noise} 就变成了

\begin{equation} \begin{split} D(i,j)=round\Big\{&g_0\big[K(i,j)U(i,j)I_0+N_{Th}(i,j)+N_S(i,j)+N_{FP}(i,j)\big]\Big.\\[.6em] \Big.& + D_\mathrm{offset}(i,j)\Big\} \end{split} \label{eq:spatial_response_model_with_fpn} \end{equation}

● 读出噪声

信号放大单元把模拟电压信号进行放大的过程中会引入一定的读出噪声(read-out noise)$N_R$。读出噪声的期望为零,其方差与放大单元的增益系数 $g$ 成线性正相关。

把读出噪声考虑进来后,式 \eqref{eq:spatial_response_model_with_fpn} 就变成了

\begin{equation} \begin{split} D(i,j)=round\bigg\{&g_0\Big[K(i,j)U(i,j)I_0+N_{Th}(i,j)+N_S(i,j)+N_{FP}(i,j)\Big.\bigg.\\[.2em] \bigg.\Big.& + N_R(i,j)\Big] + D_\mathrm{offset}(i,j)\bigg\} \end{split} \label{eq:spatial_response_model_with_readout_noise} \end{equation}

● 量化误差

由于 ADC 输出的数字信号需要以整数的形式存储在寄存器中,因此这一连续信号转化为离散信号的过程中必然会引入一定的量化误差(quantization error)$N_Q$(一些文献中也称为取整误差)。若进入模数转换单元的模拟电压信号等概率地分布于实数轴上,则量化误差将服从区间 $[-\tfrac{1}{2}, \tfrac{1}{2}]$ 内的均匀分布,即 $N_Q\sim{}\mathcal{U}(-\tfrac{1}{2}, \tfrac{1}{2})$,因此有

\begin{equation} E(N_Q) = 0\,,\qquad{}\mathrm{var}(N_Q) = \tfrac{1}{12} \label{eq:quantization_error_charact} \end{equation}

把量化误差考虑进来后,式 \eqref{eq:spatial_response_model_with_readout_noise} 就变成了

\begin{equation} \begin{split} D(i,j) & = g_0\Big[K(i,j)U(i,j)I_0 + N_{Th}(i,j) + N_S(i,j) + N_{FP}(i,j) + N_R(i,j)\Big]\\[.8em] & + D_\mathrm{offset}(i,j) + N_Q(i,j) \end{split} \label{eq:noise_model} \end{equation}

这个就是带有噪声项的数字相机原始响应值构成模型。再次注明一下,$I_0$ 代表的是一个完美的感光单元理论上释放出的电子数〔见式 \eqref{eq:I0}〕。

显然,由于热噪声、散粒噪声、读出噪声、量化误差均为随机变量,因此式 \eqref{eq:noise_model} 中的原始响应值 $D$ 也是一个随机变量。

把以上六种噪声或者误差的统计特性进行了一个简单的整理,如下表所示。

噪声来源符号分布类型期望/样本均值方差/样本方差
像素响应非均匀性$K$$\overline{\mu}_K=1$$\overline{\sigma}_K^2\ll{}1$
固定模式噪声$N_{FP}$$\overline{\mu}_{FP}$ 未知$\overline{\sigma}_{FP}^2$未知
热噪声$N_{Th}$泊松分布$\mu_{Th}$ 未知$\sigma_{Th}^2$ 未知
散粒噪声$N_S$泊松分布(有效信号+散粒噪声)$\mu_S=0$$\sigma_S^2=KUI_0+N_{Th}$
读出噪声$N_R$未知$\mu_R=0$$\sigma_R^2$ 未知
量化误差$N_Q$均匀分布$\mu_Q=0$$\sigma_Q^2=\tfrac{1}{12}$

3. 噪声估计与误差修正

如果只是要了解图像噪声的来源和大致特性的话,看完上面一节就够了。这一节谈一下如何对传感器的噪声进行参数估计,以及如何利用估计出的噪声参数对像素响应非均匀性(PRNU)以及固定模型噪声(FPN)这两类系统误差进行修正。

为什么我们需要大费周章地对图像传感器的噪声和误差进行分析呢?首先,在进行对成像精度有很高要求的图像处理时,我们希望能够把相机当作严格的测量仪器而不是一个简单的光信号传感器。既然是测量仪器,那么系统的可靠性和测量结果的可复现性自然是一个重要的性能评价指标,因此系统误差的标定和修正就成为了一个必不可少的步骤。其次,如果不通过建模的手段对噪声和误差的参数进行估计,那么当相机的拍摄参数改变时,或者当场景发生变化时(因为噪声与电子数有关,而电子数与场景自身的辐射特性有关),已有的标定信息将不可避免地失效。因此这一「建模+参数估计」的过程实际上是将相机的标定流程从离线切换至了在线,从而允许我们更加灵活地对系统进行修正。

不过呢,因为推导过程需要引入传感器平面的位置坐标,所以这一节里的各种公式可能看起来并不是那么友好 😳

这一节中所有与空间位置有关的参数都将显式地用 $(i,j)$ 进行表示。若不带该空间坐标则表示该参数为一位置无关的常量。

3.1 一点准备工作:噪声的分解

为了后续推导的需要,我们需要把式 \eqref{eq:noise_model} 中的原始响应值 $D(i,j)$ 拆解为两部分:第一部分 $\mu(i,j)$ 表示其中的直流项(也就是信号的期望),第二部分 $N(i,j)$ 表示由随机变量构成的噪声项:

\begin{equation} D(i,j) = \mu(i,j) + N(i,j) \label{eq:decomposition0} \end{equation} \begin{equation} \left\{ \begin{split} \mu(i,j) & = g_0\Big[K(i,j)U(i,j)I_0 + N_{FP}(i,j) + \mu_{Th}\Big] + D_\mathrm{offset}(i,j)\\[.5em] N(i,j) & = g_0\Big[N_{Th}(i,j) – \mu_{Th} + N_S(i,j) + N_R(i,j)\Big] + N_Q(i,j) \end{split} \right. \label{eq:decomposition} \end{equation}

显然,$N(i,j)$ 是一个期望为零的随机变量,且根据前文,它的方差可以表示为

\begin{equation} \sigma_N^2(i,j) = g_0^2\left[\sigma_S^2(i,j)+\sigma_R^2\right] + \sigma_Q^2 \label{eq:noise_variance} \end{equation}

💡 式 \eqref{eq:noise_variance} 中第二个等式里,热噪声 $N_{Th}$ 的方差正好等于它的期望(因为是泊松分布),因此两项相减恰好为零;读出噪声的方差 $\sigma_R^2$ 是一个全局常数,与感光单元所处的位置无关,因此可以省略位置坐标 $(i,j)$。

另外,既然都写到这里了,那就顺带推导一下信噪比的计算公式吧。

信噪比(signal-noise-ratio)定义为信号强度的平方 $\mu^2$ 与方差 $\sigma^2$ 之比,因此有

\begin{equation*} \begin{split} S/N & = 10\log_{10}\left[\frac{E\left(D^2\right)}{E\left(\sigma_N^2\right)}\right] = 20\log_{10}\left[\frac{\mu}{E(\sigma_N)}\right]\\[1em] & = 20\log_{10}\Bigg\{\frac{g_0\left(KUI_0 + N_{FP} + \mu_{Th}\right)}{E\left[\sqrt{g_0^2\left(\sigma_S^2+\sigma_R^2\right)+\sigma_Q^2}\,\right]}\Bigg\} \end{split} \label{eq:snr} \end{equation*}

计算信噪比的时候不妨忽略空间非均匀性调制函数 $U$ 和小量 $\sigma_Q^2$,也不考虑 PRNU 和 FPN 两种系统误差,并假设暗电流偏置为零,此时,上式可以简化为

\begin{equation*} \begin{split} S/N & = 20\log_{10}\bigg(\frac{I_0 + \mu_{Th}}{\sqrt{I_0 + \mu_{Th} +\sigma_R^2}}\bigg) \end{split} \label{eq:snr0} \end{equation*}

由此我们可以得出那个众所周知的结论:要提高图像的信噪比,只能增加传感器上收集的电子数。调节增益 $g_0$ 或者一味地增加位深都是没有任何帮助的(cf. Noise, Dynamic Range, and Bit Depth)。而要增加电子数,要么延长积分时间 $T$,要么改进光电转换函数 $q$ 使其具有更高的转换效率。

另外,从信噪比公式其实也可以得出结论:对于处理 raw 图像的同学来说,前期拉高 ISO(增大 $g_0$)和后期调节图像亮度其实并没有本质区别。

进一步地,根据噪声与电子数 $I$ 之间的相关性,式 \eqref{eq:decomposition} 中的噪声项 $N(i,j)$ 可以再被拆解为与电子数相关噪声 $N_I(i,j)$ 以及与电子数无关噪声 $N_C(i,j)$:

\begin{equation} \left\{ \begin{split} N_I(i,j) & = g_0N_S(i,j)\\[.8em] N_C(i,j) & = g_0\left[N_{Th}(i,j) + N_R(i,j) – \mu_{Th}\right] + N_Q(i,j) \end{split} \right. \label{eq:noise_decomposition} \end{equation}

根据散粒噪声的特性,有:

\begin{equation} \begin{split} \sigma_I^2(i,j) & = g_0^2\sigma_S^2(i,j)\\[.8em] & = g_0^2\left[K(i,j)U(i,j)I_0(i,j) + N_{Th}(i,j)\right] \end{split} \label{eq:photon_dependent_noise} \end{equation}

同时,根据前文还有

\begin{equation} \sigma_C^2(i,j) = g_0^2\left(\mu_{Th} + \sigma_R^2\right) + \frac{1}{12} \label{eq:photon_independent_noise} \end{equation}
正如上面提到的,热噪声的期望与读出噪声的方差均与感光单元所处的位置无关,因此 $\mu_{Th}$ 与 $\sigma_R^2$ 可省略位置坐标。此外,需要注意一下,$N_C(i,j)$ 的表达式 \eqref{eq:noise_decomposition} 中减号右边的那个 $\mu_{Th}$ 是一个常数项,所以在计算方差时得到的其实就是零。

3.2 整体噪声估计

在对图像传感器的系统误差进行估计之前,我们需要先对整体噪声水平 $N(i,j)$ 进行参数估计,而 $N(i,j)$ 的参数当然指的就是它的方差 $\sigma_N^2(i,j)$ 啦。

正如渐晕图片里显示的那样,即使对于完全均匀的场景,传感器不同区域也会对应于不同的照度,从而对应不同的释放电子数。对于位于传感器的中心与边缘的两个感光单元来说,两者有可能相差数倍以上。因此从这一小节开始,对于实际噪声的参数估计,若无特别说明,均指对于传感器某一特定区域进行计算。若要对整个传感器进行标定,可先分区域处理($5\times5$ 或者 $7\times7$ 一般就够了),最后再对各区域的参数进行插值从而得到全局的噪声参数估计。

对一台相机在原始响应值上的最终噪声进行估计通常有两种方法,一是采集两幅 raw 图像 $D_1(i,j)$ 及 $D_2(i,j)$,并在其差值图像的基础上进行噪声方差估计;二是采集多幅图像,然后在均值图像的基础上进行估计。第一种方法涉及的计算稍微麻烦一些,所以我就只介绍第二种方法啦。

在对整体噪声方差 $\sigma_N^2(i,j)$ 进行估计之前,我们需要先计算一下期望值 $\mu(i,j)$。显然,一种最简单的方法就是在时域上对带噪声的信号进行平滑。

假如我们对着同一固定场景使用完全相同的参数拍摄 $n$ 张图像,那么有

\begin{equation} \left\{\begin{split} D_1(i,j) & = \mu_1(i,j) + N_1(i,j)\\[.5em] & \vdots\\[.5em] D_{n}(i,j) & = \mu_{n}(i,j) + N_{n}(i,j) \end{split}\right. \label{eq:multiple_images} \end{equation}

由于噪声项 $N(i,j)$ 具有 $E\left[N(i,j)\right] = 0$ 的特性,而当图像数量 $n$ 足够大时,我们可以使用样本均值作为数学期望的一致性近似。因此,$\mu(i,j)$ 的一致性估计结果也就可以通过在时域上计算均值的方法得到:

\begin{equation} \begin{split} \widehat{\mu}(i,j) & \approx \widetilde{E\,}\left[\mu(i,j)\right]\\[.5em] & = \tfrac{1}{n}\sum\nolimits_{p=1}^{n}\mu_p(i,j)\\[.5em] & = \tfrac{1}{n}\sum\nolimits_{p=1}^{n}D_p(i,j) – \tfrac{1}{n}\sum\nolimits_{p=1}^{n}N_p(i,j)\\[.5em] & \approx \tfrac{1}{n}\sum\nolimits_{p=1}^{n}D_p(i,j) – E\left[N(i,j)\right]\\[.5em] & = \tfrac{1}{n}\sum\nolimits_{p=1}^{n}D_p(i,j) \end{split} \label{eq:mu_estimator} \end{equation}

式中的 $\widetilde{E\,}(\cdot)$ 表示在时域中计算均值(temporal mean),下同。

接着,我们使用 $\widetilde{\mathrm{var}}\left[N(i,j)\right]$ 作为 $\mathrm{var}\left[N(i,j)\right]$ 的近似,从而得到 $\sigma_N^2(i,j)$ 的一致性估计:

\begin{equation} \begin{split} \widehat{\sigma}_N^2(i,j) & \approx \widetilde{\mathrm{var}}\left[N(i,j)\right]\\[.8em] & = \widetilde{\mathrm{var}}\left[D(i,j)\right]\\[.8em] & = \tfrac{1}{n}\sum\nolimits_{p=1}^{n}\left[D_p(i,j)-\widehat{\mu}(i,j)\right]^2 \end{split} \label{eq:overall_noise_estimator} \end{equation}

式中的 $\widetilde{\mathrm{var}}(\cdot)$ 表示在时域中计算样本方差(temporal sample variance),下同。

我使用这种方法对 Nikon D3x 和 SONY A7 两台相机 G 通道的整体噪声方差 $\sigma_N^2(i,j)$ 进行了估计,结果如下图所示。实验时我使用均匀白板作为拍摄对象,并根据 ISO 的不同对光源的强度进行了调解以确保 raw 图像中的响应值尽可能占满有效位深,同时将拍摄张数 $n$ 设定为16。

Nikon D3x 的 $\widehat{\sigma}_N^2(i,j)$
曝光时间固定为 1/8s,14-bit 位深
SONY A7 的 $\widehat{\sigma}_N^2(i,j)$
曝光时间固定为 1/8s,12-bit 位深

3.3 综合增益系数的估计

接下来我们讨论一下如何通过拍照的方式对相机的综合增益 $g_0$ 进行估计。这部分的内容会稍微 tricky 一些~

由于 $N_I$ 与 $N_C$ 之间不存在相关性,因此整体随机噪声的方差 $\sigma_N^2$ 可被分解为两部分:

\begin{equation} \begin{split} \sigma_N^2(i,j) & = \sigma_I^2(i,j) + \sigma_C^2(i,j)\\[.6em] & = g_0^2\left[K(i,j)U(i,j)I_0 + N_{Th}(i,j)\right] + \sigma_C^2 \end{split} \label{eq:overall_noise_decomposition} \end{equation}

接下来要划重点啦~ 对于式 \eqref{eq:decomposition} 中的 $\mu(i,j)$,我们可以对等号两侧同时计算局部空间均值(local spatial mean),那么有

\begin{equation} \begin{split} \overline{E}^w\left[\mu(i,j)\right] & = g_0\left\{\overline{E}^w\left[K(i,j)U(i,j)\right]I_0 + \overline{E}^w\left[N_{FP}(i,j)\right] + \mu_{Th}\right\}\\[.6em] & + \overline{E}^w\left[D_\mathrm{offset}(i,j)\right] \end{split} \label{eq:mu_local_spatial_mean} \end{equation}

这里的符号 $\overline{E}^w\left[x(i,j)\right]$ 表示以 $(i,j)$ 坐标为中心,在 $w\times{}w$ 的窗口内对矩阵 $\mathbf{x}$ 进行空间均值滤波(在实际操作时其实就是用一个所有元素都相同的 $w\times{}w$ 核对 $\mathbf{x}$ 进行卷积)。

下面简单说一下这一步空间均值滤波的用处。我们知道,非均匀性函数 $U(i,j)$ 的空间变化整理来说是十分缓慢的(看上图),因此当窗口尺寸 $w$ 不是太大时,可认为该窗口内的 $U(i,j)$ 是一个常数,不妨就叫作 $\overline{U}^w$ 吧。另一方面,当 $w$ 比较大时,这一窗口内包含的像素数就足够多了,因此在一个足够大的窗口内对 $K(i,j)$、$N_{Th}(i,j)$ 以及 $D_\mathrm{offset}(i,j)$ 计算均值,得到的结果也可以认为是位置无关的,并且根据上文有

\begin{equation} \left\{ \begin{split} \overline{E}^w\left[K(i,j)\right] & \approx 1\\[.4em] \overline{E}^w\left[N_{Th}(i,j)\right] & \approx \mu_{Th}\\[.4em] \overline{E}^w\left[D_\mathrm{offset}(i,j)\right] & \approx \mu_\mathrm{offset}\\ \end{split} \right.\qquad{}\mathrm{if}\ w \mathrm{\ is\ large\ enough} \label{eq:local_spatial_means} \end{equation}

这里的 $\mu_\mathrm{offset}$ 表示响应值偏置量的期望值(暗电流!),可以从传感器的参数手册里查到,或者用 Dcraw 之类的软件获得。

同样的道理,对于式 \eqref{eq:overall_noise_decomposition},我们也可以对等号两侧同时同时计算局部空间均值,有

\begin{equation} \overline{E}^w\left[\sigma_N^2(i,j)\right] = g_0^2\left\{\overline{E}^w\left[K(i,j)U(i,j)I_0\right] + \overline{E}^w\left[N_{Th}(i,j)\right]\right\} + \sigma_C^2 \label{eq:overall_noise_local_spatial_mean} \end{equation}

所以,只要我们选取一个合适的不大不小的( 😳 )窗口尺寸 $w$,然后把式 \eqref{eq:local_spatial_means} 分别代入式 \eqref{eq:mu_local_spatial_mean} 和式 \eqref{eq:overall_noise_local_spatial_mean} 中,就可以得到

\begin{equation} \left\{ \begin{split} \overline{\mu}^w(i,j) & = g_0\left[\overline{U}^w(i,j)I_0 + \overline{N}_{FP}^w(i,j) + \mu_{Th}\right] + \mu_\mathrm{offset}\\[.5em] \overline{\sigma_N^2}^w(i,j) & = g_0\left[\overline{U}^w(i,j)I_0 + \mu_{Th}\right] + \sigma_C^2 \end{split} \right. \label{eq:mu_vs_overall_noise} \end{equation}

观察上式就会发现,如果把 $\overline{\mu}^w(i,j)$ 当作自变量 $x$,把 $\overline{\sigma_N^2}^w(i,j)$ 当作因变量 $y$,那么两者之间其实显然满足 $y=ax+b$ 的直线方程,其中斜率就是 $g_0$,而截距是 $\sigma_C^2 – g_0\mu_\mathrm{offset} – g_0^2\overline{N}_{FP}^w(i,j)$:

\begin{equation} \overline{\sigma_N^2}^w(i,j) = g_0\overline{\mu}^w(i,j) + \left[\sigma_C^2 – g_0\mu_\mathrm{offset} – g_0^2\overline{N}_{FP}^w(i,j)\right] \label{eq:mu_vs_overall_noise_formula} \end{equation}

所以呢,只要我们能够获得足够多的 $(x,y)$ 样本点,那么自然就可以通过直线拟合的方式确定斜率 $g_0$ 了。实际操作时,可以通过改变光源亮度的方式获得 $n$ 组不同的 $D(i,j)$,然后对式 \eqref{eq:mu_estimator} 和式 \eqref{eq:overall_noise_estimator} 进行卷积得到局部均值图像,从而得到 $n$ 组 $\left[\overline{\mu}^w(i,j),\ \overline{\sigma_N^2}^w(i,j)\right]$ 坐标,接着在使用最大似然估计对 $g_0$ 进行计算。

改变光源亮度这一操作对于实验环境的要求比较苛刻,因为大多数标准光源基本上都不允许对亮度进行调节,即使能够调节,也很难保证相对光谱功率不发生变化。此外再澄清一点,图像的张数 $n$ 和窗口尺寸 $w$ 这两个符号在上下文中可能会多次出现,我故意不对它们加以区分,否则公式读起来实在太不友好了…… 大家自行区分吧,它们在不同小节中都是独立存在的。

但是呢,用这种拟合的方式去估计 $g_0$ 会出现一个问题:式 \eqref{eq:mu_vs_overall_noise_formula} 中自变量和因变量都是带有位置坐标 $(i,j)$ 的,因此最终估计出的综合增益 $g_0$ 也必定是位置相关的。而在前文我们说了,$g_0$ 表示的是一个理想的、无任何制造误差的感光单元所对应的综合增益,所以我们还需要对估计出的 $g_0(i,j)$ 求一个平均值以获得最终的综合增益估计值 $\widehat{g}_0$。

对于 Nikon D3x 和 SONY A7 两台相机使用这种方法估计出的综合增益 $g_0$ 如下图所示。实验中我选取的窗口尺寸 $w=15$,光源等级数 $n=8$。

Nikon D3x 的三通道综合增益 $\widehat{g}_0$
SONY A7 的三通道综合增益 $\widehat{g}_0$

3.4 暗电流噪声的估计

在前文中我并没有严格地对「暗电流」这个名词进行定义,这里补充解释一下。

如果我们在完全黑暗(光子数为零)的条件下使用相机进行拍摄,那么式 \eqref{eq:decomposition} 可以改写为

\begin{equation} \left\{ \begin{split} \mu_\mathrm{dark}(i,j) & = g_0N_{FP}(i,j) + g_0\mu_{Th} + D_\mathrm{offset}(i,j)\\[.5em] N_\mathrm{dark}(i,j) & = g_0\Big[N_{Th}(i,j) + N_{S,\mathrm{dark}}(i,j) + N_R(i,j) – \mu_{Th}\Big] + N_Q(i,j) \end{split} \right. \label{eq:decomposition_dark} \end{equation}

显然,这里的 $\mu_\mathrm{dark}(i,j)$ 对应了相机在未接收到任何光信号情况下对应的数字响应值期望,因此也通常被称为暗电流(dark current)或者黑电平(black level)。在很多场合我们提起相机的暗电流通常指的是一个具体的数字,比如像下面这张图中 Dcraw 检测到的两台相机 darkness 分别为0和128,但是在需要严格标定的情况下,相机的暗电流噪声应该是一个关于各个像素坐标 $(i,j)$ 的函数,$\mu_\mathrm{dark}(i,j)$,而不是一个简单的常数。在这篇文章中,我认为相机的固定模式噪声 $N_{FP}(i,j)$ 和响应值偏置量 $D_\mathrm{offset}(i,j)$ 共同组成了暗电流噪声,当然,还包括热噪声期望 $\mu_{Th}$ 以及综合增益 $g_0$。

Dcraw 检测出的两台相机的暗电流数值大小

估计暗电流噪声 $\mu_\mathrm{dark}(i,j)$ 当然再简单不过啦,利用 $E\left[N_\mathrm{dark}(i,j)\right]=0$ 这个性质,我们只需要在全黑环境下拍摄 $n$ 幅图像然后取一下均值,就可以得到关于暗电流噪声的一致性估计:

\begin{equation} \begin{split} \widehat{\mu}_\mathrm{dark}(i,j) & \approx \widetilde{E\,}\left[\mu_\mathrm{dark}(i,j)\right]\\[.5em] & = \tfrac{1}{n}\sum\nolimits_{p=1}^{n}D_{\mathrm{dark},p}(i,j) – \tfrac{1}{n}\sum\nolimits_{p=1}^{n}N_{\mathrm{dark},p}(i,j)\\[.5em] & \approx \tfrac{1}{n}\sum\nolimits_{p=1}^{n}D_{\mathrm{dark},p}(i,j) – E\left[N_\mathrm{dark}(i,j)\right]\\[.5em] & = \tfrac{1}{n}\sum\nolimits_{p=1}^{n}D_{\mathrm{dark},p}(i,j) \end{split} \label{eq:dark_current_estimator} \end{equation}

下面两幅图是我使用这种方法对两台相机 G 通道估计出的暗电流噪声 $\widehat{\mu}_\mathrm{dark}(i,j)$,这里的拍摄张数 $n$ 我设定为16。由于暗电流噪声中包含了固定模式噪声,因此在下图中其实已经可以明显地看出 $\widehat{\mu}_\mathrm{dark}(i,j)$ 中的 pattern 是存在一定的空间规律的,比如 Nikon D3x 的噪声就呈现出纵向相似性和横向相异性。

Nikon D3x 的 $\widehat{\mu}_\mathrm{dark}(i,j)$
曝光时间固定为 1/8s,ISO100,$\mu_\mathrm{offset}=0$
SONY A7 的 $\widehat{\mu}_\mathrm{dark}(i,j)$
曝光时间固定为 1/8s,ISO100,$\mu_\mathrm{offset}=128$

3.5 像素响应非均匀性的估计

最后,我们再讨论一下如何对像素响应非均匀性 $K(i,j)$ 进行估计。

这一小节的方法其实和图像处理中的空间滤波有异曲同工之处,因为虽然 $K(i,j)$ 与 $U(i,j)$ 看似结合在了一起共同影响了最终的响应值,但我们可以利用它们空间频率存在差异这一性质对其进行解耦。

为表达方便起见,我首先令 $e(i,j) = K(i,j)U(i,j)I_0$。回想一下前文提到的一些性质,如果我们对 $e(i,j)$ 中各像素在一个 $w\times{}w$ 的邻域内计算均值,那么一方面,当 $w$ 足够小时,可认为在该窗口内的 $U(i,j)$ 为一常数〔因为 $U(i,j)$ 不含高频成分〕,不妨称作 $\overline{U}^w$;另一方面,当 $w$ 足够大时,该窗口内 $K(i,j)$ 的均值必定趋向于1〔因为 $K(i,j)$ 的数值总是在1附近浮动,可参考上文第2节〕。因此,我们只需要选取一个合适的窗口尺寸 $w$,就可以得到

\begin{equation} \overline{e}^w(i,j) = \overline{E}^w\left[e(i,j)\right] \approx \overline{U}^w(i,j)I_0 \label{eq:prnu_spatial_mean} \end{equation}

利用这个近似,就可以对 $K(i,j)$ 进行估计啦:

\begin{equation} K(i,j) \approx \frac{K(i,j)U(i,j)}{\overline{U}^w(i,j)} = \frac{e(i,j)}{\overline{e}^w(i,j)} \label{eq:prnu_estimator} \end{equation}

因此,和第3.3小节中 $g_0$ 的估计方法一样,只要获得足够多的 $x$ 和 $y$ 坐标〔也就是 $\overline{e}^w(i,j)$ 和 $e(i,j)〕$,我们就可以通过直线拟合的方式估计出一条斜率为 $K(i,j)$ 且经过原点的直线。实际操作时,仍然可通过改变光源亮度的方式获得 $n$ 组 $\left[\overline{e}^w(i,j),\ e(i,j)\right]$ 坐标。

为了避免拍摄物体表面存在的缺陷或污点对直线拟合造成干扰,可以在每次切换光源时稍微移动被摄物体,这样即使在 $n$ 组样本点中存在个别异常点,也很容易发现并剔除,从而保证结果的鲁棒性。

下面再讨论一下 $e(i,j)$ 的计算。

联立式 \eqref{eq:decomposition0} 和式 \eqref{eq:decomposition},有

\begin{equation} D(i,j) = g_0\left[e(i,j) + N_{FP}(i,j) + \mu_{Th}\right] + D_\mathrm{offset}(i,j) + N(i,j) \label{eq:response_reformula} \end{equation}

再一次地,利用 $E\left[N(i,j)\right]=0$ 这一性质,我们可以通过在同一条件下拍摄 $n$ 幅图像并计算均值的方式消去 $N(i,j)$:

\begin{equation} \begin{split} \widetilde{E}\left[D(i,j)\right] & = \tfrac{1}{n}\sum\nolimits_{p=1}^n{}D_p(i,j)\\[.6em] & \approx g_0\left[e(i,j) + N_{FP}(i,j) + \mu_{Th}\right] + D_\mathrm{offset}(i,j) \end{split} \label{eq:response_without_noise} \end{equation}

所以,联立式 \eqref{eq:response_without_noise} 和式 \eqref{eq:decomposition_dark} 就可以得到 $e(i,j)$ 的估计:

\begin{equation} \widehat{e}(i,j) \approx \frac{\tfrac{1}{n}\sum\nolimits_{p=1}^n{}D_p(i,j)-\widehat{\mu}_\mathrm{dark}(i,j)}{\widehat{g}_0} \label{eq:e_estimator} \end{equation}

下面两幅图是我对两台相机 G 通道中像素响应非均匀性 $K(i,j)$ 估计出的结果,这部分实验中我选用了 $n=16$ 以及 $w=15$。

Nikon D3x 的 $\widehat{K}(i,j)$
SONY A7 的 $\widehat{K}(i,j)$

References

新的博客

作者 Jueqin
2019年10月19日 22:21

没错,这个万年一更的博客其实并没有 out of service。不但如此,我还把原来的博客迁移到了阿里云上,WordPress 和主题都进行了升级,算是迎接一个新的开始吧。

这段时间主要折腾了以下一些 features:

  • 用 child-theme 调整了一些页面外观,对中文更友好一些
  • 全站 https(只要用了阿里云全家桶这个可以傻瓜式搞定)
  • 评论收到回复后发送邮件通知
  • 本地部署了 MathJax 3.0
  • 原来部分放在 Flickr 上的图片全部转到了七牛下,同时开了防盗链
  • Gravatar 头像也缓存到七牛
  • 把用到的 Google Fonts 缓存到了本地,确保全站都没有指向 gstatic.com 的连接,另外还顺带研究了一下 CSS 里面的 font-display 属性
  • 用 WP-Rocket 进行缓存
  • 给所有静态文件开了 CDN
  • highlight.js 作为代码高亮方案
  • 给 wp-admin 和 wp-login 界面设置了 IP 白名单,防止机器人暴力登录后台
  • 把 Google Fonts 上的思源宋体给本地化

国内大部分地区现在首页加载时间基本都能在1秒以内吧?

另外,由于 Google Search Console 中提示网站迁移后需要大约一年的时间才能完成所有收录条目的更新,所以我仍然保留了原来的博客,同时启用了全站301跳转。

不出意外的话,这个博客至少会服役接下来的三到五年吧。


最后还是贴一张原来博客的首页截图作为纪念吧:

LEGO Marauder 乐高掠夺者

作者 Jueqin
2018年11月10日 00:08

好久没玩乐高啦,趁着这次国庆在家窝了七天把这款掠夺者给搭了出来。关于这个 Marauder 我事先其实并不了解,纯粹只是觉得外形挺酷的而且基本不需要补件所以就决定搭它了。更多的介绍可以跳转至维基百科,或者在 Top Gear 里搜 Marauder。

这款 Marauder 的零件组成对于科技玩家来说还是比较友好的,这次补件仅仅补了几个近几年新出的连接销,以及2个3×11的红色面板。当然还有一些特殊件比如扶梯这种,由于并不影响具体功能,而且淘宝上价格过于离谱,我也就直接放弃了。

简介:

图片有裁剪及压缩,原图请跳转至我的 Flickr

变速箱的搭建。这款变速箱只有两速(准确来说是2+N,允许切入空挡),非常紧凑,而且搭建过程中与底盘完全是一体的,并不像 Defender 110GT500 那样可以独立拆装。由于结构相对简单,调试难度也很小,搭完就能用
底盘框架。搭之前以为是刹车时自动亮灯,后来才知道是靠两个座椅间的那个 switch 开关 LED 灯组,感觉蛮鸡肋的…… 原图纸中共用了三组 LED,车尾2组+车头1组,由于缺件我只在车尾用了一组
车头。由于缺了几个2×1的红色斜砖,只能用透明砖替代了
▲▼ 前桥模组。结构非常紧凑和牢固。图中红色轴套+深灰色齿轮处是差速锁,把轴套往齿轮处拨动启用差速锁,此时左右两轮无法以不同转速转动;反之关闭。这是我第一次搭建这种结构的差速锁,之前一直以为差速锁是靠锁死两轮间的差速器来实现的。
装上前桥。比较费解的是这里居然为每边车轮使用了两个弹簧,导致最终的前轮避震非常硬。而且在后面会看到,这车的前部底盘设计得其实是比后轮更低的,这就导致在实际行驶过程中前轮的避震基本没什么用…… 建议使用灰色偏软的那套弹簧或者干脆改为一个轮子配一个弹簧
装上接收器后线束乱得不行。这套 MOC 的说明书有一个很大的问题就是搭建过程中不考虑走线次序,好几处都需要拆开才能把接收器连接线穿过去
▲▼ 底盘。后桥没有单独拍照,用的是跟前桥基本一样的差速锁结构
这里看得很清楚了,在设计阶段其实后轴就要比前轴低1个单位左右,但是由于后轮只使用了一个避震弹簧,所有落地之后这个高度差基本被平衡掉了。进入驾驶室的那个阶梯也被我魔改了一番……
6个轮子其实都不是说明书上的型号,原作的前后轮要比这张图中小一圈
▲▼ 落地之后底盘基本水平
方向盘可以活动,但并不是跟转向系统联动。从驾驶室中穿过的是驱动绞盘的 M 电机的连接线
这个就是驱动绞盘的轴了。这款的引擎盖打开方式蛮有意思,把那个黑色的键往下一压引擎盖就弹出来了
车屁股。那个阶梯我稍微改长了一点,原作只有两节
前脸。原作里绞盘拉着的是一个小的吊钩,我给换了个大的
前轮的独立悬挂。由于弹簧太硬了,可以看出悬挂基本都没有下沉,得用手用力压车身才能看出避震的效果
原作里前轮上方的那个三角形面板是红色的,我由于缺了一个,就干脆都改为黑色了……
底盘高度调节示意图。左右两侧各由一个 M 电机控制底盘升降,所以是可以单独调节两侧的底盘高度的
后轮的避震弹簧
前轮的避震弹簧
车尾 LED 灯组效果

简单说一下我对这个 MOC 的感想。这款 Marauder 在控制上并没有什么花哨的地方,而是采用了最稳妥的策略:一个电机控制一个动作。这种设计的好处是整车非常稳定,基本不需要任何的调校就可以跑起来,而且可以很容易地进行改造,即使关键部分缺件也总是能找到其他方式完成搭建;当然,坏处就是搭建的过程相对无趣,完全无法体会到像搭建 8043 或者 Defender 110 时那种被机构设计惊艳到的感觉。另外还有一点要提的就是,作者提供的搭建手册虽然印制非常精美,但是很多步骤却并不是最佳的搭建顺序,如果完全按手册上的顺序来搭建的话,简直就是变态级的难度(比如各个电机和接收器的控制线需要从车身中穿来穿去)。相比之下,我还是喜欢 Defender 110GT500 这种模块化的结构设计。

Image quality degradation of object-color metamer mismatching in digital camera color reproduction

作者 Jueqin
2018年4月11日 19:16

PDF (1.74M) BibTeX

Description

Metamer mismatching is a phenomenon that two objects, which are colorimetrically indistinguishable under one lighting condition, become distinguishable under another one. Due to the unavailability of spectral information, metamer mismatching introduces an inherent uncertainty into cameras’ color reproduction. To investigate the degree of image quality degradation by the metamer mismatching, a large spectral reflectance database was collected in this study to search the object-color metamers sets (OCMSs) of the spectra in hyperspectral images. Then metamer-degraded images were constructed and compared with the ground truth images by directional statistics based color similarity index image quality assessment (DSCSI-IQA) metrics to evaluate the perceptual image degradation. The results indicate that the object-color metamer mismatching has only little impact on the image quality degradation, whereas the inappropriate selection of color correction matrices involved with the illumination metamerism is the primary factor for the accuracy decrease in the digital camera color reproduction.

Figures

Figure 1. Flowchart of the constructions of object-color metamer set (OCMS) and metamer-degraded images.
Figure 2. The spectral power distributions of 20 test illuminants. Blue solid line: 8 practical illuminants in Table 2. Yellow dashed line: the corresponding daylight series sources. Red dotted line: the corresponding 4-channel LED sources.
Figure 3. Spectral sensitivity functions of 3 test camera models. From left to right: Canon 60D DSLR, Nikon D80 DSLR, and PointGrey Grasshopper 50S5C industrial camera.
Figure 4. Degraded images and the corresponding color difference maps of “Painting” under the test illuminant F8 for Nikon D80 DSLR. (a) The ground truth image. (b) Metamer-degraded image with 0th percentile degree (i.e., root-polynomial color corrected image without the object-color metamer mismatching). (c) With 50th percentile degree. (d) With 95th percentile degree. (e) CAT02 color corrected image. (f) Inappropriate color corrected image by a daylight source-based color correction matrix.
Figure 5. DSCSI-IQA scores with different degrees of metamer mismatching for 3 test camera models.
Figure 6. DSCSI-IQA scores with different degrees of metamer mismatching for 8 practical test illuminants.

Publication

Reference

  • G. Wyszecki and W. S. Stiles, Color Science: Concepts and Methods, Quantitative Data and Formulae, vol. 8 (Wiley New York, 1982).
  • D. K. Prasad and L. Wenhe, “Metrics and statistics of frequency of occurrence of metamerism in consumer cameras for natural scenes,” J. Opt. Soc. Am. A, 32, 1390–1402 (2015).
  • I. Nimeroff and J. A. Yurow, “Degree of metamerism”, J. Opt. Soc. Am. 55, 185–190 (1965).
  • A. D. Logvinenko, B. Funt, H. Mirzaei, and R. Tokunaga, “Rethinking colour constancy”, PloS One, 10, e0135029 (2015).
  • X. Zhang, B. Funt, and H. Mirzaei, “Metamer mismatching in practice versus theory”, J. Opt. Soc. Am. A, 33, A238–A247 (2016).
  • More…
Feel free to contact me with any suggestions/corrections/comments.

Comparison of object-color and illuminant metamerism for digital image color correction

作者 Jueqin
2018年3月15日 14:02

Paper (4,752K) Slides(6,547K)

Description

Metamerism is the phenomenon that two object colors, which are colorimetrically indistinguishable under one lighting and viewing condition, become distinguishable under another condition. Since the number of channels of an RGB camera is less than that required to represent the spectral information, the variation of either the captured object or the illuminant may introduce color reproduction errors when transforming device-dependent RGB values to device-independent stimuli. In this study, we collected and utilized a large spectral reflectance database to investigate the color reproduction errors corresponding to the object-color metamerism, and employed a spectrally tunable LED light source to generate spectral power distributions (SPDs) that were metameric to a specific illuminant to analyze the reproduction errors corresponding to the illuminant metamerism. The image quality assessment (IQA) metric was adopted to evaluate the degree of image distortion caused by the two types of metamerism. The IQA results indicate that, compared with the illuminant metamerism, the object-color metamerism has little impact on the accuracy of color correction, and consequently the acquisition of the SPD of the illuminant is the critical factor for high-fidelity color reproduction.

Figures

Figure 1. Schematic diagram of the reconstruction of the color corrected images $I_\textrm{ideal}$ as well as the distorted image $I_\textrm{OCM}^k$ by the object-color metamerism.
Figure 2.The SPDs of the 8 test illuminants (blue solid line) with the daylight metamers (black dashed line) and 4-channel LED metamers (red dotted line). From left to right, top to bottom: CIE-A, D50, D100, CWF, F8, TL84, LED, and iPhone Flash. Note that CIE-A and TL84 have no daylight metamer since their CCT are below 4000K.
Figure 3. The DCSCI scores of two degrees of the object-color metamerism and two types of the illuminant metamerism.

Publication

Reference

  • Andersen, C. F. and Connah, D. 2016. Weighted constrained hue-plane preserving camera characterization, IEEE Trans. Image Process, 25(9) 4329–4339.
  • Prasad, D. K. and Wenhe, L. 2015. Metrics and statistics of frequency of occurrence of metamerism in consumer cameras for natural scenes, J. Opt. Soc. Am. A., 32(7) 1390–1402.
  • Hung, P. 2002. Sensitivity metamerism index for digital still camera, Photonics Asia 2002, 4922(2002) 1–14.
  • Qiu, J. and Xu, H. 2016. Camera response prediction for various capture settings using the spectral sensitivity and crosstalk model, Appl. Opt., 55(25) 6989–6999.
  • Zhang, F., Xu, H. and Wang, Z. 2016. Spectral design methods for multi-channel led light sources based on differential evolution, Appl. Opt., 55(28) 7771–7781.
  • More…
Feel free to contact me with any suggestions/corrections/comments.
❌
❌