由于芯片CAMERALINK输入RawData的Bayer格式转RGB功能收行缓存大小的限制,最大只支持4K模式下进行Bayer格式转RGB功能。所以相关的转换功能需要使用CPU或者GPU进行转换。下面介绍一下相关的格式的原理和转换程序编写方法。
图像格式和滤光片间的关系
对应很多CAMERALINK或普通相机COMS光原件本身只是对一个一个点对光强弱状态进行量化,并不会颜色进行区分。达到区分颜色的方式是使用滤光片产生对颜色的区分。类似下图经过滤光片后CMOS每个点对应的光强度就是对应颜色的光强度。
下图简单说明颜色设置和图像输出格式的关系
工业相机常用的格式说明:
Bayer转RGB算法
bayer一般使用插值算法转换成RGB格式
要将图像从bayer格式转换为 RGB 格式,我们需要插入两种缺失的颜色每个像素的值。几种标准插值方法(最近邻、线性、三次、三次样条、等)
- Bayer format to RGB
将Bayer Pattern的格式转换为RGB,那就需要通过插值的方式将每个像素点中丢失的两个颜色找回来。有几种插值的方式可以使用,但是最常用的方法是线性插值的修正调节版本。
右边的rgb都是从左边的RGB通过公式转换出来的,不同的算法他们的对应关系会有不同。在程序中对对应关系进行解读。
简化版本的转换程序序分析
下面就是simple版本的转换代码,由于程序支持多种排列模式:RGGB , GBRG,GRBG,BGGR。由于模式太多不利于分析程序,我们认为前面的bayer图片按RGGB模式保存为文件,文件结构如下:
以设定为RGGB模式,一个简单的转换分析代码。
转换结果关系分析:
int bayer_Simple(const uint8_t *restrict bayer, uint8_t *restrict rgb, int sx, int sy, int tile)
{
const int bayerStep = sx;
const int rgbStep = 3 * sx;//rgb 一个像素的字节数
int width = sx;
int height = sy;
//判读输入格式排列为BGGR GBRG blue =-1 其他 blue =1
int blue = tile == DC1394_COLOR_FILTER_BGGR //RGGB模式下该值为1
|| tile == DC1394_COLOR_FILTER_GBRG ? -1 : 1;
//判读输入格式排列为GBRG GRBG start_with_green =1 ,其他 start_with_green =0
int start_with_green = tile == DC1394_COLOR_FILTER_GBRG//RGGB模式下该值为0
|| tile == DC1394_COLOR_FILTER_GRBG;
int i, imax, iinc;
if ((tile>DC1394_COLOR_FILTER_MAX)||(tile<DC1394_COLOR_FILTER_MIN))//判读tile 范围
return DC1394_INVALID_COLOR_FILTER;
/* add black border *///最右列和最底的行由于算法原因计算得出的值无法插值,使用黑色替代。
imax = sx * sy * 3;
for (i = sx * (sy - 1) * 3; i < imax; i++) {
rgb[i] = 0;
}
iinc = (sx - 1) * 3;
for (i = (sx - 1) * 3; i < imax; i += iinc) {
rgb[i++] = 0;
rgb[i++] = 0;
rgb[i++] = 0;
}
rgb += 1;//将RGB指针先移到G
width -= 1;//宽有填0所以直接宽度减少1
height -= 1;//高有填0所以直接宽度减少1
//bayer
for (; height--; bayer += bayerStep, rgb += rgbStep) {//循环一次计算一行
const uint8_t *bayerEnd = bayer + width;//bayer一行的最后
// RGGB模式下0、2、4偶数行为红色先出blue=1,start_with_green=0 ,1、3、4 奇数行为blue=-1,start_with_green=1
if (start_with_green) {//RGGB模式偶数行不运行
rgb[-blue] = bayer[1];//RGGB模式保存蓝
rgb[0] = (bayer[0] + bayer[bayerStep + 1] + 1) >> 1;//计算绿色的值
rgb[blue] = bayer[bayerStep];//RGGB模式保存红
bayer++;//bayer指向下个字节,RGGB模式指向的是蓝色
rgb += 3;
}
//在本程序模式下,中间的红色点和蓝色点都会被两次使用
if (blue > 0) {//本行红色在蓝色前出现,RGGB模式下0、2、4偶数行运行
for (; bayer <= bayerEnd - 2; bayer += 2, rgb += 6) {
rgb[-1] = bayer[0];//保存红色
rgb[0] = (bayer[1] + bayer[bayerStep] + 1) >> 1;//计算绿色的平均值
rgb[1] = bayer[bayerStep + 1];//用第二行的蓝色直接赋值两个蓝色一样
rgb[2] = bayer[2];//保存下个点的红色
rgb[3] = (bayer[1] + bayer[bayerStep + 2] + 1) >> 1;//计算绿色的平均值
rgb[4] = bayer[bayerStep + 1];//用第二行的蓝色直接赋值
}
} else {{//本行蓝色在红色前出现,RGGB模式下1、3、4 奇数行运行
for (; bayer <= bayerEnd - 2; bayer += 2, rgb += 6) {
rgb[1] = bayer[0];//保存蓝色
rgb[0] = (bayer[1] + bayer[bayerStep] + 1) >> 1;//计算绿色的平均值
rgb[-1] = bayer[bayerStep + 1];//用第二行的红色直接赋值两个红色一样
rgb[4] = bayer[2];//保存蓝色
rgb[3] = (bayer[1] + bayer[bayerStep + 2] + 1) >> 1;//计算绿色的平均值
rgb[2] = bayer[bayerStep + 1];//用第二行的红色直接赋值两个红色一样
}
}
if (bayer < bayerEnd) {//最后一个像素处理
rgb[-blue] = bayer[0];//RGGB模式下0、2、4偶数行运行为红 ,RGGB模式下1、3、4 奇数行为蓝
rgb[0] = (bayer[1] + bayer[bayerStep] + 1) >> 1;//计算绿色的平均值
rgb[blue] = bayer[bayerStep + 1];//RGGB模式下0、2、4偶数行运行
bayer++;
rgb += 3;
}
bayer -= width;//回到行的第一个字节
rgb -= width * 3;//rgb 回到本行处理的第一个字节
// RGGB模式下0、2、4偶数行为红色先出blue=1,start_with_green=0 ,1、3、4 奇数行为blue=-1,start_with_green=1
blue = -blue;//处理一行蓝色和红色先出的关系发生变化
start_with_green = !start_with_green;//处理一行绿色是不是第一个字节会发生变化,
}
return 1;
}
NEON编程
一个很好的NEON 指令查找说明的网站
大体实现RGB转换,代码有点长但是为了考虑运行效率和移植方便性没有进行函数封装化和使用.h方式简化。很多段内容重复可以优化可读性。其实最neon最核心的部分代码只有类似的一小段,程序运行时下面一小段实际一次能处理16个点。从代码上来看效率是非常高的应该有原有代码的6倍以上的效率,但是实际LINUX下运行效率提升不大可能是我的图像输入区域是无CACHE内存拷贝时间较长,对性能有2倍影响导致。下面是核心的取值转换部分。
/*
* neon.h
*
* Created on: 2021年12月6日
* Author: Administrator
*/
#if 1
bayer_ODD_row = vld2_u8(&bayer[width]);//以两分量形式获取下一行数据
#if RG_GB==1
//R
//B
b_result= vzip_u8(bayer_ODD_row.val[1], bayer_ODD_row.val[1]); //将row1_b和row1_b本身合并为B0B0B1B1B2B2.....
//G
g1_result= vrhadd_u8(bayer_EVEN_row.val[1],bayer_ODD_row.val[0]);//将G色row0的和row1的值相加初2除4舍5入
row_g_chang = vext_u8(bayer_ODD_row.val[0], bayer_ODD_row.val[0], 1);//将G色的row1行去掉第一个绿色,后面值前移
g2_result= vrhadd_u8(bayer_EVEN_row.val[1],row_g_chang);//将G色row0的和row1的值相加初2除4舍5入
#else
//R
row_r_chang = vext_u8(bayer_ODD_row.val[0], bayer_ODD_row.val[0], 1);//将ROW0 去掉R0 从R1开始
r_result= vzip_u8(bayer_ODD_row.val[0], row_r_chang); //将row0_r_chang和r_result交替合并为R0R1R1R2R2....
//B
//G
g1_result= vrhadd_u8(bayer_EVEN_row.val[0],bayer_ODD_row.val[1]);//将G色row0的和row1的值相加初2除4舍5入
row_g_chang = vext_u8(bayer_EVEN_row.val[0], bayer_EVEN_row.val[0], 1);//将G色的row1行去掉第一个绿色,后面值前移
g2_result= vrhadd_u8(bayer_ODD_row.val[1],row_g_chang);//将G色row0的和row1的值相加初2除4舍5入
#endif
//将G色row0的和移位后的row1的值相加除2后4舍5入
g_result= vzip_u8(g1_result, g2_result);//将两次计算的G色交并保存
rgb_result.val[R_OUT_NUMB]=r_result.val[0];
rgb_result.val[G_OUT_NUMB]=g_result.val[0];
rgb_result.val[B_OUT_NUMB]=b_result.val[0];
// printf("line &bayer[0]:0x%x &bayer[width]:0x%x\n",&bayer[0],&bayer[width]);
// printf("rg line 0x%x\n",rgb);
// printf("rg line 0x%x\n",rgb+RGB_BYTE*8);
WRITE_VST
rgb_result.val[R_OUT_NUMB]=r_result.val[1];
rgb_result.val[G_OUT_NUMB]=g_result.val[1];
rgb_result.val[B_OUT_NUMB]=b_result.val[1];
WRITE_VST2
bayer+=width;
rgb+=rgb_width;
//1、2、3奇数行的处理
bayer_EVEN_row = vld2_u8(&bayer[width]);//以两分量形式获取下一行数据
#if RG_GB==1
//R
row_r_chang = vext_u8(bayer_EVEN_row.val[0], bayer_EVEN_row.val[0], 1);//将ROW0 去掉R0 从R1开始
r_result= vzip_u8(bayer_EVEN_row.val[0], row_r_chang); //将row0_r_chang和r_result交替合并为R0R1R1R2R2....
//B
//G
g1_result= vrhadd_u8(bayer_ODD_row.val[0],bayer_EVEN_row.val[1]);//将G色row0的和row1的值相加初2除4舍5入
row_g_chang = vext_u8(bayer_ODD_row.val[0], bayer_ODD_row.val[0], 1);//将G色的row1行去掉第一个绿色,后面值前移
g2_result= vrhadd_u8(bayer_EVEN_row.val[1],row_g_chang);//将G色row0的和row1的值相加初2除4舍5入
#else
//R
//B
b_result= vzip_u8(bayer_EVEN_row.val[1], bayer_EVEN_row.val[1]); //将row1_b和row1_b本身合并为B0B0B1B1B2B2.....
//G
g1_result= vrhadd_u8(bayer_ODD_row.val[1],bayer_EVEN_row.val[0]);//将G色row0的和row1的值相加初2除4舍5入
row_g_chang = vext_u8(bayer_EVEN_row.val[0], bayer_EVEN_row.val[0], 1);//将G色的row1行去掉第一个绿色,后面值前移
g2_result= vrhadd_u8(bayer_ODD_row.val[1],row_g_chang);//将G色row0的和row1的值相加初2除4舍5入
#endif
//将G色row0的和移位后的row1的值相加除2后4舍5入
g_result= vzip_u8(g1_result, g2_result);//将两次计算的G色交并保存
rgb_result.val[R_OUT_NUMB]=r_result.val[0];
rgb_result.val[G_OUT_NUMB]=g_result.val[0];
rgb_result.val[B_OUT_NUMB]=b_result.val[0];
// printf("line &bayer[0]:0x%x &bayer[width]:0x%x\n",&bayer[0],&bayer[width]);
// printf("gb line 0x%x\n",rgb);
// printf("gb line 0x%x\n",rgb+RGB_BYTE*8);
WRITE_VST
rgb_result.val[R_OUT_NUMB]=r_result.val[1];
rgb_result.val[G_OUT_NUMB]=g_result.val[1];
rgb_result.val[B_OUT_NUMB]=b_result.val[1];
WRITE_VST2
#endif
转换函数全部程序,带横向裁剪功能。
本人使用时对图像进行了裁剪,并对最后几个点色彩不正确不太关系,没有对行的最后几个点特殊处理(写0值),程序先竖向处理几个点再横向处理。减少了重复读取数据的次数又能兼顾cache的横向的CACHE 命中。
本人使用时行是8对齐的,且输入又多余一行没有对最后一行特殊处理(写0值)。
#define RGB_BYTE 3
#define USE_BAYER_NUMB 8 //一次计算使用的BAYER 字节数
#define R_OUT_NUMB 0//0开始 R在输出的第几位
#define G_OUT_NUMB 1//0开始 R在输出的第几位
#define B_OUT_NUMB 2//0开始 R在输出的第几位
#define RG_GB 1 //RG_GB模式设置为1 ,GB_RG模式设置为0
#if 0
# define do_prefetch(_addr, _lvl) do { } while (0)
#else
#define do_prefetch(_addr, _lvl) do { \
__builtin_prefetch(((void const *)(_addr)) + 128, 0, (_lvl)); \
} while (0);
#endif
#if RGB_BYTE==3
#define WRITE_VST vst3_u8(rgb, rgb_result);//RGB的值一并写入内存
#define WRITE_VST2 vst3_u8(rgb+RGB_BYTE*8, rgb_result);//RGB的值一并写入内存
#else
#define WRITE_VST vst4_u8(rgb, rgb_result);//RGB的值一并写入内存
#define WRITE_VST2 vst4_u8(rgb+RGB_BYTE*8, rgb_result);//RGB的值一并写入内存
#endif
#define ONE_TIME_HIGH 8//一次处理几行 //有几个include 对应 *2个行
void neon_bayer_Simple(uint8_t * in_bayer, uint8_t * out_rgb, uint16_t width, uint16_t height,uint16_t out_wide){// 6148->6144
uint16_t i,j;
uint32_t rgb_width;
uint16_t row_cycle;//一行循环多少次,一行循环后剩余需要填充的点
uint8_t row_left,higt_left;
uint8_t * bayer;
uint8_t * rgb;
uint8x8x2_t bayer_EVEN_row ;
uint8x8x2_t bayer_ODD_row ;
//R
uint8x8_t row_r_chang ;
uint8x8x2_t r_result;
//B
uint8x8x2_t b_result;
//G
uint8x8_t g1_result;
uint8x8_t row_g_chang;
uint8x8_t g2_result;
//将G色row0的和移位后的row1的值相加除2后4舍5入
uint8x8x2_t g_result;//将两次计算的G舍交并保存
#if RGB_BYTE==3
uint8x8x3_t rgb_result;//if 3通道rgb修改为
#else
uint8x8x4_t rgb_result={0};//if 4通道rgb修改为
#endif
//test
higt_left=height&ONE_TIME_HIGH;
height=height/ONE_TIME_HIGH;//处理耦数行,且最后一行数据不能用
row_left=out_wide%(USE_BAYER_NUMB*2);
row_cycle=out_wide-row_left;
rgb_width=out_wide*RGB_BYTE;
for(i=0;i<height;i++) {
for (j=0;j<row_cycle;j=j+USE_BAYER_NUMB*2) {//循环只处理能整除的部分
rgb=out_rgb+j*RGB_BYTE;
bayer=in_bayer+j;
bayer_EVEN_row = vld2_u8(bayer);//以两分量形式获取本行数据
#if RG_GB==1
//R
row_r_chang = vext_u8(bayer_EVEN_row.val[0], bayer_EVEN_row.val[0], 1);//将ROW0 去掉R0 从R1开始
r_result= vzip_u8(bayer_EVEN_row.val[0], row_r_chang); //将row0_r_chang和r_result交替合并为R0R1R1R2R2....
#else
//B
b_result= vzip_u8(bayer_EVEN_row.val[1], bayer_EVEN_row.val[1]); //将row1_b和row1_b本身合并为B0B0B1B1B2B2.....
#endif
//#include "neon.h"的数量需要和ONE_TIME_HIGH关系对应
#include "neon.h"
bayer+=width;
rgb+=rgb_width;
#include "neon.h"
bayer+=width;
rgb+=rgb_width;
#include "neon.h"
bayer+=width;
rgb+=rgb_width;
#include "neon.h"
}
in_bayer+=width*ONE_TIME_HIGH;
out_rgb+=rgb_width*ONE_TIME_HIGH;
}
//后面的根据尺寸调整,最后2行是不能处理的,所以需要特殊处理根据高度和相关的关系
}
计算结果
bayer模式下RGGB输入16x16图像内容:
转换后的RGB 格式内容: