Максимум нескольких изображений или массивов в Julia - PullRequest
4 голосов
/ 26 мая 2020

Я хочу найти максимум несколько изображений: загрузить их в массив и найти максимум по первому измерению.

Python код, например:

import cv2
import sys
import numpy as np

imgs_paths = sys.argv[1:]
imgs = list(map(cv2.imread, imgs_paths))
imgs_arr = np.array(imgs, dtype=np.float32)
imgs_max = np.max(imgs_arr, 0)

Я сделал следующее:

using Colors, Images

function im_to_array(im)
    img_array = permutedims(channelview(im), (2,3,1)) 
    img_array = Float32.(img_array)
    return img_array
end


imgs = map(Images.load, imgs_paths)
imgs_arr = map(im_to_array, imgs)
a = imgs_arr
b = reshape(cat(a..., dims=1), tuple(length(a), size(a[1])...))
imgs_max = maximum(b, dims=1)

Но это уродливо.

Я нашел более простой способ получить максимум (код ниже), но производительность ужасная. Может быть, это не то, что я ожидал.

function im_to_array(im)
    img_array = permutedims(channelview(im), (2,3,1)) 
    img_array = Float32.(img_array)
    return img_array
end

imgs = map(Images.load, imgs_paths)
imgs_arr = map(im_to_array, imgs)
imgs_max = max.(imgs_arr...)

Время работы первого метода на 120 изображениях FHD на моем ноутбуке составляет ~ 5 секунд. И я не могу определить время выполнения второго метода, потому что я ждал ~ 30 минут, и он не прекращался. Тестирую на Юлии 1.4.1

Есть ли лучший способ найти максимум нескольких изображений?

UPD : вот простой случай того, что я хотите:

a = [zeros(Int8, 8, 8, 3), zeros(Int8, 8, 8, 3), zeros(Int8, 8, 8, 3)] # 3 black images with shape 8x8
max.(a) #doesn't work
max.(a...) #works with this simple input but when I test it on 120 FHD images it's extremely slow 

UPD2 : Я тестировал оба метода на меньшем количестве изображений.

function max1(imgs_arr)
    a = imgs_arr
    b = reshape(cat(a..., dims=1), tuple(length(a), size(a[1])...))
    imgs_max = maximum(b, dims=1)
    return imgs_max
end

function max2(imgs_arr)
    return max.(imgs_arr...)
end
imgs_arr = my_imgs_arrays[1:5]

@time max1(imgs_arr)
@time max2(imgs_arr)

  0.247060 seconds (5.29 k allocations: 142.657 MiB)
  0.154158 seconds (44.85 k allocations: 26.388 MiB)
imgs_arr = my_imgs_arrays[1:15]

@time max1(imgs_arr)

@time max2(imgs_arr)

  0.600093 seconds (72.38 k allocations: 382.923 MiB)
  0.769446 seconds (1.24 M allocations: 71.374 MiB)

imgs_arr = my_imgs_arrays[1:25]

@time max1(imgs_arr)

@time max2(imgs_arr)

  1.057548 seconds (23.08 k allocations: 618.309 MiB)
  5.270050 seconds (151.52 M allocations: 2.329 GiB, 4.77% gc time)

Итак, больше изображений использую - медленнее работает.

Ответы [ 2 ]

6 голосов
/ 26 мая 2020

Похоже, вы хотите сделать попарное максимальное сокращение для нескольких изображений. Во-первых, вот функция для генерации случайных «изображений»:

rand_images(k, dims...) = [rand(UInt8, dims...) for _ = 1:k]

Я сгенерирую вектор из трех случайных изображений 10x12:

julia> images = rand_images(3, 10, 12)
3-element Array{Array{UInt8,2},1}:
 [0x51 0xdc … 0xf7 0x1e; 0xe1 0x10 … 0xd8 0x98; … ; 0x54 0x45 … 0x7a 0xaf; 0x7b 0xfc … 0x0a 0x81]
 [0xc8 0xa5 … 0xa8 0x81; 0x92 0x89 … 0x9f 0xbe; … ; 0x6a 0x03 … 0xb1 0xfd; 0x34 0xa9 … 0xa3 0x50]
 [0x26 0x9b … 0x2a 0x7c; 0x5c 0x7d … 0x8d 0x2b; … ; 0x32 0x1b … 0x57 0xdf; 0x96 0xa1 … 0x2a 0xc9]

Один из простых способов сделать это - сделайте попарное максимальное уменьшение:

julia> using BenchmarkTools

