openISP学习5-CNF — Chroma Noise Filtering(Bayer 域色度噪声滤波)

文章目录

1.算法原理

在 Bayer 域中,R 和 B 通道的采样密度是 G 通道的一半,因此更容易受到噪声影响。色度噪声表现为 R/B 像素相对于周围 G 像素异常偏高。CNF 在去马赛克之前直接在 Bayer 域中检测并校正这种噪声。

噪声检测(CND)

在 8×8 邻域内,在实际python代码一般使用 range(-4, 4)来遍历(这里更容易表现出像素坐标偏移,我们需要统计当前像素上下左右的像素),分别统计三类像素的均值:

  • avgG:绿色像素平均值(偶行奇列 + 奇行偶列,共 40 个)
  • avgC1:同行同色(R 或 B)均值(25 个)
  • avgC2:对角同色均值(16 个)

检测条件: 当前像素 center 与 avgG 和 avgC2 的差值均超过阈值 thres,且 avgC1 也同样异常时,判定为色度噪声。

下面是8x8的bayer图像,从这里直观看出上面3个方向的像素个数,但这里能看到像素的个数和上面对应的像素个数不一样,这里留个疑问吧 ? 先继续

噪声校正(CNC)

上面检测到色度异常像素后,然后校正采用自适应融合策略:

  • 阻尼因子(dampFactor) 根据 AWB 增益自适应调整(增益越大,阻尼越强):
  • r_gain ≤ 1.0 → dampFactor = 1.0
  • r_gain ∈ (1.0, 1.2] → dampFactor = 0.5
  • r_gain > 1.2 → dampFactor = 0.3
  • 色度校正值:

chromaCorrected = max(avgG, avgC2) + dampFactor × (center - max(avgG, avgC2))

  • 亮度衰减因子(fade1, fade2) 在亮区减小校正强度(避免破坏高亮细节)

  • 最终输出:

output = (1 - fadeTot) × center + fadeTot × chromaCorrected

2.算法代码

实现细节:

  • 仅对 R 和 B 通道进行校正,G 通道直接保留
  • 边界 padding 为 4 像素(reflect 模式)
  • 逐 2×2 Bayer 块遍历
