Как извлечь, выгрузить и обработать массив байтов из GetPixelSpan, а затем сохранить обратно в файл? - PullRequest
0 голосов
/ 05 сентября 2018

Это, наверное, очень простой вопрос, но я не могу понять, как собрать все воедино. Этот вопрос и этот вопрос , а также эта страница в документации API все несколько намекают на ответ, но я не смог понять, что Мне нужно от них.

Так что сейчас я пытаюсь реализовать наивную небольшую программу, чтобы открыть изображение, вывести пиксели в массив, обработать их немного, а затем сохранить обновленные пиксели обратно как новое изображение. В данном конкретном случае я хочу взять среднее значение в окне 3х3 вокруг каждого пикселя как простое размытие. Конкретная операция не слишком важна (определенно есть более эффективные способы, я специально пытаюсь написать наивную версию прямо сейчас для последующего сравнения с другими версиями), но я не смог понять, как это сделать. бывает. Прямо сейчас у меня есть:

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, то получаю сообщение об ошибке об отсутствующем декодере, но если я его включаю, я получаю сообщение об ошибке об отсутствующем SOI.

Итак, мой вопрос: как я могу извлечь пиксели и работать с ними / каждым каналом с переменным размером, большим, чем 8 бит (например, 32 бита), чтобы я мог выполнять промежуточные операции, которые не могут быть представлены в 8 бит на канал, перед тем как преобразовать окончательный результат обратно в байты, а затем восстановить его обратно во что-то, что можно сохранить на диск как образ?

Возможно, я забыл упомянуть кое-что важное, поэтому, пожалуйста, не стесняйтесь спрашивать разъяснения :) Спасибо.

1 Ответ

0 голосов
/ 05 сентября 2018

Я не знаком с F #, но похоже, что есть несколько проблем:

  • Строка Image.Load<Rgba32>(img.GetConfiguration(), rpx, Formats.Jpeg.JpegDecoder()) попытается декодировать поток в памяти, закодированный в формате Jpeg (предоставляется как byte[]).

  • По вашему вопросу:

    , чтобы я мог выполнять промежуточные операции, которые не могут быть представлены в 8 битах на канал

Почему бы вам просто не поработать с массивом Rgba32[]? Там нет необходимости для extractPixelParts ... вещи. Хранение всех ваших пикселей в зубчатом массиве (uint32[][]) приведет к очень медленному выполнению кода из-за ненужных выделений кучи.

EDIT: Извините, я неправильно понял этот момент. Если вам нужна более высокая точность для промежуточных операций, я предлагаю использовать Vector4! Вы можете использовать pixel.ToVector4() и pixel.PackFromVector4(...)

Мое предложение (все еще не оптимизировано, но, вероятно, легко понять):

  1. Не копировать изображение. Просто создайте массив Rgba32[] (!!!) с помощью let pxs = img.GetPixelSpan().ToArray()
  2. Обрабатывать значения внутри массива по формуле arr[y * Width + x] = CreateMyNewRgbaPixelValueAtXY(....), где CreateMyNewRgbaPixelValueAtXY(...) должен возвращать Rgba32
  3. Вернуть новое изображение по Image.LoadPixelData(pxs). Метод LoadPixelData создаст новое изображение, скопировав в него ваши данные pxs: Rgba32[].
  4. Утилизируйте исходное изображение!

РЕДАКТИРОВАТЬ 2

Для эффективного выполнения промежуточной операции я предлагаю следующее:

  • Создайте inputPixelData:Vector4[] для вашего промежуточного массива, заполненного вызовом pixel.ToVector4() для каждого входного пикселя
  • Создайте другой массив outputPixelData:Vector4[] и заполните его обработкой inputPixelData
  • Упакуйте outputPixelData обратно в массив pixels:Rgba32[], используя .PackFromVector4(outputPixelData[y * Width + x]) (Не знаю, как лучше всего это сделать в F #)
  • Image.LoadPixelData(pixels)

Возможно, есть лучший способ, но я незнаком с F #.

...