拜耳阵列(Bayer Pattern)以及常见彩色滤波矩阵(CFA)

一、拜耳阵列的来源

图像传感器将光线转化成电流,光线越亮,电流的数值就越大;光线越暗,电流的数值就越小。图像传感器只能感受光的强弱,无法感受光的波长。由于光的颜色由波长决定,所以图像传播器无法记录颜色,也就是说,它只能拍黑白照片,这肯定是不能接受的。

一种解决方案是照相机内置三个图像传感器,分别记录红、绿、蓝三种颜色,然后再将这三个值合并。这种方法能产生最准确的颜色信息,但是成本太高,无法投入实用。

1974年,柯达公司的工程师Bryce Bayer提出了一个全新方案,在图像传感器前面,设置一层彩色滤光片阵列(Color Filter Array,CFA) ,有间隔的在每个像素上放置单一颜色的滤镜。 这样,每个通道能得到一个部分值空缺的图片,然后通过各种插值手段填充空缺的值,进而得到彩色图像。

二、拜耳阵列的工作原理

拜耳阵列是实现CCD 或CMOS 传感器拍摄彩色图像的主要技术之一。它模拟人眼对色彩的敏感程度,采用1红2绿1蓝的彩色滤光片阵列,对光线进行过滤。

如下图所示,光线经过拜耳滤光片,获取到R、G、B三个通道的图像,R通道和B通道分别只有1/4的像素被填充,G通道只有1/2的像素被填充。通过插值算法,将R、G、B三个通道的原始图填充,就可以得到完备的彩色图像。

三、常见色彩滤波矩阵(CFA)

随着科技发展,越来越多不同设计的CFA被应用于相机和手机中。主要分为RGB、RGB-IR、RCCC、其它等四类

3.1 For RGB sensor

在数字化的时代,需要一种标准来量化

