Image Signal Processing(ISP)-第二章-Demosaic去马赛克以及BMP软件实现
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软件实现相关推荐
- Image Signal Processing(ISP)-第三章-BCL, WB, Gamma的原理和软件实现
Hello!ISP系列文章终于更新了,距离上一篇文章发布已经过去半年多啦!哈哈,虽然这段时间没有写文,但是这个简单ISP的代码还是有更新的哦,有兴趣的朋友可以到Github查看.话不多说,我们接着讲I ...
- Image Signal Processing(ISP)-第四章-LSC, CC的原理和软件实现
Hello!距这个小ISP的软件实现大概过去两年了,一直没有打起精神来将连载写完.但是我们绝不烂尾,绝不! Image Signal Processing-第四章-LSC, CC的原理和软件实现 1. ...
- 第二章:并行硬件和并行软件
第二章:并行硬件和并行软件 背景知识 冯诺依曼结构:内存与CPU分开,CPU分为控制单元和算术逻辑单元,内存的速度限制了CPU的速度 进程.多任务和线程:没啥好说的,操作系统都有 对冯·诺 ...
- 《LINUX3.0内核源代码分析》第二章:中断和异常 【转】
转自:http://blog.chinaunix.net/uid-25845340-id-2982887.html 摘要:第二章主要讲述linux如何处理ARM cortex A9多核处理器的中断.异 ...
- 操作系统(王道笔记第二章)
目录 第二章 2.1_1进程的定义.组成.组成形式.特征 2.1_2进程的状态与转换 2.1_3进程的控制 2.1_4进程通信 2.1_5线程概念和多线程模型 2.2_1处理机调度的概念层次 2.2_ ...
- 计算机网络学习笔记:第二章
文章目录 计算机网络学习笔记:第二章 前言 2.1.应用层协议原理 2.1.1 网络应用程序体系结构 2.1.2 进程通信 2.1.3 可供应用程序使用的运输服务 2.1.4 因特网提供的传输层服务 ...
- 死锁 操作系统第二章知识点归纳总结
系列文章 第一章 操作系统概述 第二章 进程管理之进程描述与控制 第二章 进程管理之进程调度 第二章 进程管理之进程同步 第二章 进程管理 死锁 一组相互竞争系统资源或进行通信的进程间的永久阻塞 2. ...
- Digital Signal Processing 数字信号处理
DSP是什么 | 数字信号处理 | 数字信号处理器 | DSP处理器与通用处理器的比较 | Digital Signal Processing 数字信号处理 作为一 ...
- OS知识点汇总(考研用)——第二章:进程管理(下)
OS知识点汇总(考研用)--第二章:进程管理(下) 本文参考于<2021年操作系统考研复习指导>(王道考研),<计算机操作系统教程> 思维导图: 文章目录 OS知识点汇总(考 ...
最新文章
- python 内存中的文件操作 StringIO cStringIO 简介
- HTML-参考手册: HTML 颜色名
- SDH点对点接入与MPLS有什么区别?——Vecloud
- 【转载】Windows自带.NET Framework版本大全
- 消息队列面试 - 为什么使用消息队列,消息队列有什么优点和缺点?
- 手机网站开发相关介绍
- sql server 监视_使用SQL Server Reporting Services进行快速,肮脏的服务器监视
- vue 前端png转pdf_Vue前端HTML保存为PDF的两种常用方式 「干货分享」
- Tcl Tutorial 笔记3 ·math
- c语言程序设计现代方法算法pdf,c语言程序设计(排序算法).pdf
- ps制作计算机考试证件照,如何通过PS制作一寸证件照(超详细流程)?
- 免费地图大战?阿里上将高德百度元帅百度地图
- 135编辑器代码是html吗,不会代码,你也能做背景样式!!!
- Jenkins Pipeline 一键部署SpringBoot项目
- 10款国外免费VoIP服务
- Flink的Source端和Sink端大全
- 代码编写中的疑问与问题解法
- 容联云完成内部调查:重新符合纽交所上市要求 仍未递交年报
- python算法工程师需要学什么_成为一名 AI 算法工程师,你需要具备哪些能力?...
- PLC结构化文本设计模式和算法
热门文章
- mysql安装到吐血
- 膳食纤维之短链脂肪酸
- ArcGIS API For Javascript之调用动态地图服务+属性、空间查询
- css复选框表单,用CSS来美化表单——复选按钮篇
- 大白话之哈希表和哈希算法
- 网站服务器欠费,网络连接正常,显示DNS不可用是否欠费?
- dns服务器未响应重启就好,网络诊断提示DNS服务器未响应解决方法 - 全文
- 单向链表与双向链表的区别
- 高等数学笔记-乐经良老师-第九章-重积分
- ​iPhone 14 Pro 全系降价 700 元;Gmail 之父:有了 ChatGPT,搜索引擎活不过两年了|极客头条...