openISP学习6-CFA -Color Filter Array Interpolation(去马赛克)

文章目录

      • 1.算法原理
      • [2.Malvar 算法](#2.Malvar 算法)
        • [示例:在 R 位置插值 G](#示例:在 R 位置插值 G)
        • [示例:在 R 位置插值 B](#示例:在 R 位置插值 B)
        • [各 Bayer 位置的插值方向](#各 Bayer 位置的插值方向)
      • 3.CFA算法实现
      • [4.CFA 测试代码](#4.CFA 测试代码)
        • ['RGGB' bayer pattern 转RGB](#'RGGB' bayer pattern 转RGB)
        • ['BGGR' bayer pattern 转RGB](#'BGGR' bayer pattern 转RGB)
        • 图像显示函数的更改

1.算法原理

Bayer 阵列每个像素只记录一种颜色,去马赛克(Demosaicing)通过插值恢复每个位置的完整 RGB 值。openISP 的CFA模块实现了 Malvar-He-Cutler (2004) 算法,这是经典的基于梯度修正的线性插值 方法。CFA就是把bayer域的图像转换成RGB域的图像

2.Malvar 算法

Malvar 算法的核心是:在基础双线性插值 的基础上,加入梯度修正项来减少伪彩色。每种 Bayer 位置(R、Gr、Gb、B)对应一组固定的 5×5 卷积系数。

下面以 在R 位置插值 G 和 B 为例(系数已除以 8):

示例:在 R 位置插值 G

当前像素为R,则给当前R插入G分量

复制代码
G = (4×中心 - 上上 - 下下 - 左左 - 右右
     + 2×上 + 2×下 + 2×左 + 2×右) / 8
  • 5x5卷积核为:

    复制代码
       0 0 -1  0  0
       0 0  2  0  0
      -1 2  4  2 -1
       0 0  2  0  0
       0 0 -1  0  0

加权系数如下所示:

示例:在 R 位置插值 B

当前像素是R,给当前像素插入B分量

复制代码
B = (6×中心 - 3/2×(上上+下下+左左+右右)
     + 2×(左上+右上+左下+右下)) / 8

5x5卷积核为:

复制代码
 0    0 -1.5   0   0
 0    2   0    2   0 
-1.5  0   6    0  -1.5
 0    2   0    2   0
 0    0  -1.5  0   0

其实加权系数如下所示:

各 Bayer 位置的插值方向
位置 已知颜色 R 插值方向 G 插值方向 B 插值方向
R R 直接 十字 对角
Gr G 水平 直接 垂直
Gb G 垂直 直接 水平
B B 对角 十字 直接

上面说的都概括在下面这张图中(取至openISP官方文档), 对应8中卷积核.

3.CFA算法实现

实现细节

  • 输入 padding 2 像素(reflect 模式)
  • 图像转 int32 防止中间计算溢出 (这里在写测试代码一直有溢出问题,我的代码中就先改成uint32类型)
  • 逐 2×2 Bayer 块遍历,一次处理 4 个像素
  • 输出三通道 RGB,clip 到 [0, cfa_clip]
python 复制代码
import numpy as np

class CFA:
    'Color Filter Array Interpolation'

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

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

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

	# z
    def malvar(self, is_color, center, y, x, img):
        if is_color == 'r':
            r = center
            g = 4 * img[y,x] - img[y-2,x] - img[y,x-2] - img[y+2,x] - img[y,x+2] \
                + 2 * (img[y+1,x] + img[y,x+1] + img[y-1,x] + img[y,x-1])
            b = 6 * img[y,x] - 3 * (img[y-2,x] + img[y,x-2] + img[y+2,x] + img[y,x+2]) / 2 \
                + 2 * (img[y-1,x-1] + img[y-1,x+1] + img[y+1,x-1] + img[y+1,x+1])
            g = g / 8
            b = b / 8
        elif is_color == 'gr':
            r = 5 * img[y,x] - img[y,x-2] - img[y-1,x-1] - img[y+1,x-1] - img[y-1,x+1] - img[y+1,x+1] - img[y,x+2] \
                + (img[y-2,x] + img[y+2,x]) / 2 + 4 * (img[y,x-1] + img[y,x+1])
            g = center
            b = 5 * img[y,x] - img[y-2,x] - img[y-1,x-1] - img[y-1,x+1] - img[y+2,x] - img[y+1,x-1] - img[y+1,x+1] \
                + (img[y,x-2] + img[y,x+2]) / 2 + 4 * (img[y-1,x] + img[y+1,x])
            r = r / 8
            b = b / 8
        elif is_color == 'gb':
            r = 5 * img[y,x] - img[y-2,x] - img[y-1,x-1] - img[y-1,x+1] - img[y+2,x] - img[y+1,x-1] - img[y+1,x+1] \
                + (img[y,x-2] + img[y,x+2]) / 2 + 4 * (img[y-1,x] + img[y+1,x])
            g = center
            b = 5 * img[y,x] - img[y,x-2] - img[y-1,x-1] - img[y+1,x-1] - img[y-1,x+1] - img[y+1,x+1] - img[y,x+2] \
                + (img[y-2,x] + img[y+2,x]) / 2 + 4 * (img[y,x-1] + img[y,x+1])
            r = r / 8
            b = b / 8
        elif is_color == 'b':
            r = 6 * img[y,x] - 3 * (img[y-2,x] + img[y,x-2] + img[y+2,x] + img[y,x+2]) / 2 \
                + 2 * (img[y-1,x-1] + img[y-1,x+1] + img[y+1,x-1] + img[y+1,x+1])
            g = 4 * img[y,x] - img[y-2,x] - img[y,x-2] - img[y+2,x] - img[y,x+2] \
                + 2 * (img[y+1,x] + img[y,x+1] + img[y-1,x] + img[y,x-1])
            b = center
            r = r / 8
            g = g / 8
        return [r, g, b]

    def execute(self):
        img_pad = self.padding()
        img_pad = img_pad.astype(np.uint32)
        raw_h = self.img.shape[0]
        raw_w = self.img.shape[1]
        cfa_img = np.empty((raw_h, raw_w, 3), np.uint16)
        for y in range(0, img_pad.shape[0]-4-1, 2):
            for x in range(0, img_pad.shape[1]-4-1, 2):
                if self.bayer_pattern == 'rggb':
                    r = img_pad[y+2,x+2]
                    gr = img_pad[y+2,x+3]
                    gb = img_pad[y+3,x+2]
                    b = img_pad[y+3,x+3]
                    if self.mode == 'malvar':
                        cfa_img[y,x,:] = self.malvar('r', r, y+2,x+2, img_pad)
                        cfa_img[y,x+1,:] = self.malvar('gr', gr, y+2,x+3, img_pad)
                        cfa_img[y+1,x,:] = self.malvar('gb', gb, y+3,x+2, img_pad)
                        cfa_img[y+1,x+1,:] = self.malvar('b', b, y+3,x+3, img_pad)
                elif self.bayer_pattern == 'bggr':
                    b = img_pad[y+2,x+2]
                    gb = img_pad[y+2,x+3]
                    gr = img_pad[y+3,x+2]
                    r = img_pad[y+3,x+3]
                    if self.mode == 'malvar':
                        cfa_img[y,x,:] = self.malvar('b', b, y+2,x+2, img_pad)
                        cfa_img[y,x+1,:] = self.malvar('gb', gb, y+2,x+3, img_pad)
                        cfa_img[y+1,x,:] = self.malvar('gr', gr, y+3,x+2, img_pad)
                        cfa_img[y+1,x+1,:] = self.malvar('r', r, y+3,x+3, img_pad)
                elif self.bayer_pattern == 'gbrg':
                    gb = img_pad[y+2,x+2]
                    b = img_pad[y+2,x+3]
                    r = img_pad[y+3,x+2]
                    gr = img_pad[y+3,x+3]
                    if self.mode == 'malvar':
                        cfa_img[y,x,:] = self.malvar('gb', gb, y+2,x+2, img_pad)
                        cfa_img[y,x+1,:] = self.malvar('b', b, y+2,x+3, img_pad)
                        cfa_img[y+1,x,:] = self.malvar('r', r, y+3,x+2, img_pad)
                        cfa_img[y+1,x+1,:] = self.malvar('gr', gr, y+3,x+3, img_pad)
                elif self.bayer_pattern == 'grbg':
                    gr = img_pad[y+2,x+2]
                    r = img_pad[y+2,x+3]
                    b = img_pad[y+3,x+2]
                    gb = img_pad[y+3,x+3]
                    if self.mode == 'malvar':
                        cfa_img[y,x,:] = self.malvar('gr', gr, y+2,x+2, img_pad)
                        cfa_img[y,x+1,:] = self.malvar('r', r, y+2,x+3, img_pad)
                        cfa_img[y+1,x,:] = self.malvar('b', b, y+3,x+2, img_pad)
                        cfa_img[y+1,x+1,:] = self.malvar('gb', gb, y+3,x+3, img_pad)
        self.img = cfa_img
        return self.clipping()

4.CFA 测试代码

'RGGB' bayer pattern 转RGB
python 复制代码
    def test_uniform_bayer_rgb_values(self):
        h, w = 32, 32
        # 第一组参数图像显示太白,不利于展示
        #img = make_bayer_rggb(h, w, r_val=400, g_val=600, b_val=300)
        img = make_bayer_rggb(h, w, r_val=200, g_val=300, b_val=400)
        cfa = CFA(img.copy().astype(np.uint16), 'malvar', 'rggb', clip=1023)
        out = cfa.execute()
        show_bayer_RGB_images('rggb', img, out, "left", "right-rggb-malvar")
        # 取中心区域避开边界插值误差
        center = out[8:24, 8:24, :]
        r_mean = float(center[:, :, 0].mean())
        g_mean = float(center[:, :, 1].mean())
        b_mean = float(center[:, :, 2].mean())
  • 测试效果图
    上面一开始在构建rggb bayer图像使用的是r_val=400, g_val=600, b_val=300,这个会有溢出风险,展示先改用暗光元素r_val=200, g_val=300, b_val=400, G和B分量最多,图像显示青绿色.下图中的左图使用opencv2的库直接根据bayer格式转成的RGB, 右边没有进行格式转换,直接显示的(我们转换后的图像就是RGB图像).
'BGGR' bayer pattern 转RGB
python 复制代码
    def test_bayer_pattern_bggr(self):
        """bggr 模式:B 在偶行偶列,R 在奇行奇列,R 通道值应接近 400。"""
        h, w = 32, 32
        #img = np.zeros((h, w), dtype=np.int32)
        img = np.zeros((h, w), dtype=np.uint16)
        img[0::2, 0::2] = 200 #300   # B
        img[0::2, 1::2] = 300 #600   # Gb
        img[1::2, 0::2] = 300 #600   # Gr
        img[1::2, 1::2] = 100 #400   # R
        cfa = CFA(img.copy(), 'malvar', 'bggr', clip=1023)
        out = cfa.execute()
        show_bayer_RGB_images('bggr', img, out, "left", "right-bggr-malvar")
        center_r = float(out[8:24, 8:24, 0].mean())
        self.assertAlmostEqual(center_r, 400, delta=20, msg=f"bggr R 均值 {center_r:.1f} 偏差过大")
  • 测试效果图
    构造的bayer图像,GR和GB分量最大,图像偏绿,下图中的左图使用opencv2的库直接根据bayer格式转成的RGB, 右边没有进行格式转换,直接显示的(我们转换后的图像就是RGB图像).
图像显示函数的更改

这个函数稍微更新了下,可以一起显示bayer图像和RGB图像. bayer图像需要根据patter格式,转成RGB图像再显示. 而第二个直接就是RGB图像,不用进行格式转换.

python 复制代码
def show_bayer_RGB_images(pattern, img1_data, img2_data, name1="Image 1", name2="Image 2"):
    """
    输入1张Bayer格式图像和1张RGB格式图像,将其转换为RGB后左右显示在桌面上。
    
    参数:
        pattern (str): img1_data 的Bayer排列模式 ("bggr", "rggb", "gbrg", "grbg")
        img1_data (numpy.ndarray): 第一张图像的Bayer原始数据 (H, W)
        img2_data (numpy.ndarray): 第二张图像的去马赛克后RGB数据 (H, W, 3)
        name1 (str): 第一张图片的标题名称
        name2 (str): 第二张图片的标题名称
    """
    # --- 第一步:处理第一张图像 (Bayer 转 RGB) ---
    # OpenCV默认使用BGR色彩空间,根据传入的pattern进行转换
    if pattern == "bggr":
        rgb_img1 = cv2.cvtColor(img1_data, cv2.COLOR_BAYER_BG2BGR)
        #rgb_img2 = cv2.cvtColor(img2_data, cv2.COLOR_BAYER_BG2BGR)
    elif pattern == "rggb":
        rgb_img1 = cv2.cvtColor(img1_data, cv2.COLOR_BAYER_RG2BGR)
        #rgb_img2 = cv2.cvtColor(img2_data, cv2.COLOR_BAYER_RG2BGR)
    elif pattern == "gbrg":
        rgb_img1 = cv2.cvtColor(img1_data, cv2.COLOR_BAYER_GB2BGR)
        #rgb_img2 = cv2.cvtColor(img2_data, cv2.COLOR_BAYER_GB2BGR)
    elif pattern == "grbg":
        rgb_img1 = cv2.cvtColor(img1_data, cv2.COLOR_BAYER_GR2BGR)
        #rgb_img2 = cv2.cvtColor(img2_data, cv2.COLOR_BAYER_GR2BGR)

    # --- 第二步:处理第二张图像 (已经是RGB格式) ---
    # 如果img2_data是由OpenCV生成的BGR图像,请取消下面这行注释进行转换:
    # img2_data = cv2.cvtColor(img2_data, cv2.COLOR_BGR2RGB)
    # 注意这里没有进行格式转换,直接显示
    rgb_img2 = img2_data

    # --- 第三步:创建画布并左右显示 ---
    fig, axs = plt.subplots(1, 2, figsize=(8, 4))
    
    # 在左侧子图显示第一张图片
    axs[0].imshow(rgb_img1)
    axs[0].set_title(name1)
    axs[0].axis('off')  # 隐藏坐标轴
    
    # 在右侧子图显示第二张图片
    axs[1].imshow(rgb_img2)
    axs[1].set_title(name2)
    axs[1].axis('off')  # 隐藏坐标轴
    
    # 自动调整布局,防止标题重叠
    plt.tight_layout()
    # 弹出窗口显示
    plt.show() 
相关推荐
香蕉鼠片11 小时前
数字化图像的过程
人工智能·深度学习·计算机视觉
埃科光电11 小时前
打通全场景检测痛点UB系列相机赋能多元智造场景
图像处理·数码相机·计算机视觉·制造·相机
A hao12 小时前
P2与P2.5 LED显示屏的5大区别
图像处理·人工智能·广告
一个王同学14 小时前
从零到一 | CV转多模态大模型 | week12 | 整理 MiniLLaVA 工程与文档
人工智能·深度学习·算法·机器学习·计算机视觉
硅谷秋水15 小时前
世界模型:架构、方法、推理与应用的综述(下)
人工智能·机器学习·计算机视觉·语言模型·机器人
硅谷秋水15 小时前
世界模型:架构、方法、推理与应用的综述(上)
人工智能·机器学习·计算机视觉·语言模型
AI人工智能+18 小时前
一种基于深度学习的端到端户口本识别技术,通过多阶段神经网络架构实现高精度信息提取
人工智能·深度学习·计算机视觉·自然语言处理·ocr
扫地僧98519 小时前
Tyche :医学图像分割中的随机上下文学习
人工智能·机器学习·计算机视觉
C++ 老炮儿的技术栈20 小时前
如何利用 OpenCV 将图像显示在对话框窗口上
c语言·c++·人工智能·qt·opencv·计算机视觉·github