using Sdcb.LibRaw; using RawContext r = RawContext.OpenFile(@"C:\a7r3\DSC02653.ARW"); r.Unpack(); r.DcrawProcess(); using ProcessedImage image = r.MakeDcrawMemoryImage(); using Bitmap bmp = ProcessedImageToBitmap(image); Bitmap ProcessedImageToBitmap(ProcessedImage rgbImage) { // 堆代码 duidaima.com rgbImage.SwapRGB(); using Bitmap bmp = new Bitmap(rgbImage.Width, rgbImage.Height, rgbImage.Width * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, rgbImage.DataPointer); return new Bitmap(bmp); }这段代码主要演示如何将RAW照片转换为Bitmap图像,有一点值得一提:LibRaw输出的像素格式和Bitmap有些许不同,具体体现在红蓝两色需要调换,代码中使用rgbImage.SwapRGB();用来调换红色和蓝色的顺序,也就是将RGB24转换成了BGR24。虽然这个示例基于.ARW照片,但实际上几乎所有RAW格式照片都是支持的,包括.CR2或者.DNG,可以通过RawContext.SupportedCameras获取支持的相机列表,截止当前版本它支持了1182款相机型号:
Console.WriteLine("Sdcb.LibRaw supported cameras:"); foreach (string model in RawContext.SupportedCameras) { Console.WriteLine(model); }输出如下(有省略):
Sdcb.LibRaw supported cameras: 1: Adobe Digital Negative (DNG) 2: AgfaPhoto DC-833m ... 1057: Sony ILCE-1 (A1) ... 1181: Zeiss ZX1 1182: Zenit M2. WPF和OpenCV示例
BitmapSource ProcessedImageToBitmapSource(ProcessedImage rgbImage) { return BitmapSource.Create(rgbImage.Width, rgbImage.Height, 96, 96, PixelFormats.Rgb24, null, rgbImage.AsSpan<byte>().ToArray(), rgbImage.Width * 3); }值得一提的是 WPF 的图片 BitmapSource 并不需要调换 R和 B 两个通道的颜色。如果你使用的是 OpencvSharp4 ,可以使用下面的代码将 ProcessedImage 转换为Mat:
Mat ProcessedImageToMat(ProcessedImage rgbImage) { Mat mat = new Mat(rgbImage.Height, rgbImage.Width, MatType.CV_8UC3, rgbImage.AsSpan<byte>().ToArray()); Cv2.CvtColor(mat, mat, ColorConversionCodes.RGB2BGR); return mat; }请注意上面3个示例中,我都使用了.AsSpan<byte>().ToArray()用来将内存复制一份。这样一来额外会复制会让性能略微降低,这是为了确保Bitmap、BitmapSource或Mat的生命周期由自己来管理,否则它们会共享使用ProcessedImage的内存,导致意外的情况。
// 小心,代码直接使用了由ProcessedImage创建的内存 Mat ProcessedImageToMatZeroCopy(ProcessedImage rgbImage) { Mat mat = new Mat(rgbImage.Height, rgbImage.Width, MatType.CV_8UC3, rgbImage.DataPointer); // 换成rgbImage.DataPointer Cv2.CvtColor(mat, mat, ColorConversionCodes.RGB2BGR); return mat; }3. 图像后期
r.DcrawProcess(c => { c.HalfSize = false; // 图片只保留1/4大小 c.UseCameraWb = true; // 使用机内白平衡,false则会由UserMultipliers控制白平衡 c.Gamma[0] = 0.35; // 调整Gamma曲线指数 c.Gamma[1] = 3.5; // 调整Gamma曲线斜率 c.Brightness = 2.2f; // 亮度 c.Interpolation = true; // 是否执行反马赛克(demosaic)操作 c.OutputBps = 8; // 输出位数8位 c.OutputTiff = false; // 输出为tiff文件?false表示输出Bitmap // c.Cropbox = new Rectangle(4000, 2000, 1500, 700); // 裁切 // 还有许多其它设置可以自行探索 });原图:
using Sdcb.LibRaw; using RawContext r = RawContext.OpenFile(@"C:\a7r3\DSC02653.ARW"); using ProcessedImage image = r.ExportThumbnail(thumbnailIndex: 0); using Bitmap bmp = (Bitmap)Bitmap.FromStream(new MemoryStream(image.AsSpan<byte>().ToArray()));在上面的示例中我使用了r.ExportThumbnail(thumbnailIndex: 0);,它可以导出第0张缩略图,请注意这个是一个快捷函数,它内部会调用了下面2个函数:
r.UnpackThumbnail() r.MakeDcrawMemoryThumbnail()请注意它转换为Bitmap的方式有所不同,由于它的数据本质是JPEG格式,因此不再需要更换红色、蓝色的通道位置,同样也不需要关注它的宽度和高度,同样的道理如果使用OpenCV解码也应该使用Cv2.ImDecode。
using Sdcb.LibRaw; using RawContext r = RawContext.OpenFile(@"C:\a7r3\DSC02653.ARW"); // r.SaveRawImage() is a shortcut for r.Unpack() + r.DcrawProcess() + r.WriteDcrawPpmTiff(fileName) r.SaveRawImage(@"C:\test\test.tiff"); 同样地r.SaveRawImage(@"C:\test\test.tiff");也是Sdcb.LibRaw提供的一个快捷方式,它内部按顺序调用了下面3个函数: r.Unpack() r.DcrawProcess() r.WriteDcrawPpmTiff()6. 获取照片元数据信息
using RawContext r = RawContext.OpenFile(@"C:\a7r3\DSC02653.ARW"); LibRawImageParams imageParams = r.ImageParams; LibRawImageOtherParams otherParams = r.ImageOtherParams; LibRawLensInfo lensInfo = r.LensInfo; Console.WriteLine($"相机: {imageParams.Model}"); Console.WriteLine($"版本号: {imageParams.Software}"); Console.WriteLine($"ISO: {otherParams.IsoSpeed}"); Console.WriteLine($"快门速度: 1/{1 / otherParams.Shutter:F0}s"); Console.WriteLine($"焦距: {otherParams.FocalLength}mm"); Console.WriteLine($"艺术家标签: {otherParams.Artist}"); Console.WriteLine($"拍摄日期: {new DateTime(1970, 1, 1, 8, 0, 0).AddSeconds(otherParams.Timestamp)}"); Console.WriteLine($"镜头名称: {lensInfo.Lens}");在我的这个示例中,输出如下:
相机: ILCE-7RM3 版本号: ILCE-7RM3 v3.10 ISO: 100 快门速度: 1/400s 焦距: 50mm 艺术家标签: Zhou Jie/sdcb 拍摄日期: 2023/1/26 12:54:01 镜头名称: FE 50mm F1.2 GM
var sw = Stopwatch.StartNew(); using RawContext r = RawContext.OpenFile(@"C:\Users\ZhouJie\Pictures\a7r3\11030126\DSC02653.ARW"); r.Unpack(); r.DcrawProcess(); Console.WriteLine($"耗时:{sw.ElapsedMilliseconds}ms");输出如下:
耗时:1627ms
Windows Imaging Component
之前的文章说过,可以使用系统自带的WIC进行RAW照片解码:
// 需要安装NuGet包:Vortices.Direct2D1 Stopwatch sw = Stopwatch.StartNew(); IWICImagingFactory2 wic = new IWICImagingFactory2(); using IWICBitmapDecoder decoder = wic.CreateDecoderFromFileName(@"C:a7r3\DSC02653.ARW"); using IWICFormatConverter converter = wic.CreateFormatConverter(); converter.Initialize(decoder.GetFrame(0), PixelFormat.Format24bppBGR); var data = new byte[converter.Size.Width * 3 * converter.Size.Height]; converter.CopyPixels(converter.Size.Width * 3, data); Console.WriteLine($"耗时:{sw.ElapsedMilliseconds}ms"); // 下面转Bitmap // fixed (byte* pdata = data) // { // new System.Drawing.Bitmap(converter.Size.Width, converter.Size.Height, converter.Size.Width * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, (IntPtr)pdata).DumpUnscaled(); // }输出如下:
耗时:2177ms这个方案的缺点是它无法对 RAW 照片做一些后处理。
Stopwatch sw = Stopwatch.StartNew(); using MagickImage image = new MagickImage(@"C:\a7r3\DSC02653.ARW"); Console.WriteLine($"耗时:{sw.ElapsedMilliseconds}ms");输出如下:
耗时:5496ms这个方案的缺点是它明显慢一些,且它的后处理都并非基于拜尔数据,因此后期空间有限。
#include <libraw\libraw.h> #include <chrono> int main() { auto start = std::chrono::high_resolution_clock::now(); libraw_data_t* data = libraw_init(0); libraw_open_file(data, "C:\\a7r3\\DSC02653.ARW"); libraw_unpack(data); libraw_dcraw_process(data); auto end = std::chrono::high_resolution_clock::now(); printf("耗时: %lld ms\n", std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()); libraw_recycle(data); libraw_close(data); }输出如下:
耗时: 1619 ms
方案名称 | 耗时(ms) | 说明 |
---|---|---|
Sdcb.LibRaw | 1627 | |
Windows Imaging Component(WIC) | 2177 | 后期空间有限 |
Magick.NET | 5496 | 后期空间有限 |
原生C代码 | 1619 |