Hello!ISP的基础知识分享第二章终于来了!最近精力都投入到了工作上,真是没时间写东西。但一位大佬私信我催更,着实让我感动。即使我的文字只有一个人看,那我也会写下去,而且泡做事怎么会半途而费呢?

Image Signal Processing-第二章-Demosaic去马赛克以及BMP软件实现

  • 1. Demosaic去马赛克
    • 1.1 Demosaic 原理
    • 1.2 Demosaic软件实现
    • 1.3 输出结果
  • 2. BMP位图(Bitmap)
    • 2.1 BMP基础
    • 2.2 保存BMP的软件实现
    • 2.3 输出结果

连载
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的基础以及获取Raw的详细方法。在获取Raw数据后,我们就可以正式开始ISP了。这里,我把ISP分为 必要操作优化操作

必要操作 是指对Raw数据不做处理,尽量无失真地转化为常见的图片格式的数据。
优化操作 是指尽可能分析数据不真实的原因,找到并实施对应的改善处理。

必要操作需要较多关于文件格式以及编码的知识,而优化操作需要较多图像算法的知识。

在本篇连载中,让我先介绍ISP中的必要操作,实现把Raw数据转化为常见图片格式的数据。这不仅可以保证整个ISP的通畅,还可以保存常见图片格式文件,帮助我们方便地分析ISP各模块的效果。

此ISP的必要操作有两个,第一是去马赛克操作Demosaic,第二个是压缩编码操作。Demosaic能转化Bayer域数据成为RGB数据。压缩编码能够将图片数据保存为常见的图片格式文件。

1. Demosaic去马赛克

1.1 Demosaic 原理

前面介绍了,Bayer域的数据是以4个像素为一个单元,由N个单元组成一幅图片。这意味着一个单元有4个像素,但其中两个像素是绿色的GbGr,一个是蓝色的B,一个是红色的R。我们要用红绿蓝合成任意的光,就必须在B像素补充绿色和红色,在R像素补充蓝色和绿色,在GbGr补充红色和蓝色。

那如何去补充这些没有被感光器件输出的颜色呢?

我们采用的方式是估计。由于一幅图片在空间上的信息是有相关性的,而这种相关性体现在数字图像中,就是像素值的线性相关。于是我们可以靠线性估计来估计这些没有的数据。说直白点就是靠邻域已有的数据进行线性插值而获得没有的数据。

上图显示了Demosaic的步骤和具体执行的操作。首先提取贝尔域三个通道的数据。然后对三份数据分别进行线性插值。最后用三个通道的补全数据来组成一幅完整的图片。

上图中Bn/Gn/Rn为已有数据,bn/gn/rn为待估计像素点。则双线性插值的规则是:

b1=(B1+B2)/2
b2=(B1+B3)/2
b3=(B1+B2+B3+B4)/4

g1=(G1+G2+G3+G4)/4
g2=(G3+G4+G5+G6)/4

r1=(R1+R2)/2
r2=(R1+R3)/2
r3=(R1+R2+R3+R4)/4

1.2 Demosaic软件实现

用到的读取各个通道的函数,在上一章介绍Raw图获取和显示时已经介绍过了,此处再次贴出。

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;
}

操作Demosaic的函数如下。要说明的是,此处的插值函数没有对边界值进行操作,即第一行,最后一行,第一列,最后一列没有进行插值。若要全图插值,则需要添加边界值逻辑。此处不再添加。