julia> @btime reduce(images) do a, b
           max.(a, b)
       end
  400.485 ns (2 allocations: 416 bytes)
10×12 Array{UInt8,2}:
 0xc8  0xdc  0x82  0xa7  0xa6  0xce  0xcd  0xb2  0x6e  0xba  0xf7  0x81
 0xe1  0x89  0x9f  0xeb  0x89  0xdf  0xd2  0xd2  0xab  0xea  0xd8  0xbe
 0xeb  0xdd  0x9e  0xe2  0xf5  0x4b  0xd2  0xe8  0xe4  0xf8  0xb9  0xf8
 0x63  0xa3  0xd7  0xea  0xf0  0x93  0xed  0xf7  0xfb  0xfb  0x9f  0xbb
 0xf2  0x51  0xf0  0xd4  0xfc  0xcf  0xf4  0xdd  0xeb  0xc3  0xe9  0xf9
 0xf8  0x72  0xfa  0x92  0x72  0xaa  0xa2  0xed  0xa1  0xdf  0xf1  0xd0
 0xef  0xe6  0x64  0xb3  0xd0  0x6a  0xce  0x9e  0x96  0xba  0xed  0xf9
 0xdb  0xc5  0x52  0xb3  0xf7  0xd1  0xdd  0xba  0xac  0xbc  0xd3  0xa1
 0x6a  0x45  0x88  0xda  0xf5  0xc6  0xcf  0x64  0xbc  0xf9  0xb1  0xfd
 0x96  0xfc  0xb1  0xc0  0xc4  0xcf  0x89  0xb4  0xe8  0xad  0xa3  0xc9

Это довольно быстро: 400 нс. Я бы выбрал время для изображений, размер которых сопоставим с тем, что вы делаете, но вы не упомянули размеры изображений, которые я могу видеть (код не зависит от данных, поэтому данные в изображениях не должны иметь значения).

При сокращении вычисляется максимальный срез, уменьшая его с изображением за раз, что может быть не самым быстрым способом сделать это. Кажется, что может быть быстрее вычислять каждый максимальный «пиксель» по одному для всех изображений, что немного сложнее, но также может быть выполнено:

function max_images(images::Vector{<:Array})
    M = copy(images[1])
    for i = 1:length(M)
        for j = 2:length(images)
            M[i] = max(M[i], images[j][i])
        end
    end
    return M
end

Это работает, но занимает 421 наносекунды, что медленнее, чем версия для уменьшения массива! Ой. Одна из причин заключается в том, что нет гарантии, что все изображения имеют одинаковый размер, поэтому во внутренней индексации l oop каждого изображения проверяются границы. Мы можем пропустить это на свой страх и риск, поместив аннотацию inbounds на @inbounds M[i] = max(M[i], images[j][i]). Таким образом, время сократилось до 282 нс. Есть немного больше скорости, которую можно получить, сообщив компилятору, что он может безопасно переупорядочить оба цикла, чтобы воспользоваться преимуществами параллелизма на уровне команд, поместив макрос @simd в каждый для l oop. Это сокращает время до 240 нс. Окончательная версия кода:

function max_images(images::Vector{<:Array})
    M = copy(images[1])
    @simd for i = 1:length(M)
        @simd for j = 2:length(images)
            @inbounds M[i] = max(M[i], images[j][i])
        end
    end
    return M
end
1 голос
/ 26 мая 2020

Я так привык избегать циклов в numpy, что забыл, что могу их использовать.

Я могу использовать простой l oop (max3 в коде ниже), как в @stefankarpinski ответ:

function max1(imgs_arr)
    a = imgs_arr
    b = reshape(cat(a..., dims=1), tuple(length(a), size(a[1])...))
    imgs_max = maximum(b, dims=1)
    return imgs_max
end

function max2(imgs_arr)
    return max.(imgs_arr...)
end

function max3(images::Vector{<:Array})
    M = copy(images[1])
    @simd for j = 2:length(images)
        M = max.(M, images[j])
    end
    return M
end

И это самый быстрый способ:

# typeof(my_imgs) is Array{Array{Float32,3},1}
# size(my_imgs[1]) is (1080, 1920, 3)

imgs_arr = my_imgs[1:20] 
@time max1(imgs_arr)
@time max2(imgs_arr)
@time max3(imgs_arr)

  0.656771 seconds (5.62 k allocations: 498.630 MiB)
  3.237826 seconds (118.20 M allocations: 1.784 GiB, 5.24% gc time)
  0.137279 seconds (40 allocations: 474.611 MiB)

Но max - простая функция, и мой вопрос остается для других функций, например median.

...