技术标签: Raw读取和显示 C++ Image Singal Processing Opencv ISP
前阵子接到任务要学习并软件实现一个ISP,刚拿到任务时既兴奋又担心。兴奋又有很多知识要了解了,担心ISP太过复杂,自己不能胜任这个任务。不管怎么样,还是坚持尝试了近一个月,捣腾出一个小ISP。
连载:
Image Signal Processing(ISP)-第一章-ISP基础以及Raw的读取显示
Image Signal Processing(ISP)-第二章-Demosaic去马赛克以及BMP软件实现
Image Signal Processing(ISP)-第三章-BCL, WB, Gamma的原理和软件实现
Image Signal Processing(ISP)-第四章-LSC, CC的原理和软件实现
Github:
BoyPao/ImageSignalProcessing-ISP
从这篇文章开始,我将介绍整个ISP最基础的要求,并介绍如何软件实现这些最基础的要求。因为内容实在太多,此篇 Image Signal Processing(ISP)-第一章 将介绍关于ISP的基础概念,以及ISP第一步,Raw图的读取。如果这些知识能够帮到你,希望你要持续关注泡的连载哦,虽然下一篇遥遥无期,哈哈哈哈哈。
ISP在相机数据流中的位置
ISP是什么? Image Signal Processing(ISP) 的目的是对光学传感器输出的Raw图数据进行信号处理,使之成为符合人眼真实生理感受的信号,并加以输出。
为什么需要ISP呢? 这是个复杂的问题,其根源就是Raw图数据排布的形式。以下给出jpg和Raw的对比效果,这能非常直观地告诉你,Raw图为什么叫做Raw图(未经处理的图)。
jpg和Raw对比,左jpg,右Raw
我们知道所有的光都被能分解为几种基础色光。反之,用一定比例的各种饱和度、色调的基础色光就可以合成几乎所有的光。光学Seneor正是利用了这一原理,设置了红绿蓝pixel(像素单元)来接收光信号。而目前,红绿蓝pixel的排布采用了Bayer(贝尔)排布。这种排布造就了上图显示的传感器Raw图数据直接显示的效果。问题所在:1.图上全是绿色,2有网格现象(我们称之为棋盘格现象)
怎么样实现ISP?那么如何处理Raw图的数据,使之能够显示符合人眼真实感受的图片,这就成为了ISP的重要使命。为实现这一使命,ISP分为三个重要的部分,Bayer域信号处理,RGB域信号处理,YUV域信号处理。
ISP三个域的处理流程
Bayer域的信号处理,主要目的是修正传感器物理特性造成的数据偏移,并且将信号进行插值,恢复完整RGB信号。
RGB域的信号处理,主要目的是对色彩进行补偿,恢复人眼真实感受
YUV域的信号处理,主要目的是分离亮度信号和色度信号,分别对两种信号处理,并且进行JPG的压缩编码。
这里,我设计了一个简单的ISP,使之能简单地完成最基础的ISP要求,并用软件实现了此ISP。(实际上,商用ISP比我设计的简单ISP要复杂很多,处理上也涉及很多更好的算法。本文只讨论最为基础的东西,以做简单的分享。了解ISP后对其各模块有兴趣的朋友可以针对地查阅相应模块的论文或资料)
这个简单的ISP flow chart显示在下方,它包含了Bayer域,RGB域,YUV域的具体操作。
我们来看看这些具体的操作有些什么?
Black Level Correction:指黑电平矫正。我们知道8bit的数字信号量化范围是0-255,0代表最黑,255代表最亮。但是sensor由于要上电感光,所以最黑的情况,也是有电压的,所以这时并不是0电平。这一步就是矫正sensor的非零电平。
Shading Correction:指光偏移矫正。由于镜头对不同色光的折射率是不同的,RGB三色光不能完全一致地在Sensor上成像,因此需要这一步补偿矫正。
G Channels Correction:指两个绿色通道的矫正。在Bayer域绿色通道有两个,但是由于Sensor的构造,感光时这两个通道会存在差异。这一步矫正此差异
White Balance:指白平衡。由于光源不同的色温会带来不同的成像,而往往人们需要去除色温带来的差异,因此需要白平衡这一步操作。
Demosaic:指去马赛克操作。实际上这一步实现了Bayer排布方式转为RGB排布方式
Color Correction:色彩矫正。这一步利用标准色卡进行信号的矫正,使信号恢复人眼真实感受
Gamma:伽马矫正,对信号进行非线性矫正,提高图像对比度,并使其符合CRT显示器的非线性显示。虽然现在不使用CRT显示器了,但gamma矫正仍是ISP必须的一部分,因为Sensor端依然和人眼有着差异。
Noise Reduction:降噪操作,由于Gamma,Shading Correction等一些列操作会增强信号,一些噪声也随之被增强,要去除增强的影响,同时还原更干净的成像,需要进行图像降噪。
Edge Enhancement:边缘增强,因为降噪处理会平滑图片,为了让图片更为清晰,在降噪后通常需要增强图片。
这个简单的ISP采用C++和Opencv实现。使用的测试图片为1920x1080大小的一张Raw图,此Raw图的bayer排布为B,Gb,Gr,R,编码为Mipi编码。(这对后面的软件实现尤为重要)。在此后的章节,我将介绍每一个模块如何通过软件实现,并附上一些经验教训。
在了解了ISP的一些基础概念后,要实现ISP,还需要获取Raw的数据,然后才能进行数据的处理。那么如何获取Raw图数据,那就需要好好了解一下Raw的规则了。Raw的规则由Sensor的像素排布决定,而目前这种排布采用的是Bayer排布。
Bayer排布是什么?Raw图所存的是Sensor直接生成的数据。目前的Sensor采用Bayer排布,我们称这样排的排布为Bayer域。
在Bayer域中,以四个邻域像素为单元组成整幅图片,每个单元都有两个绿色感光像素Gr 和Gb,一个红色感光像素R,一个蓝色感光像素B。我们称之为B通道,Gr通道,Gb通道,R通道。
如下图所示,整幅图由4x3个单元组成,每个单元有4个像素点,他的顺序是B,Gb,Gr,R。所以整幅图的大小是8x6=48个像素。
单元内的四个像素排布顺序是不固定的,可能是B,Gb,Gr,R,也可能是R,Gr,Gb,B。这是由Sensor厂商决定的。
为什么要采用Bayer排布?前面我们介绍了,我们可以将任意光分解为几个基础色光,然后用一定比例的基础色光合成任意的光。这里Bayer排布提供了我们一种分解光的规则。此规则把任意的光分解为了蓝绿红三个基础色光。
但为什么一个单元有两个绿色像素呢?原因是因为人眼对于绿色和黄色更加敏感,采用两个通道的绿色可以更好地还原真实图像。这也是为什么Raw看上去是一片绿色的原因。一些厂商甚至采用两个通道的黄色生产了BYbYrR的贝尔传感器,这里就不介绍了。
在了解了什么是Bayer域后,我们就可以谈谈储存Bayer域数据的Raw文件了。
Raw是什么?Raw文件就是只有Bayer域数据的文件,未经任何的压缩。所以Raw文件有三个特点:1. 数据是Bayer数据。2. 没有任何关于数据格式的信息。3. 特别大,都在十几Mb以上。
我们可以用notepad的二进制插件打开一副Raw图,可以看到他的数据内容。
10bit Mipi编码Raw的文件规则是,用5个字节存储4个像素数据,这样5x8=4x10,没有任何的编码冗余,完美利用每一位编码来存储数据。数据字节以5个为一组,每一组第五个字节头两位补充到第一字节后面组成10bit,次两位补充到第二字节后面组成第二个pixel数据,以此类推。
Mipi编码规则
到此,我们终于清楚地认识到了Raw图以及Raw图的规则,那么让我们上手获取他的数据吧。
接下来,直接放代码吧
#define WIDTH 1920
#define HEIGHT 1080
#define IMGPATH "C:\\Users\\penghao6\\Desktop\\1MCC_IMG_20181229_001526_1.RAW"
int main() {
//设置显示图片的大小
int Imgsizex, Imgsizey;
int Winsizex, Winsizey;
Winsizex = GetSystemMetrics(SM_CXSCREEN);
Winsizey = GetSystemMetrics(SM_CYSCREEN);
Imgsizey = Winsizey * 2 / 3;
Imgsizex = Imgsizey * WIDTH / HEIGHT;
int i,j;
ifstream OpenFile(IMGPATH);
if (OpenFile.fail()){
cout << "Open RAW failed!" << endl;
}
else {
size_t nsize = WIDTH * HEIGHT;
unsigned char *data = new unsigned char[nsize * 5 / 4];
int *decodedata = new int[nsize];
int *Bdata = new int[nsize];
int *Gdata = new int[nsize];
int *Rdata = new int[nsize];
unsigned char *r = new unsigned char[nsize];
unsigned char *g = new unsigned char[nsize];
unsigned char *b = new unsigned char[nsize];
Mat dst(HEIGHT, WIDTH, CV_8UC3, Scalar(0, 0, 0));
//将读不到的pixel置为黑色
for (i = 0; i < nsize; i++) {
Bdata[i] = 0;
Gdata[i] = 0;
Rdata[i] = 0;
}
OpenFile.read((char *)data, WIDTH * HEIGHT * 5 / 4);
data = reinterpret_cast<unsigned char *>(data);
Mipidecode(data, decodedata);//Mipi decode
ReadChannels(decodedata, Bdata, Gdata, Rdata);//pick up channels
//压缩10bit到8bit用于显示
Compress10to8(Bdata, b);
Compress10to8(Gdata, g);
Compress10to8(Rdata, r);
//dst保存四个通道,其中两个绿色通道通一保存在Gdata和g中
for (i = 0; i < HEIGHT; i++) {
for (j = 0; j < WIDTH; j++) {
dst.data[i*WIDTH * 3 + 3 * j] = (unsigned int)b[i*WIDTH + j];
dst.data[i*WIDTH * 3 + 3 * j + 1] = (unsigned int)g[i*WIDTH + j];
dst.data[i*WIDTH * 3 + 3 * j + 2] = (unsigned int)r[i*WIDTH + j];
}
}
namedWindow(RESNAME, 0);
resizeWindow(RESNAME, Imgsizex, Imgsizey);
imshow(RESNAME, result);
waitKey(0);
OpenFile.close();
}
cin >> i;//保持程序不退
return 0;
}
这里用到的Mipi解码函数是
void Mipidecode(unsigned char* src, int * dst) {
int i;
for (i = 0; i < WIDTH * HEIGHT * 5 / 4; i += 5) {
dst[i*4/5] = ((int)src[i] << 2) + (src[i+4]&0x3);
dst[i * 4 / 5+1] = ((int)src[i+1] << 2) + ((src[i + 4]>>2) & 0x3);
dst[i * 4 / 5+2] = ((int)src[i+2] << 2) + ((src[i + 4] >> 4) & 0x3);
dst[i * 4 / 5+3] = ((int)src[i+3] << 2) + ((src[i + 4] >> 6) & 0x3);
}
cout << " Mipi decode finished " << endl;
}
用到的读BGR通道函数是
void ReadChannels(int* data, int* B, int* G, int* R){
int i, j;
for (i = 0; i < HEIGHT; i ++) {
for (j = 0; j < WIDTH; j ++) {
if(i%2==0 &&j%2==0)
B[i*WIDTH + j] = data[i*WIDTH + j];
if ((i % 2 == 0 && j % 2 == 1) || (i % 2 == 1 && j %2== 0))
G[i*WIDTH + j] = data[i*WIDTH + j];
if (i % 2 == 1 && j % 2 == 1)
R[i*WIDTH + j] = data[i*WIDTH + j];
}
}
cout << " Read RGB channels finished " << endl;
}
用到的10bit压缩为8bit函数是
void Compress10to8(int * src, unsigned char * dst) {
int i, j;
for (i = 0; i < HEIGHT; i++) {
for (j = 0; j < WIDTH; j++) {
//采取直接舍弃最低两位,暴力压缩方式,舍弃精度,这应该放在ISP后期
if ((src[i*WIDTH + j] >> 2) > 255)
dst[i*WIDTH + j] = 255;
else if ((src[i*WIDTH + j] >> 2) < 0)
dst[i*WIDTH + j] = 0;
else
dst[i*WIDTH + j] = (src[i*WIDTH + j] >> 2) & 255;
}
}
}
通过以上代码,我们实现了将Raw图数据读取出来,并加以显示。
读出的Raw图
我们将Raw图放大,可以看到Bayer排布的现象
Bayer排布的棋盘格显现
至此我们成功地读取的Raw图了数据,并把这个数据加以显示。从中我们看到三个严重的现象:1. 棋盘格现象。2. 颜色全是绿的。3.图片太暗了。如何从这样的图片获得符合真实人眼感受的图片呢?泡将在下一章继续分享ISP是如何解决这两个现象的。ISP任重而道远!
敬请期待!
文章浏览阅读3.8k次。1、将下载好的萤石js插件,添加到SoringBoot项目中。位置可参考下图所示。(容易出错的地方,在将js插件在html页面引入时,发生路径错误的问题)所以如果对页面中引入js的路径不清楚,可参考下图所示存放路径。2、将ezuikit.js引入到demo-live.html中。(可直接将如下代码复制到你创建的html页面中)<!DOCTYPE html><html lan..._ezuikit 测试的url
文章浏览阅读322次。第二步,在弹出的对话框选择,设备驱动—>PLC—>莫迪康—>ModbusRTU—>COM,根据配置软件选择的协议选期期,这里以此为例,然后点击“下一步”。第四步,把使用虚拟串口打勾(GPRS设备),根据需要选择要生成虚拟口,这里以选择KVCOM1为例,然后点击“下一步”设备ID即Modbus地址(1-255) 使用DTU时,为下485接口上的设备地址。第六步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“第五步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“_组态王ua
文章浏览阅读9.4k次,点赞22次,收藏19次。安装npm相当于安装node.js,Node.js已自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西_npm安装配置
文章浏览阅读748次,点赞21次,收藏26次。大家好,小编来为大家解答以下问题,python基础训练100题,python入门100例题,现在让我们一起来看看吧!宝子们还在新手村练级的时候,不单要吸入基础知识,夯实自己的理论基础,还要去实际操作练练手啊!由于文章篇幅限制,不可能将100道题全部呈现在此除了这些,下面还有我整理好的基础入门学习资料,视频和讲解文案都很齐全,用来入门绝对靠谱,需要的自提。保证100%免费这不,贴心的我爆肝给大家整理了这份今天给大家分享100道Python练习题。大家一定要给我三连啊~
文章浏览阅读1k次。 为了在 Linux ( Ubuntu) 上安装sublime,一般大家都会选择常见的教程或是 sublime 官网教程,然而在国内这种方法可能失效。为此,需要用安装包安装。以下就是使用官网安装包安装的教程。打开 sublime 官网后,点击右上角 download, 或是直接访问点击打开链接,即可看到各个平台上的安装包。选择 Linux 64 位版并下载。下载后,打开终端,进入安装..._ubuntu 安装sumlime text打不开
文章浏览阅读563次,点赞13次,收藏6次。CrossOver24是一款类虚拟机软件,专为macOS和Linux用户设计。它的核心技术是Wine,这是一种在Linux和macOS等非Windows操作系统上运行Windows应用程序的开源软件。通过CrossOver24,用户可以在不购买Windows授权或使用传统虚拟机的情况下,直接在Mac或Linux系统上运行Windows软件和游戏。该软件还提供了丰富的功能,如自动配置、无缝集成和实时传输等,以实现高效的跨平台操作体验。
文章浏览阅读1.7k次。一个用聊天的方式让ChatGPT帮我写的线程安全的环形List_为什么gpt一写list就卡
文章浏览阅读336次。我们在前面的文章里曾写过Web应用中乱码产生的原因和处理方式,旧文回顾:深度揭秘乱码问题背后的原因及解决方式其中我们提到可以通过Filter的方式来设置请求和响应的encoding,来解..._filterconfig selectencoding
文章浏览阅读651次。转自:http://www.jb51.net/article/36480.htmencodeURI和decodeURI是成对来使用的,因为浏览器的地址栏有中文字符的话,可以会出现不可预期的错误,所以可以encodeURI把非英文字符转化为英文编码,decodeURI可以用来把字符还原回来_js encodeur decodeurl
文章浏览阅读1.9w次,点赞6次,收藏3次。前言在日常的Android开发当中,我们肯定要打包apk。但是今天我打包的时候遇到一个很奇怪的问题Android The destination folder does not exist or is not writeable,大意是目标文件夹不存在或不可写。出现问题的原因以及解决办法上面有说报错的中文大意是:目标文件夹不存在或不可写。其实问题就在我们的打包界面当中图中标红的Desti..._the destination folder does not exist or is not writeable
文章浏览阅读94次。一、配置代码编辑区的样式 <1>打开Eclipse,Help —> Install NewSoftware,界面如下: <2>点击add...,按下图所示操作: name:随意填写,Location:http://eclipse-color-th..._ecplise高大上设置
文章浏览阅读2.8k次。一,下载mysql:http://dev.mysql.com/downloads/mysql/; 打开页面之后,在Select Platform:下选择linux Generic,如果没有出现Linux的选项,请换一个浏览器试试。我用的谷歌版本不可以,换一个别的浏览器就行了,如果还是不行,需要换一个翻墙的浏览器。 二,下载完后解压缩并放到安装文件夹下: 1、MySQL-client-5.6.2_linux mysql 安装 mysql-5.6.24-1.linux_glibc2.5.x86_64.rpm-bundle