void Demosaic(int *data, int *B, int *G, int *R) {FirstPixelInsertProcess(data, B);TwoGPixelInsertProcess(data, G);LastPixelInsertProcess(data, R);cout << " Demosaic finished" << endl;
}void FirstPixelInsertProcess(int *src, int *dst) {int i, j;for (i = 0; i < HEIGHT; i++) {for (j = 0; j < WIDTH; j++) {if (i % 2 == 0 && j % 2 == 1 && j > 0 && j < WIDTH - 1) {dst[i * WIDTH + j] = (src[i * WIDTH + j - 1] + src[i * WIDTH + j + 1]) / 2;}if (i % 2 == 1 && j % 2 == 0 && i > 0 && i < HEIGHT - 1) {dst[i * WIDTH + j] = (src[(i - 1) * WIDTH + j] + src[(i + 1) * WIDTH + j]) / 2;}}}for (i = 0; i < HEIGHT; i++) {for (j = 0; j < WIDTH; j++) {if (i % 2 == 1 && j % 2 == 1 && j > 0 &&j < WIDTH - 1 && i > 0 && i < HEIGHT - 1) {dst[i * WIDTH + j] = (src[(i - 1) * WIDTH + j - 1] + src[(i - 1) * WIDTH + j + 1]+ src[(i + 1) * WIDTH + j - 1]+src[(i + 1) * WIDTH + j + 1]) / 4;}                      }}//cout << " First Pixel Insert Process finished " << endl;
}void TwoGPixelInsertProcess(int *src, int *dst) {int i, j;for (i = 0; i < HEIGHT; i++) {for (j = 0; j < WIDTH; j++) {if (i % 2 == 0 && j % 2 == 0 && j > 0 &&j < WIDTH - 1 && i > 0 && i < HEIGHT - 1) {dst[i * WIDTH + j] = (src[i * WIDTH + j - 1] + src[i * WIDTH + j + 1] + src[(i - 1) * WIDTH + j] + src[(i + 1) * WIDTH + j]) / 4;}if (i % 2 == 1 && j % 2 == 1 && j > 0 && j < WIDTH - 1 && i > 0 && i < HEIGHT - 1) {dst[i * WIDTH + j] = (src[i * WIDTH + j - 1] + src[i * WIDTH + j + 1] + src[(i - 1) * WIDTH + j] + src[(i + 1) * WIDTH + j]) / 4;}}}//cout << " TWO Green Pixel Insert Process finished " << endl;
}void LastPixelInsertProcess(int *src, int *dst) {int i, j;for (i = 0; i < HEIGHT; i++) {for (j = 0; j < WIDTH; j++) {if (i % 2 == 1 && j % 2 == 0 && j > 0 && j < WIDTH - 1) {dst[i * WIDTH + j] = (src[i * WIDTH + j - 1] + src[i * WIDTH + j + 1]) / 2;}if (i % 2 == 0 && j % 2 == 1 && i > 0 && i < HEIGHT - 1) {dst[i * WIDTH + j] = (src[(i - 1)*WIDTH + j] + src[(i + 1) * WIDTH + j]) / 2;}}}for (i = 0; i < HEIGHT; i++) {for (j = 0; j < WIDTH; j++) {if (i % 2 == 0 && j % 2 == 0 && j > 0 && j < WIDTH - 1 && i > 0 && i < HEIGHT - 1) {dst[i * WIDTH + j] = (src[(i - 1) * WIDTH + j - 1] +src[(i - 1) * WIDTH + j + 1] + src[(i + 1) * WIDTH + j - 1] + src[(i + 1)*WIDTH + j + 1]) / 4;}}}//cout << " Last Pixel Insert Process finished " << endl;}

通过在主函数中ReadChannels函数后面调用上面介绍的Demosaic(decodedata, Bdata, Gdata, Rdata);函数,实现去马赛克操作。

1.3 输出结果

Demosaic的输出结果如下:

接下来我们对比一下Raw输出和Demosaic后的差异

我们放大图片仔细观察

可以看到Bayer域数据已经完全被补充完整,成为了完整的图像数据,棋盘格现象消除了,图片似乎可以呈现红色和蓝色了。

对比商用ISP处理的jpg文件,我们看看此时还存在什么缺点。

可以看出,棋盘格现象已被解决。遗留问题1. 图片非常暗2. 图片颜色还是非常绿。这两个问题需要到下一篇文章,介绍优化操作时才能解决。

值得注意的是,我们的ISP采用了简单的双线性插值来实现Demosaic。但是实际上,线性插值没有考虑到图像内容突变的情况,也就是图像内容为边缘的情况。这种情况下线性插值会使得边缘变得平滑,最终造成图像边缘模糊的后果。在商用领域,还有一些更为复杂的设计与算法来解决这一问题,这里我也没有太多的了解就不做介绍了。

2. BMP位图(Bitmap)

之前已经介绍了ISP必要操作的Demosaic,现在介绍第二个必要操作,压缩编码。本来想介绍JPEG压缩编码的,但是经过了解,发现JPEG压缩过于复杂了,涉及到离散余弦变换,游程编码,压缩量化,霍夫曼编码(熵编码)。如果整个实现JPEG压缩编码,要动用到大二大三两年多的知识。考虑到时间和复杂程度,最终我选择了非常好实现的BMP图片格式。

2.1 BMP基础

BMP就是计算机中最简单的一个图片格式,图片数据按位储存,我们称之为位图。
位图文件格式如下

我们看看上图蓝色部分BITMAPFILEHEADER结构体的声明

WORD是unsigned short,2个字节。DWORD是unsigned long,4个字节。

bfType 是指文件类型,Windows的BMP类型为BM,其值为0x424D,采用小端存储,应该写为4D42。
bfSize 指整个BMP所占的字节数,即文件大小。
bfReserved1 保留字段,目前需要设置为0
bfReserved2 保留字段,目前需要设置为0
bfOffBits 指图像数据地址相对文件开始地址的偏移量。

我们看看上图绿色部分BITMAPINFOHEADER结构体的声明

biSize 指BITMAPINFOHEADER结构体所占的字节数,就是图中绿色部分的大小
biWidth 指图像的宽度,一行的像素个数
biHeight 指图像的高度,一列的像素个数
biPlanes 一般设置为1
biBitCount 指位深,可以设置为1,4,8,16,24,32。对于8bitRGB数据,应设置为8x3=24
biCompression 指压缩方式,我们这里设置为BI_RGB,即不压缩
biSizeImage 指图像数据所占位数
biXPelsPerMeter 指X轴分辨率,在位宽为24时,用默认值即可
biYpelsPerMeter 指Y轴分辨率,在位宽为24时,用默认值即可
biClrUsed 指调色板使用数量,0值时代表使用所有调色板
biClrImportant 指重要调色板的索引,0表示所有调色板都重要

在我们的ISP中使用压缩后的8bit记录一个像素点的形式,因此位深为24,不需要用调色板。所以调色板代码段,也就是图中黄色部分就被移除了。

最后我们介绍一下上图粉色部分图像数据的排布规则:
打开BMP文件后,图像的显示将从BMP文件数据区的末尾进行倒序扫描。从右到左,从下至上。因此我们在保存Raw图数据的时候,需要把图像数据进行180度旋转。在我的简单ISP中,先对数据以像素为单位倒置,然后对数据进行镜像操作,实现了180度数据旋转。

2.2 保存BMP的软件实现

软件实现分为三个部分,第一部分将10bit图像数据压缩为8bit,第二部对图像数据做180度旋转,第三部分配置Bitmap的头信息,并且写入data数据。

第一部分,压缩数据,压缩的代码已经在上一章分享了。这个简单ISP采取的是暴力去精度的压缩方式。此处再次贴出。

void Compress10to8(int *src, unsigned char *dst) {int i, j;for (i = 0; i < HEIGHT; i++) {for (j = 0; j < WIDTH; j++) {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;}}}
}