对比一下这个C# 的Demosaic和matlab的demosaic_malvar,我希望C#的处理结果要和matlab完全一样:public static Mat Demosaic(Mat raw, string bayerPattern) { // 确保输入为单通道图像 if (raw.Channels() != 1) throw new ArgumentException("Input must be single-channel image"); int rows = raw.Rows; int cols = raw.Cols; // 创建拜耳掩模 Mat maskR = new Mat(rows, cols, MatType.CV_8UC1, Scalar.Black); Mat maskGr = new Mat(rows, cols, MatType.CV_8UC1, Scalar.Black); Mat maskGb = new Mat(rows, cols, MatType.CV_8UC1, Scalar.Black); Mat maskB = new Mat(rows, cols, MatType.CV_8UC1, Scalar.Black); // 根据拜耳模式设置掩模 switch (bayerPattern.ToLower()) { case "rggb": SetMask(maskR, 0, 0); // R: 偶行偶列 SetMask(maskGr, 0, 1); // Gr: 偶行奇列 SetMask(maskGb, 1, 0); // Gb: 奇行偶列 SetMask(maskB, 1, 1); // B: 奇行奇列 break; case "grbg": SetMask(maskGr, 0, 0); // Gr SetMask(maskR, 0, 1); // R SetMask(maskB, 1, 0); // B SetMask(maskGb, 1, 1); // Gb break; case "gbrg": SetMask(maskGb, 0, 0); // Gb SetMask(maskB, 0, 1); // B SetMask(maskR, 1, 0); // R SetMask(maskGr, 1, 1); // Gr break; case "bggr": SetMask(maskB, 0, 0); // B SetMask(maskGb, 0, 1); // Gb SetMask(maskGr, 1, 0); // Gr SetMask(maskR, 1, 1); // R break; default: throw new ArgumentException("Unsupported Bayer pattern. Use RGGB, GRBG, GBRG, or BGGR."); } // 转换输入图像为双精度浮点型 Mat rawDouble = new Mat(); raw.ConvertTo(rawDouble, MatType.CV_64FC1); // 定义 Malvar 算法的卷积核 (5x5) Mat kernelG = new Mat(5, 5, MatType.CV_64FC1, new double[,] { { 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 } }) / 8.0; Mat kernelRB = new Mat(5, 5, MatType.CV_64FC1, new double[,] { { 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 } }) / 8.0; Mat kernelRGr = new Mat(5, 5, MatType.CV_64FC1, new double[,] { { 0, 0, 0.5, 0, 0 }, { 0, -1, 0, -1, 0 }, { -1, 4, 5, 4, -1 }, { 0, -1, 0, -1, 0 }, { 0, 0, 0.5, 0, 0 } }) / 8.0; Mat kernelRGb = new Mat(5, 5, MatType.CV_64FC1, new double[,] { { 0, 0, -1, 0, 0 }, { 0, -1, 4, -1, 0 }, { 0.5, 0, 5, 0, 0.5 }, { 0, -1, 4, -1, 0 }, { 0, 0, -1, 0, 0 } }) / 8.0; // 初始化输出通道 Mat R = new Mat(rows, cols, MatType.CV_64FC1, Scalar.Black); Mat G = new Mat(rows, cols, MatType.CV_64FC1, Scalar.Black); Mat B = new Mat(rows, cols, MatType.CV_64FC1, Scalar.Black); // === 绿色通道插值 === // 复制已知的绿色像素 (Gr 和 Gb) rawDouble.CopyTo(G, maskGr); rawDouble.CopyTo(G, maskGb); // 在非绿色位置插值 Mat GInterp = new Mat(); Cv2.Filter2D(rawDouble, GInterp, MatType.CV_64FC1, kernelG, anchor: new Point(-1, -1), delta: 0, borderType: BorderTypes.Replicate); Mat maskGreen = maskGr | maskGb; Mat maskNonGreen = new Mat(); Cv2.BitwiseNot(maskGreen, maskNonGreen); GInterp.CopyTo(G, maskNonGreen); // === 红色通道插值 === rawDouble.CopyTo(R, maskR); // 已知红色像素 // 在 Gr 位置插值红 Mat RGr = new Mat(); Cv2.Filter2D(rawDouble, RGr, MatType.CV_64FC1, kernelRGr, anchor: new Point(-1, -1), borderType: BorderTypes.Replicate); RGr.CopyTo(R, maskGr); // 在 Gb 位置插值红 Mat RGb = new Mat(); Cv2.Filter2D(rawDouble, RGb, MatType.CV_64FC1, kernelRGb, anchor: new Point(-1, -1), borderType: BorderTypes.Replicate); RGb.CopyTo(R, maskGb); // 在 B 位置插值红 Mat RB = new Mat(); Cv2.Filter2D(rawDouble, RB, MatType.CV_64FC1, kernelRB, anchor: new Point(-1, -1), borderType: BorderTypes.Replicate); RB.CopyTo(R, maskB); // === 蓝色通道插值 === rawDouble.CopyTo(B, maskB); // 已知蓝色像素 // 在 Gr 位置插值蓝 (使用 RGb 核) Mat BGr = new Mat(); Cv2.Filter2D(rawDouble, BGr, MatType.CV_64FC1, kernelRGb, anchor: new Point(-1, -1), borderType: BorderTypes.Replicate); BGr.CopyTo(B, maskGr); // 在 Gb 位置插值蓝 (使用 RGr 核) Mat BGb = new Mat(); Cv2.Filter2D(rawDouble, BGb, MatType.CV_64FC1, kernelRGr, anchor: new Point(-1, -1), borderType: BorderTypes.Replicate); BGb.CopyTo(B, maskGb); // 在 R 位置插值蓝 (使用 RB 核) Mat BR = new Mat(); Cv2.Filter2D(rawDouble, BR, MatType.CV_64FC1, kernelRB, anchor: new Point(-1, -1), borderType: BorderTypes.Replicate); BR.CopyTo(B, maskR); // 四舍五入到整数 Mat RInt = RoundMat(R); Mat GInt = RoundMat(G); Mat BInt = RoundMat(B); // 合并通道并返回 Mat[] channels = { RInt, GInt, BInt }; Mat result = new Mat(); Cv2.Merge(channels, result); return result; } // 辅助函数:设置拜耳掩模 private static void SetMask(Mat mask, int rowStart, int colStart) { for (int r = rowStart; r < mask.Rows; r += 2) { for (int c = colStart; c < mask.Cols; c += 2) { mask.At<byte>(r, c) = 255; } } } // 辅助函数:四舍五入矩阵值 private static Mat RoundMat(Mat src) { Mat dst = new Mat(); src.ConvertTo(dst, MatType.CV_32S); // 四舍五入到整数 dst.ConvertTo(dst, MatType.CV_64FC1); // 转回双精度 return dst; } function rgb = demosaic_malvar(raw_matrix, bayerPattern) % 确保输入是双精度 raw_matrix = double(raw_matrix); [rows, cols] = size(raw_matrix); % 创建精确的逻辑型拜耳掩模 (RGGB) mask_R = false(rows, cols); mask_Gr = false(rows, cols); mask_Gb = false(rows, cols); mask_B = false(rows, cols); % 根据bayerPattern设置掩模 switch lower(bayerPattern) case 'rggb' % RGGB模式 mask_R(1:2:end, 1:2:end) = true; mask_Gr(1:2:end, 2:2:end) = true; mask_Gb(2:2:end, 1:2:end) = true; mask_B(2:2:end, 2:2:end) = true; case 'grbg' % GRBG模式 mask_Gr(1:2:end, 1:2:end) = true; mask_R(1:2:end, 2:2:end) = true; mask_B(2:2:end, 1:2:end) = true; mask_Gb(2:2:end, 2:2:end) = true; case 'gbrg' % GBRG模式 mask_Gb(1:2:end, 1:2:end) = true; mask_B(1:2:end, 2:2:end) = true; mask_R(2:2:end, 1:2:end) = true; mask_Gr(2:2:end, 2:2:end) = true; case 'bggr' % BGGR模式 mask_B(1:2:end, 1:2:end) = true; mask_Gb(1:2:end, 2:2:end) = true; mask_Gr(2:2:end, 1:2:end) = true; mask_R(2:2:end, 2:2:end) = true; otherwise error('Unsupported Bayer pattern. Use RGGB, GRBG, GBRG, or BGGR.'); end % 精确的Malvar卷积核 kernel_G = [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] / 8; kernel_R_B = [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] / 8; kernel_R_Gr = [0, 0, 0.5, 0, 0; 0, -1, 0, -1, 0; -1, 4, 5, 4, -1; 0, -1, 0, -1, 0; 0, 0, 0.5, 0, 0] / 8; kernel_R_Gb = [0, 0, -1, 0, 0; 0, -1, 4, -1, 0; 0.5, 0, 5, 0, 0.5; 0, -1, 4, -1, 0; 0, 0, -1, 0, 0] / 8; % 蓝在Gr位置 (与红在Gb相同) kernel_B_Gr = kernel_R_Gb; % 蓝在Gb位置 (与红在Gr相同) kernel_B_Gb = kernel_R_Gr; % 蓝在R位置 (专用核,与红在B位置相同) kernel_B_R = kernel_R_B; % 初始化通道 R = zeros(rows, cols); G = zeros(rows, cols); B = zeros(rows, cols); % === 绿色通道插值 === G(mask_Gr | mask_Gb) = raw_matrix(mask_Gr | mask_Gb); G_interp = imfilter(raw_matrix, kernel_G, 'replicate', 'conv'); G(mask_R | mask_B) = G_interp(mask_R | mask_B); % === 红色通道插值 === R(mask_R) = raw_matrix(mask_R); R_Gr = imfilter(raw_matrix, kernel_R_Gr, 'replicate', 'conv'); R(mask_Gr) = R_Gr(mask_Gr); R_Gb = imfilter(raw_matrix, kernel_R_Gb, 'replicate', 'conv'); R(mask_Gb) = R_Gb(mask_Gb); R_B = imfilter(raw_matrix, kernel_R_B, 'replicate', 'conv'); R(mask_B) = R_B(mask_B); % === 蓝色通道插值 === B(mask_B) = raw_matrix(mask_B); B_Gr = imfilter(raw_matrix, kernel_B_Gr, 'replicate', 'conv'); B(mask_Gr) = B_Gr(mask_Gr); B_Gb = imfilter(raw_matrix, kernel_B_Gb, 'replicate', 'conv'); B(mask_Gb) = B_Gb(mask_Gb); B_R = imfilter(raw_matrix, kernel_B_R, 'replicate', 'conv'); B(mask_R) = B_R(mask_R); % 四舍五入并组合RGB图像 R = round(R); G = round(G); B = round(B); rgb = cat(3, R, G, B); end
最新发布
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值