Image Signal Processing(ISP)-第一章-ISP基础以及Raw的读取显示_isp raw图读取-程序员宅基地

技术标签: 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图的读取。如果这些知识能够帮到你,希望你要持续关注泡的连载哦,虽然下一篇遥遥无期,哈哈哈哈哈。

1. ISP基础概念

在这里插入图片描述

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域的具体操作。

我的ISP流程图

我们来看看这些具体的操作有些什么?

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编码。(这对后面的软件实现尤为重要)。在此后的章节,我将介绍每一个模块如何通过软件实现,并附上一些经验教训。

2. Raw的读取与显示

在了解了ISP的一些基础概念后,要实现ISP,还需要获取Raw的数据,然后才能进行数据的处理。那么如何获取Raw图数据,那就需要好好了解一下Raw的规则了。Raw的规则由Sensor的像素排布决定,而目前这种排布采用的是Bayer排布。

2.1 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文件了。

2.2 Raw文件

Raw是什么?Raw文件就是只有Bayer域数据的文件,未经任何的压缩。所以Raw文件有三个特点:1. 数据是Bayer数据。2. 没有任何关于数据格式的信息。3. 特别大,都在十几Mb以上。
我们可以用notepad的二进制插件打开一副Raw图,可以看到他的数据内容。
在这里插入图片描述

2.3 10bit Mipi编码Raw文件规则

10bit Mipi编码Raw的文件规则是,用5个字节存储4个像素数据,这样5x8=4x10,没有任何的编码冗余,完美利用每一位编码来存储数据。数据字节以5个为一组,每一组第五个字节头两位补充到第一字节后面组成10bit,次两位补充到第二字节后面组成第二个pixel数据,以此类推。

在这里插入图片描述

Mipi编码规则

到此,我们终于清楚地认识到了Raw图以及Raw图的规则,那么让我们上手获取他的数据吧。

2.4 读取和显示的软件实现

接下来,直接放代码吧

#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任重而道远!

敬请期待

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42910064/article/details/102454839

智能推荐

海康威视网络摄像头开发流程(五)------- 直播页面测试_ezuikit 测试的url-程序员宅基地

文章浏览阅读3.8k次。1、将下载好的萤石js插件,添加到SoringBoot项目中。位置可参考下图所示。(容易出错的地方,在将js插件在html页面引入时,发生路径错误的问题)所以如果对页面中引入js的路径不清楚,可参考下图所示存放路径。2、将ezuikit.js引入到demo-live.html中。(可直接将如下代码复制到你创建的html页面中)<!DOCTYPE html><html lan..._ezuikit 测试的url

如何确定组态王与多动能RTU的通信方式_组态王ua-程序员宅基地

文章浏览阅读322次。第二步,在弹出的对话框选择,设备驱动—>PLC—>莫迪康—>ModbusRTU—>COM,根据配置软件选择的协议选期期,这里以此为例,然后点击“下一步”。第四步,把使用虚拟串口打勾(GPRS设备),根据需要选择要生成虚拟口,这里以选择KVCOM1为例,然后点击“下一步”设备ID即Modbus地址(1-255) 使用DTU时,为下485接口上的设备地址。第六步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“第五步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“_组态王ua

npm超详细安装(包括配置环境变量)!!!npm安装教程(node.js安装教程)_npm安装配置-程序员宅基地

文章浏览阅读9.4k次,点赞22次,收藏19次。安装npm相当于安装node.js,Node.js已自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西_npm安装配置

火车头采集器AI伪原创【php源码】-程序员宅基地

文章浏览阅读748次,点赞21次,收藏26次。大家好,小编来为大家解答以下问题,python基础训练100题,python入门100例题,现在让我们一起来看看吧!宝子们还在新手村练级的时候,不单要吸入基础知识,夯实自己的理论基础,还要去实际操作练练手啊!由于文章篇幅限制,不可能将100道题全部呈现在此除了这些,下面还有我整理好的基础入门学习资料,视频和讲解文案都很齐全,用来入门绝对靠谱,需要的自提。保证100%免费这不,贴心的我爆肝给大家整理了这份今天给大家分享100道Python练习题。大家一定要给我三连啊~

Linux Ubuntu 安装 Sublime Text (无法使用 wget 命令,使用安装包下载)_ubuntu 安装sumlime text打不开-程序员宅基地

文章浏览阅读1k次。 为了在 Linux ( Ubuntu) 上安装sublime,一般大家都会选择常见的教程或是 sublime 官网教程,然而在国内这种方法可能失效。为此,需要用安装包安装。以下就是使用官网安装包安装的教程。打开 sublime 官网后,点击右上角 download, 或是直接访问点击打开链接,即可看到各个平台上的安装包。选择 Linux 64 位版并下载。下载后,打开终端,进入安装..._ubuntu 安装sumlime text打不开

CrossOver for Mac 2024无需安装 Windows 即可以在 Mac 上运行游戏 Mac运行exe程序和游戏 CrossOver虚拟机 crossover运行免安装游戏包-程序员宅基地

文章浏览阅读563次,点赞13次,收藏6次。CrossOver24是一款类虚拟机软件,专为macOS和Linux用户设计。它的核心技术是Wine,这是一种在Linux和macOS等非Windows操作系统上运行Windows应用程序的开源软件。通过CrossOver24,用户可以在不购买Windows授权或使用传统虚拟机的情况下,直接在Mac或Linux系统上运行Windows软件和游戏。该软件还提供了丰富的功能,如自动配置、无缝集成和实时传输等,以实现高效的跨平台操作体验。

随便推点

一个用聊天的方式让ChatGPT写的线程安全的环形List_为什么gpt一写list就卡-程序员宅基地

文章浏览阅读1.7k次。一个用聊天的方式让ChatGPT帮我写的线程安全的环形List_为什么gpt一写list就卡

Tomcat自带的设置编码Filter-程序员宅基地

文章浏览阅读336次。我们在前面的文章里曾写过Web应用中乱码产生的原因和处理方式,旧文回顾:深度揭秘乱码问题背后的原因及解决方式其中我们提到可以通过Filter的方式来设置请求和响应的encoding,来解..._filterconfig selectencoding

javascript中encodeURI和decodeURI方法使用介绍_js encodeur decodeurl-程序员宅基地

文章浏览阅读651次。转自:http://www.jb51.net/article/36480.htmencodeURI和decodeURI是成对来使用的,因为浏览器的地址栏有中文字符的话,可以会出现不可预期的错误,所以可以encodeURI把非英文字符转化为英文编码,decodeURI可以用来把字符还原回来_js encodeur decodeurl

Android开发——打包apk遇到The destination folder does not exist or is not writeable-程序员宅基地

文章浏览阅读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

Eclipse配置高大上环境-程序员宅基地

文章浏览阅读94次。一、配置代码编辑区的样式 <1>打开Eclipse,Help —> Install NewSoftware,界面如下: <2>点击add...,按下图所示操作: name:随意填写,Location:http://eclipse-color-th..._ecplise高大上设置

Linux安装MySQL-5.6.24-1.linux_glibc2.5.x86_64.rpm-bundle.tar_linux mysql 安装 mysql-5.6.24-1.linux_glibc2.5.x86_6-程序员宅基地

文章浏览阅读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