第二部分,将图片数据进行180度旋转。

void setBMP(BYTE *data, Mat datasrc) {int j = 0;int row = 0;int col = 0;int size;BYTE temp;size= WIDTH * HEIGHT * datasrc.channels();memset(data, 0x00, size);if (datasrc.channels() == 3) {for (int i = 0; i < WIDTH * HEIGHT; i++) {data[i * 3] = datasrc.data[i * 3];data[i * 3 + 1] = datasrc.data[i * 3 + 1];data[i * 3 + 2] = datasrc.data[i * 3 + 2];}//矩阵反转while (j < 3 * WIDTH * HEIGHT - j) {temp = data[3 * WIDTH * HEIGHT - j - 1];data[3 * WIDTH * HEIGHT - j - 1] = data[j];data[j] = temp;j++;}//图像镜像翻转for (row = 0; row < HEIGHT; row++) {while (col < 3 * WIDTH - col) {temp = data[row * 3 * WIDTH + 3 * WIDTH - col - 1];data[3 * row * WIDTH + 3 * WIDTH - col - 1] = data[3 * row * WIDTH + col];data[3 * row*WIDTH + col] = temp;col++;}col = 0;}}
}

第三部分,配置Bitmap的头信息,并且写入data数据

void saveBMP(BYTE *data, string BMPPath) {BITMAPFILEHEADER header;BITMAPINFOHEADER headerinfo;int size = WIDTH * HEIGHT * 3;//bitmap文件头header.bfType = 0x4D42;header.bfReserved1 = 0;header.bfReserved2 = 0;header.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + size;header.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);//bitmap信息头headerinfo.biSize = sizeof(BITMAPINFOHEADER);headerinfo.biHeight = HEIGHT;headerinfo.biWidth = WIDTH;headerinfo.biPlanes = 1;headerinfo.biBitCount = 24;headerinfo.biCompression = 0; headerinfo.biSizeImage = size;headerinfo.biClrUsed = 0;headerinfo.biClrImportant = 0;//写Bitmap文件cout << " BMPPath: " << BMPPath << endl;ofstream out(BMPPath, ios::binary);out.write((char*)&header, sizeof(BITMAPFILEHEADER));out.write((char*)&headerinfo, sizeof(BITMAPINFOHEADER));out.write((char*)data, size);out.close();cout << " BMP saved "<<endl;
}

在主函数中,完成10bit到8bit的压缩,再完成三通道叠加后,开辟内存
BYTE *BMPdata = new BYTE[WIDTH * HEIGHT * dst.channels()];
以保存Bitmap的图像数据。

调用
setBMP(BMPdata, dst); saveBMP(BMPdata,“C:\Users\Pao\Desktop\output.BMP”);
以实现BMP文件的保存。

2.3 输出结果

通过上述代码,我们实现了在桌面上保存一张BMP图片。
至此,我们实现了ISP中的必要操作,打通了从Raw转到RGB并保存为BMP的路径,为后期的优化操作提供了基础支持。ISP任重而道远。

Image Signal Processing(ISP)-第二章-Demosaic去马赛克以及BMP软件实现相关推荐

  1. Image Signal Processing(ISP)-第三章-BCL, WB, Gamma的原理和软件实现

    Hello!ISP系列文章终于更新了,距离上一篇文章发布已经过去半年多啦!哈哈,虽然这段时间没有写文,但是这个简单ISP的代码还是有更新的哦,有兴趣的朋友可以到Github查看.话不多说,我们接着讲I ...

  2. Image Signal Processing(ISP)-第四章-LSC, CC的原理和软件实现

    Hello!距这个小ISP的软件实现大概过去两年了,一直没有打起精神来将连载写完.但是我们绝不烂尾,绝不! Image Signal Processing-第四章-LSC, CC的原理和软件实现 1. ...

  3. 第二章:并行硬件和并行软件

    第二章:并行硬件和并行软件 背景知识 ​ 冯诺依曼结构:内存与CPU分开,CPU分为控制单元和算术逻辑单元,内存的速度限制了CPU的速度 ​ 进程.多任务和线程:没啥好说的,操作系统都有 ​ 对冯·诺 ...

  4. 《LINUX3.0内核源代码分析》第二章:中断和异常 【转】

    转自:http://blog.chinaunix.net/uid-25845340-id-2982887.html 摘要:第二章主要讲述linux如何处理ARM cortex A9多核处理器的中断.异 ...

  5. 操作系统(王道笔记第二章)

    目录 第二章 2.1_1进程的定义.组成.组成形式.特征 2.1_2进程的状态与转换 2.1_3进程的控制 2.1_4进程通信 2.1_5线程概念和多线程模型 2.2_1处理机调度的概念层次 2.2_ ...

  6. 计算机网络学习笔记:第二章

    文章目录 计算机网络学习笔记:第二章 前言 2.1.应用层协议原理 2.1.1 网络应用程序体系结构 2.1.2 进程通信 2.1.3 可供应用程序使用的运输服务 2.1.4 因特网提供的传输层服务 ...

  7. 死锁 操作系统第二章知识点归纳总结

    系列文章 第一章 操作系统概述 第二章 进程管理之进程描述与控制 第二章 进程管理之进程调度 第二章 进程管理之进程同步 第二章 进程管理 死锁 一组相互竞争系统资源或进行通信的进程间的永久阻塞 2. ...

  8. Digital Signal Processing 数字信号处理

    DSP是什么       | 数字信号处理 | 数字信号处理器 | DSP处理器与通用处理器的比较 |       Digital Signal Processing 数字信号处理       作为一 ...

  9. OS知识点汇总(考研用)——第二章:进程管理(下)

    OS知识点汇总(考研用)--第二章:进程管理(下)  本文参考于<2021年操作系统考研复习指导>(王道考研),<计算机操作系统教程> 思维导图: 文章目录 OS知识点汇总(考 ...

最新文章

  1. python 内存中的文件操作 StringIO cStringIO 简介
  2. HTML-参考手册: HTML 颜色名
  3. SDH点对点接入与MPLS有什么区别?——Vecloud
  4. 【转载】Windows自带.NET Framework版本大全
  5. 消息队列面试 - 为什么使用消息队列,消息队列有什么优点和缺点?
  6. 手机网站开发相关介绍
  7. sql server 监视_使用SQL Server Reporting Services进行快速,肮脏的服务器监视
  8. vue 前端png转pdf_Vue前端HTML保存为PDF的两种常用方式 「干货分享」
  9. Tcl Tutorial 笔记3 ·math
  10. c语言程序设计现代方法算法pdf,c语言程序设计(排序算法).pdf
  11. ps制作计算机考试证件照,如何通过PS制作一寸证件照(超详细流程)?
  12. 免费地图大战?阿里上将高德百度元帅百度地图
  13. 135编辑器代码是html吗,不会代码,你也能做背景样式!!!
  14. Jenkins Pipeline 一键部署SpringBoot项目
  15. 10款国外免费VoIP服务
  16. Flink的Source端和Sink端大全
  17. 代码编写中的疑问与问题解法
  18. 容联云完成内部调查:重新符合纽交所上市要求 仍未递交年报
  19. python算法工程师需要学什么_成为一名 AI 算法工程师,你需要具备哪些能力?...
  20. PLC结构化文本设计模式和算法

热门文章

  1. mysql安装到吐血
  2. 膳食纤维之短链脂肪酸
  3. ArcGIS API For Javascript之调用动态地图服务+属性、空间查询
  4. css复选框表单,用CSS来美化表单——复选按钮篇
  5. 大白话之哈希表和哈希算法
  6. 网站服务器欠费,网络连接正常,显示DNS不可用是否欠费?
  7. dns服务器未响应重启就好,网络诊断提示DNS服务器未响应解决方法 - 全文
  8. 单向链表与双向链表的区别
  9. 高等数学笔记-乐经良老师-第九章-重积分
  10. ​iPhone 14 Pro 全系降价 700 元;Gmail 之父:有了 ChatGPT,搜索引擎活不过两年了|极客头条...