如何从GetPixelSpan中提取,上传和处理字节数组,然后保存回文件?

时间:2018-09-05 02:34:05

标签: f# .net-core imagesharp

这可能是一件非常简单的事情,但是我还不太清楚如何将各个部分组合在一起。 API文档中的This questionthis question以及this page都在某种程度上暗示了答案,但我无法从中找出我需要的东西。

因此,现在我正在尝试实施一个简单的小程序来打开图像,将像素取出到数组中,进行一些处理,然后将更新后的像素另存为新图像。在这种特殊情况下,我想将每个像素周围3x3窗口的平均值作为简单的模糊。具体操作不是太重要(肯定有更有效的方法,我现在正在尝试写一个简单的版本,以便以后与其他版本进行比较),但是我无法弄清楚该如何做发生。现在我拥有的是:

let accessClampedArrayWithDefault (arr: uint32[][]) width height def x y : uint32[] =
    if x < 0 || x > width-1 || y < 0 || y > height-1 then
        def
    else
        arr.[x + width * y]

let extractPixelParts (p: Rgba32) =
    let R = uint32 p.R
    let G = uint32 p.G
    let B = uint32 p.B
    let A = uint32 p.A
    [|R; G; B; A|]

[<EntryPoint>]
let main argv =
    use img = Image.Load(@"D:\Users\sampleimage.jpg")    
    let mutable out_img = img.Clone()    
    let pxs = img.GetPixelSpan().ToArray() |> Array.map extractPixelParts    
    let mutable (nps: uint32[][]) = Array.zeroCreate pxs.Length    
    let ac = accessClampedArrayWithDefault pxs img.Width img.Height [|0u;0u;0u;0u|]

    for x in 0..img.Width-1 do
        for y in 0..img.Height-1 do
            let p = ac x y
            for z in -1..1 do
                for w in -1..1 do
                    let q = ac (x + z) (y + w)
                    nps.[x + y * img.Width] <- Array.zip p q |> Array.map (fun (a,b) -> a + b)
            nps.[x + y * img.Width] <- Array.map (fun i -> float i / 9.0 |> uint32 ) nps.[x + y * img.Width]

    let rpx = Array.collect (fun a -> Array.map byte a) nps

    let out_img = Image.Load<Rgba32>(img.GetConfiguration(), rpx, Formats.Jpeg.JpegDecoder())

    printfn "out_img's width is %d and height is %d" out_img.Width out_img.Height

,但是它失败,并在let out_img =行上出现异常。如果我不包括JpegDecoder部分,则会收到有关缺少的解码器的错误消息,但如果确实包含JpegDecoder,则会收到有关缺少的SOI的错误消息。

所以,我的问题是,如何才能以大于8位(例如32位)的可变大小提取像素并使用它们/每个通道,以便我可以执行无法以每8位表示的中间操作通道,然后将最终结果转换回字节,然后将其重构回可以作为映像保存到磁盘的内容?

我很可能忘了提到一些重要的事情,因此请随时提出澄清说明:)谢谢。

1 个答案:

答案 0 :(得分:2)

我对F#不熟悉,但是看起来有几个问题:

  • Image.Load<Rgba32>(img.GetConfiguration(), rpx, Formats.Jpeg.JpegDecoder())行将尝试解码一个Jpeg编码的内存流(提供为byte[])。

  • 关于您的问题:

      

    这样我就可以执行无法以每通道8位表示的中间操作

您为什么不只处理Rgba32[]数组? 不需要extractPixelParts ...的东西。由于不必要的堆分配,将所有像素存储在锯齿状数组(uint32[][])中将导致代码执行非常缓慢。

编辑: 抱歉,我误解了这一点。如果您需要更高的精度进行中间操作,建议使用Vector4!您可以使用pixel.ToVector4()pixel.PackFromVector4(...)

我的建议(仍未优化,但可能易于理解):

  1. 请勿复制图像。只需通过Rgba32[]
  2. 创建一个 let pxs = img.GetPixelSpan().ToArray()(!!!)数组
  3. 使用公式arr[y * Width + x] = CreateMyNewRgbaPixelValueAtXY(....)处理数组内的值,其中CreateMyNewRgbaPixelValueAtXY(...)应该返回Rgba32
  4. 通过Image.LoadPixelData(pxs)返回新图像。 LoadPixelData方法会通过将pxs: Rgba32[]数据复制到其中来创建新图像。
  5. 处置原始图像!

编辑2

为了高效地执行中间操作,我建议以下内容:

  • 通过为每个输入像素调用inputPixelData:Vector4[]为填充的中间数组创建pixel.ToVector4()
  • 创建另一个数组outputPixelData:Vector4[]并通过处理inputPixelData填充它
  • 使用outputPixelDatapixels:Rgba32[]打包到.PackFromVector4(outputPixelData[y * Width + x])数组中(不知道在F#中什么是最好的方法)
  • Image.LoadPixelData(pixels)

可能有更好的方法,但是我不熟悉F#。