python 复制代码
class CNF:
    'Chroma Noise Filtering'

    def __init__(self, img, bayer_pattern, thres, gain, clip):
        self.img = img
        self.bayer_pattern = bayer_pattern
        self.thres = thres
        self.gain = gain
        self.clip = clip

    def padding(self):
        img_pad = np.pad(self.img, ((4, 4), (4, 4)), 'reflect')
        return img_pad

    def clipping(self):
        np.clip(self.img, 0, self.clip, out=self.img)
        return self.img

    def cnc(self, is_color, center, avgG, avgC1, avgC2):
        'Chroma Noise Correction'
        r_gain = self.gain[0]
        gr_gain = self.gain[1]
        gb_gain = self.gain[2]
        b_gain = self.gain[3]
        dampFactor = 1.0
        signalGap = center - max(avgG, avgC2)
        if is_color == 'r':
            if r_gain <= 1.0:
                dampFactor = 1.0
            elif r_gain > 1.0 and r_gain <= 1.2:
                dampFactor = 0.5
            elif r_gain > 1.2:
                dampFactor = 0.3
        elif is_color == 'b':
            if b_gain <= 1.0:
                dampFactor = 1.0
            elif b_gain > 1.0 and b_gain <= 1.2:
                dampFactor = 0.5
            elif b_gain > 1.2:
                dampFactor = 0.3
        chromaCorrected = max(avgG, avgC2) + dampFactor * signalGap
        if is_color == 'r':
            signalMeter = 0.299 * avgC1 + 0.587 * avgG + 0.114 * avgC2
        elif is_color == 'b':
            signalMeter = 0.299 * avgC2 + 0.587 * avgG + 0.114 * avgC1
        if signalMeter <= 30:
            fade1 = 1.0
        elif signalMeter > 30 and signalMeter <= 50:
            fade1 = 0.9
        elif signalMeter > 50 and signalMeter <= 70:
            fade1 = 0.8
        elif signalMeter > 70 and signalMeter <= 100:
            fade1 = 0.7
        elif signalMeter > 100 and signalMeter <= 150:
            fade1 = 0.6
        elif signalMeter > 150 and signalMeter <= 200:
            fade1 = 0.3
        elif signalMeter > 200 and signalMeter <= 250:
            fade1 = 0.1
        else:
            fade1 = 0
        if avgC1 <= 30:
            fade2 = 1.0
        elif avgC1 > 30 and avgC1 <= 50:
            fade2 = 0.9
        elif avgC1 > 50 and avgC1 <= 70:
            fade2 = 0.8
        elif avgC1 > 70 and avgC1 <= 100:
            fade2 = 0.6
        elif avgC1 > 100 and avgC1 <= 150:
            fade2 = 0.5
        elif avgC1 > 150 and avgC1 <= 200:
            fade2 = 0.3
        elif avgC1 > 200:
            fade2 = 0
        fadeTot = fade1 * fade2
        center_out = (1 - fadeTot) * center + fadeTot * chromaCorrected
        return center_out

    def cnd(self, y, x, img):
        'Chroma Noise Detection'
        avgG = 0
        avgC1 = 0
        avgC2 = 0
        is_noise = 0
        for i in range(y - 4, y + 4, 1):
            for j in range(x - 4, x + 4, 1):
                if i % 2 == 1 and j % 2 == 0:
                    avgG = avgG + img[i,j]
                elif i % 2 == 0 and j % 2 == 1:
                    avgG = avgG + img[i, j]
                elif i % 2 == 0 and j % 2 == 0:
                    avgC1 = avgC1 + img[i,j]    # weights are equal, could be as gaussian dist
                elif i % 2 == 1 and j % 2 == 1:
                    avgC2 = avgC2 + img[i,j]
        avgG = avgG / 40
        avgC1 = avgC1 / 25
        avgC2 = avgC2 / 16
        center = img[y, x]
        if center > avgG + self.thres and center > avgC2 + self.thres:
            if avgC1 > avgG + self.thres and avgC1 > avgC2 + self.thres:
                is_noise = 1
            else:
                is_noise = 0
        else:
            is_noise = 0
        return is_noise, avgG, avgC1, avgC2

    def cnf(self, is_color, y, x, img):
        is_noise, avgG, avgC1, avgC2 = self.cnd(y, x, img)
        if is_noise:
            pix_out = self.cnc(is_color, img[y,x], avgG, avgC1, avgC2)
        else:
            pix_out = img[y,x]
        return pix_out

    def execute(self):
        img_pad = self.padding()
        raw_h = self.img.shape[0]
        raw_w = self.img.shape[1]
        cnf_img = np.empty((raw_h, raw_w), np.uint16)
        # 以下能看到,X,y坐标去掉四周的margin, 以及x和y的方向都是每次+2的不要搞错了.
        for y in range(0, img_pad.shape[0] - 8 - 1, 2):
            for x in range(0, img_pad.shape[1] - 8 - 1, 2):
                if self.bayer_pattern == 'rggb': #下面示例代码就是这个patter
                    r = img_pad[y + 4, x + 4]
                    gr = img_pad[y + 4, x + 5]
                    gb = img_pad[y + 5, x + 4]
                    b = img_pad[y + 5, x + 5]
                    cnf_img[y, x] = self.cnf('r', y + 4, x + 4, img_pad)
                    cnf_img[y, x + 1] = gr
                    cnf_img[y + 1, x] = gb
                    cnf_img[y + 1, x + 1] = self.cnf('b', y + 5, x + 5, img_pad)
                elif self.bayer_pattern == 'bggr':
                    b = img_pad[y + 4, x + 4]
                    gb = img_pad[y + 4, x + 5]
                    gr = img_pad[y + 5, x + 4]
                    r = img_pad[y + 5, x + 5]
                    cnf_img[y, x] = self.cnf('b', y + 4, x + 4, img_pad)
                    cnf_img[y, x + 1] = gb
                    cnf_img[y + 1, x] = gr
                    cnf_img[y + 1, x + 1] = self.cnf('r', y + 5, x + 5, img_pad)
                elif self.bayer_pattern == 'gbrg':
                    gb = img_pad[y + 4, x + 4]
                    b = img_pad[y + 4, x + 5]
                    r = img_pad[y + 5, x + 4]
                    gr = img_pad[y + 5, x + 5]
                    cnf_img[y, x] = gb
                    cnf_img[y, x + 1] = self.cnf('b', y + 4, x + 5, img_pad)
                    cnf_img[y + 1, x] = self.cnf('r', y + 5, x + 4, img_pad)
                    cnf_img[y + 1, x + 1] = gr
                elif self.bayer_pattern == 'grbg':
                    gr = img_pad[y + 4, x + 4]
                    r = img_pad[y + 4, x + 5]
                    b = img_pad[y + 5, x + 2]
                    gb = img_pad[y + 5, x + 5]
                    cnf_img[y, x, :] = gr
                    cnf_img[y, x + 1, :] = self.cnf('r', y + 4, x + 5, img_pad)
                    cnf_img[y + 1, x, :] = self.cnf('b', y + 5, x + 4, img_pad)
                    cnf_img[y + 1, x + 1, :] = gb
        self.img = cnf_img
        return self.clipping()

