在之前的这篇笔记中,我们介绍过利用RawPy工具基于Python语言实现对于相机Raw数据的解析。在这篇笔记中,还是完成同样的目标,但利用LibRaw这个库、以C++语言实现。
1.LibRaw简介
在前面,我们提到了RawPy这个Python库,如果你看过它项目的主页便会知道,它其实就是LibRaw库的Python版本接口。我们这篇则重点关注LibRaw,官网地址是这里,官方GitHub地址是这里。根据官网介绍,LibRaw可以让Raw相关的开发更加专注于算法和对数据的处理,不用担心很多繁杂、底层的问题,例如元数据的不同格式、不同压缩方式、Bayer Pattern读取等。因此,LibRaw具有丰富的扩展性,可以有效应对各种Raw数据相关任务。
2.LibRaw的安装
LibRaw官方并非是一个标准的CMake项目,因此不能按照之前经典步骤进行安装。但总体也非常简单。首先,在LibRaw官网下载源码,然后解压,进入源码目录,打开终端,输入如下内容:
./configure
make -j
sudo make install
编译、安装完成的界面如下。 至此,就完成了LibRaw的安装。
3.LibRaw的使用
LibRaw这个库的使用相对特殊一些。一般情况下,在CMake项目中使用其它依赖都是先通过find_package
命令查找,include_directories
命令把相关头文件路径包含进来,最后target_link_libraries
命令将对应的动态链接库关联到程序中。但对于LibRaw,在安装完成后使用并不需要查找包、也不用include目录,只需要在最后link一下它的链接库即可。而link的库也不是常见的${XXXX_LIBS}
这种格式,如果这样的话在我电脑上会提醒找不到。所以直接指定完整路径即可,/usr/local/lib/libraw.so
。
在这里我们简单模拟一个任务:利用相机拍摄了一张Raw相片,然后利用LibRaw进行解析,最后保存成jpg文件。这里面主要涉及OpenCV和LibRaw两个库。
3.1 CMakeLists文件
CMakeLists.txt文件如下:
cmake_minimum_required(VERSION 3.2)
project(libRaw_demo)
set(CMAKE_CXX_STANDARD 11)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(libRaw_demo main.cpp)
target_link_libraries(libRaw_demo
${OpenCV_LIBS}
/usr/local/lib/libraw.so
)
可以看到,我们不需要在CMakeLists.txt
里做任何查找的步骤,直接link一下库,然后在代码文件中包含头文件即可使用。
3.2 代码文件
#include <iostream>
#include "opencv2/opencv.hpp"
#include "libraw/libraw.h"
using namespace std;
using namespace cv;
int main() {
string raw_path = "../test/test.dng";
// step1. create image processor
LibRaw myRawProcessor;
// step2. open raw image file
if (myRawProcessor.open_file(raw_path.c_str()) != LIBRAW_SUCCESS) {
std::cout << "Open file fail" << std::endl;
}
// step3. unpack the raw data
myRawProcessor.unpack();
// step4. convert imgdata.rawdata to imgdata.image
myRawProcessor.raw2image();
// step5. get bayer pattern image
cv::Mat bayer_image(myRawProcessor.imgdata.sizes.iheight,
myRawProcessor.imgdata.sizes.iwidth, CV_16UC1,
myRawProcessor.imgdata.rawdata.raw_image);
// step6. convert to grayscale
cv::Mat color_image, gray_image;
cvtColor(bayer_image, color_image, cv::COLOR_BayerGB2BGR);
cvtColor(color_image, gray_image, cv::COLOR_BGR2GRAY);
// step7. output
imwrite("color_image.jpg", color_image);
imwrite("gray_image.jpg", gray_image);
// step8. release
myRawProcessor.recycle();
return 0;
}
可以看到,代码整理还是比较清晰的。核心主要就是第三、第四步。第三步的作用是将Raw文件中存储的二进制数据解包出来,变成可读的数据。然后第四步将这些数据转换成可显示的图片。然后,将获取到的Bayer Pattern数据转换到Mat格式,并利用OpenCV的函数转换到RGB彩色影像。转换好的影像如下图所示。 可以看到,这里出现了经典的偏绿的情况。这是因为我们只是“读取”并可视化了Raw数据,没有做任何的校正和处理。但利用LibRaw进行Raw数据处理的关键步骤就是这样。上面完整的项目也上传到了GitHub,欢迎查看。
4.LibRaw中主要数据结构与类型
在上面,我们只是读取了Raw数据,没有对Raw数据做进一步处理。本部分主要参考这个官方文档,对部分我觉得重要的数据类型与变量进行介绍。完整版参阅上面的文档。
在LibRaw中,libraw_data_t
类型的imgdata
变量是为提供的用户数据访问接口类,访问方式为LibRaw::imgdata(class_instance.imgdata)
。具体又包含以下主要字段:
idata
: libraw_iparams_t类型,记录从Raw文件读取到的主要参数,详细包含哪些内容见后文。sizes
: libraw_image_sizes_t类型,记录影像的几何相关参数,详细包含哪些内容见后文。color
: libraw_colordata_t类型,描述从文件中读取的颜色信息,详细包含哪些内容见后文。other
: libraw_imgother_t类型,描述影像的感光度、快门、光圈、焦距等信息,详细包含哪些内容见后文。rawdata
: libraw_rawdata_t类型,提供一个指向raw-data缓冲区的指针,用于访问和修改读取的数据,详细使用见后文。thumbnail
: libraw_thumbnail_t类型,在LibRaw成功打开文件后,会自动生成预览文件,详细使用见后文。lens
: libraw_lensinfo_t类型,描述用于拍摄时所用的镜头信息,详细包含哪些内容见后文。*image
: 长度为4的ushort数组类型,提供指向影像像素数据的指针,其内容在调用raw2image()
或dcraw_process()
后被填充。
4.1 libraw_iparams_t
类型的idata
变量
主要描述影像的一些通用信息,进一步包含如下主要字段:
make
: 长度为64的char类型的数组,储存设备的生产商信息。model
: 长度为64的char类型的数组,储存设备的模组信息。software
: 长度为64的char类型的数组,储存Raw数据拍摄时的软件版本信息。raw_count
: unsigned类型的数字,表示文件中包含多少个Raw影像(0代表没有成功识别)。dng_version
: unsigned类型的数字,表示Raw数据dng格式的版本信息。colors
: int类型的数字,表示Raw数据包含颜色的个数。cdesc
: char类型长度为5的数组,表示RGBG, RGBE, GMCY, GBTG颜色模式中的一种,索引从0到3的颜色。
4.2 libraw_image_sizes_t
类型的sizes
变量
主要描述影像的几何尺寸信息,进一步包含如下主要字段:
raw_height
: ushort类型的数字,表示Raw影像在高度上的全部大小(包含frame)。raw_width
: ushort类型的数字,表示Raw影像在宽度上的全部大小(包含frame)。height
: ushort类型的数字,表示影像可见部分(有意义部分)的像素高度(没有frame)。width
: ushort类型的数字,表示影像可见部分(有意义部分)的像素宽度(没有frame)。top_margin
: ushort类型的数字,frame的左上角点的高度方向坐标。left_margin
: ushort类型的数字,frame的左上角点的宽度方向坐标。iheight
: ushort类型的数字,表示输出影像的像素高度。iwidth
: ushort类型的数字,表示输出影像的像素宽度。raw_pitch
: unsigned类型的数字,表示以byte计算的每一行Raw数据所占据的大小。pixel_aspect
: double类型的数字,表示像素的width和height的比值。flip
: int类型的数字,表示影响的方向(0-无需旋转,3-180度旋转,5-90度逆时针旋转,6-90度顺时针旋转)。
4.3 libraw_colordata_t
类型的color
变量
主要描述影像的颜色信息,进一步包含如下主要字段:
curve
: ushort类型长度为65536的数组,表示相机的色调曲线(tone curve)。black
: unsigned类型的black level数值,它可能为0(这意味着在Raw数据解包阶段已经进行了black level的消除或者相机输出数据时已经完成)。cblack
: unsigned类型长度为4102的数组,表示每个颜色通道的black level数值。data_maximum
: unsigned类型的数字,表示当前Raw文件中最大的像素值(必须要在调用raw2image()
或dcraw_process()
后才会被填充)。maximum
: unsigned类型的数字,表示相机可以记录的最大的像素值。linear_max
: 长度为4的unsigned类型的数组,表示从Raw文件元数据中读取到的每个颜色通道的线性最大数值。fmaximum
: float类型的数字,表示相机可以记录的最大的像素值(以float类型表示)。fnorm
: float类型的数字,表示将float类型的Raw数据转换到int类型时的归一化系数。cam_xyz
: 4×3的float类型的数组,表示从相机RGB颜色空间到XYZ颜色空间的转换矩阵。cam_mul
: 长度为4的float类型的数组,表示拍摄时的白平衡参数。pre_mul
: 长度为4的float类型的数组,表示白天的白平衡参数。cmatrix
: 3×4的float类型的数组,表示从Raw文件中读取的相机颜色信息。rgb_cam
: 3×4的float类型的数组,表示从相机颜色空间到sRGB颜色空间的转换矩阵。ccm
: 3×4的float类型的数组,表示从文件元数据中读取的颜色校正矩阵。flash_used
: float类型的数字,表示是否使用闪光灯。
4.4 libraw_imgother_t
类型的other
变量
主要描述影像的其它信息,进一步包含如下主要字段:
iso_speed
: float类型的数字,表示感光度ISO的大小。shutter
: float类型的数字,表示快门速度。aperture
: float类型的数字,表示光圈大小。focal_len
: float类型的数字,表示焦距。timestamp
: time_t类型的数字,表示拍摄时的时间戳。gpsdata
: 长度为32的unsigned类型的数组,表示拍摄的位置信息。parsed_gps
: libraw_gps_info_t类型的对象,包含解析好的经度、纬度和高程信息。desc
: 长度为512的char类型的数组,表示影像的描述信息。artist
: 长度为64的char类型的数组,表示影像的拍摄者信息。
4.5 libraw_rawdata_t
类型的rawdata
变量
主要描述没有解析的影像Raw数据,进一步包含如下主要字段:
*raw_image
: unsigned short类型的指针,指向原始Bayer数据。*color3_image
: unsigned short数组类型的指针,指向包含3个颜色分量的像素。*color4_image
: unsigned short数组类型的指针,指向包含4个颜色分量的像素。*float_image
: float类型的指针,指向数据类型为float的Byer数据。*float3_image
: float数组类型的指针,指向数据类型为float包含3个颜色分量的像素。*float4_image
: float数组类型的指针,指向数据类型为float包含4个颜色分量的像素。
在官方文档中明确说了:在调用unpack()
函数以后,这些字段只有一个是非空的,其它所有字段外部用户应该尽力避免访问,避免指针问题。
4.6 libraw_thumbnail_t
类型的thumbnail
变量
主要描述保存在Raw文件中的预览图片的信息,进一步包含如下主要字段:
tformat
: LibRaw_thumbnail_formats类型(枚举类型),具体包括:LIBRAW_THUMBNAIL_UNKNOWN、LIBRAW_THUMBNAIL_JPEG、LIBRAW_THUMBNAIL_BITMAP、LIBRAW_THUMBNAIL_BITMAP16、LIBRAW_THUMBNAIL_LAYER、LIBRAW_THUMBNAIL_ROLLEI、LIBRAW_THUMBNAIL_H265。twidth
: ushort类型数字,表示预览图片的像素宽度。theight
: ushort类型数字,表示预览图片的像素高度。tlength
: unsigned类型数字,表示预览图片一样byte计算的大小。tcolors
: int类型数字,表示预览图片中包含的颜色个数。*thumb
: char类型的指针,指向从Raw文件中读取的预览图片数据。
4.7 libraw_lensinfo_t
类型的lens
变量
主要描述保存在Raw文件中的镜头相关信息,由于这一部分和相机和镜头硬件结合比较紧密,且各个厂家都有不同的定义,因此,没有相对统一的接口包含libraw_makernotes_lens_t
、libraw_nikonlens_t
、libraw_dnglens_t
、libraw_lensinfo_t
等内容。感兴趣可以参考官方文档里列举的对应部分,获取数据。
5.LibRaw中主要函数
这里我们简单列举、介绍LibRaw中相关的函数。本部分主要参考这个官方文档。根据文档介绍,有以下需要注意的几点:
-
- 整个Raw数据的处理流程都通过LibRaw这个类的实例化对象来完成。
-
- 一个实例化对象同时只能处理一张影像,可以实例化多个对象,从而同时处理多个影像。
-
- 如果要多个同时处理,需要关注内存的占用,可能会比较大。
5.1 打开数据
在LibRaw中提供了如下函数用于打开不同类型的数据:
LibRaw::open_datastream()
: 打开Raw数据流LibRaw::open_file()
: 打开Raw数据文件LibRaw::open_buffer()
: 打开Raw数据缓冲区数据LibRaw::open_bayer()
: 打开Bayer模式的Raw数据LibRaw::unpack()
: 打开Raw数据,并计算Black Level,相关结果保存在imgdata.image
中LibRaw::unpack_thumb()
: 打开Raw数据并生产缩略图,相关结果保存在imgdata.thumbnail.thumb
中LibRaw::unpack_thumb_ex()
: 打开第i个缩略图
5.2 辅助函数
LibRaw中也提供了一些辅助函数可供使用,部分列举如下:
LibRaw::version()
: 返回char类型的版本字符串LibRaw::cameraCount()
: 返回int类型的支持的相机Raw数据格式的个数LibRaw::cameraList()
: 返回char类型的list,包含具体支持的Raw数据格式的信息LibRaw::COLOR()
: 返回Bayer模式数据中指定行列位置的像素颜色,对于4分量的Bayer数据,范围为0-3,对于3分量数据,范围为0-2LibRaw::subtract_black()
: 执行Black Level消除,colordata.data_maximum、colordata.maximum、black level data (colordata.black和colordata.cblack)都会进行相应调整LibRaw::is_floating_point()
: 判断是否为浮点型的Raw数据,返回为1表示包含浮点数据LibRaw::convertFloatToInt()
: 将浮点型Raw数据转换为int型数据
5.3 Raw数据后处理函数
LibRaw中也提供了一些后处理函数可供使用。需要注意的是,这些后处理函数应该在open_file()
和unpack()
函数之后调用。部分列举如下:
LibRaw::raw2image()
: 将Raw数据转化为可以处理的缓冲区数据LibRaw::free_image
: 将由raw2image()申请的缓冲区imgdata.image数据释放LibRaw::dcraw_process()
: 在unpack()
函数之后调用,模拟Raw数据的后处理过程
5.4 Raw数据输出函数
LibRaw提供了数据处理后输出的函数,主要如下:
LibRaw::dcraw_ppm_tiff_writer()
: 将处理结果输出为无损的PPM/PGM/TIFF格式(通过imgdata.params.output_tiff属性设置)LibRaw::dcraw_thumb_writer()
: 将缩略图输出为PPM或JPEG格式图片
6.官方示例程序
LibRaw官方提供了示例程序以供学习,源码放在了LibRaw根目录下的samples
文件夹内,编译LibRaw成功以后,生成的可执行文件在LibRaw根目录下的bin
文件夹内。包含如下示例:
- raw-identify: 展示了LibRaw打开读取Raw文件的基本用法(
open_file()
函数)。 - simple_dcraw: 简单展示了解码Raw数据的流程。首先用
open_file()
函数打开文件,然后用unpack()
函数解析数据。一方面,调用unpack_thumb_ex()
函数和unpack_thumb()
函数获取需要的缩略图,并通过dcraw_thumb_writer()
函数保存。另一方面,调用dcraw_process()
函数进行Raw数据的后处理,并将处理结果通过dcraw_ppm_tiff_writer()
函数无损保存。 - dcraw_half: 简单展示了解码Raw数据的流程(C语言版本)。
- dcraw_emu: 较为完整的Raw数据解析示例,支持诸多参数。首先通过
open()
和mmap()
函数将Raw文件读取,并映射到内存中指定的区域,然后调用open_buffer()
函数实现对该数据的解析(当然,代码也提供了open_file()
函数的实现)。然后是常规的用unpack()
函数解析数据,再用dcraw_process()
函数进行Raw数据的后处理,最后将处理结果通过dcraw_ppm_tiff_writer()
函数无损保存。 - half_mt: 还是Raw数据的读取、解析和输出过程,但是尺寸是缩小为原图的一半。
- mem_image: 使用dcraw_make_mem_image()和dcraw_make_mem_thumb()函数,实现Raw数据的读取、解析和输出。
- unprocessed_raw: 展示了读取和解析Raw数据的流程并进行了逐像素的Gamma校正,主要利用
open_file()
函数和unpack()
函数完成。 - 4channels: 将Raw数据分解为16bit量化的4个波段的影像,核心还是基于
open_file()
函数和unpack()
函数完成,感兴趣可以参考源码实现自己想要的功能。 - multirender_test: 展示了一次打开文件,然后多次对其进行解析渲染的示例。
- postprocessing_benchmark: 展示Raw数据处理各个步骤的耗时。
- openbayer_sample: 展示了利用
open_bayer()
函数打开Raw数据的示例。
以上便是LibRaw库的相关笔记内容,可以看到,其实它并不像OpenCV那样全能,没有提供Raw数据的处理算法相关的函数,更多只是作为一个Raw数据读取和输出的角色存在。所以,如果要实现对Raw数据的处理,利用LibRaw读取数据以后如何做才是关键。
7.参考资料
- [1] https://pypi.org/project/rawpy/
- [2] https://www.libraw.org/
- [3] https://github.com/LibRaw/LibRaw
- [4] https://blog.csdn.net/zenglongjian/article/details/129225919
- [5] https://www.libraw.org/docs/API-overview.html
- [6] https://www.libraw.org/docs/API-datastruct-eng.html
- [7] https://www.libraw.org/docs/API-CXX.html
- [8] https://www.libraw.org/docs/API-datastruct.html
- [9] https://www.libraw.org/docs/Samples-LibRaw.html
本文作者原创,未经许可不得转载,谢谢配合