3. 算法测试代码

python 复制代码
def test_spike_noise_suppressed(self):
        """
        CNF 检测+修正的完整路径验证。
        设计条件:
          - R 背景 = 80,G/B 背景 = 50
          - 单个 R 尖峰 = 300
          - 在 8×8 扫描窗口内:avgC1 ≈ 60,avgG ≈ 40,avgC2 ≈ 50
          - 满足检测条件(300>50, 60>50)且 fade2 > 0(avgC1=60 ∈ (50,70])
          - 期望 center_out ≈ 174 < 300(修正发生)
        """
        img = np.zeros((24, 24), dtype=np.uint16)
        img[0::2, 0::2] = 80 #80    # R 背景(全图 R 通道中等值)
        img[0::2, 1::2] = 50    # Gr
        img[1::2, 0::2] = 50    # Gb
        img[1::2, 1::2] = 50    # B
        # 在偶行偶列位置注入单个 R 尖峰
        img[8, 8] = 500 #300
        gains = [1.5, 1.0, 1.0, 1.1]
        cnf = CNF(img.copy(), 'rggb', thres=0, gain=gains, clip=1023)
        out = cnf.execute()
        show_bayer_images('rggb', img, out, "left", "right-rggb-cnf")
        self.assertLess(int(out[8, 8]), 300,
                        f"CNF 未能抑制 R 尖峰,输出值 {out[8, 8]}")
  • 测试效果
    能看看到我们把8,8的R通道的像素置为一个很亮的值,这里图片已经经过bayer->RGB了,所以左侧图片看到8,8邻域内像素被拉高. 经过CNF后,8,8像素R通道拉低,变为174.符合预期
相关推荐
armwind3 小时前
openISP学习4-AWB(自动白平衡增益控制)
图像处理·计算机视觉
armwind4 小时前
openISP学习1-openISP介绍
图像处理·计算机视觉
armwind4 小时前
openISP学习3-AAF— Anti-Aliasing Filter(抗混叠滤波)
人工智能·计算机视觉
FL16238631294 小时前
Synapse腹部CT多器官分割数据集png图片+掩码图片+颜色映射表
人工智能·计算机视觉
在水一缸4 小时前
深度解析:基于 3D Gaussian Splatting 技术的编辑器实践与原理
计算机视觉·3d·编辑器·aigc·3d建模·nerf·3d编辑器
装不满的克莱因瓶5 小时前
从梯度下降到 Adam 优化器:掌握神经网络参数优化的核心原理
人工智能·python·深度学习·神经网络·机器学习·计算机视觉·ai
armwind5 小时前
openISP学习6-CFA -Color Filter Array Interpolation(去马赛克)
图像处理·计算机视觉
香蕉鼠片15 小时前
数字化图像的过程
人工智能·深度学习·计算机视觉
埃科光电16 小时前
打通全场景检测痛点UB系列相机赋能多元智造场景
图像处理·数码相机·计算机视觉·制造·相机