Документация Engee

Краткое руководство

Если вы хорошо разбираетесь в Julia или уже использовали другой пакет для обработки изображений, эта страница может помочь вам быстро приступить к работе. Если какие-либо термины или понятия покажутся вам странными, не волнуйтесь — в следующих разделах вы найдете гораздо более подробные пояснения.

Для начала загрузим пакет Images.jl:

julia> using Images

Изображения — это просто массивы

В большинстве случаев любой массив AbstractArray можно рассматривать как изображение. Например, числовой массив можно интерпретировать как изображение в оттенках серого.

julia> img = rand(4, 3)
4×3 Matrix{Float64}:
 0.517866   0.525796   0.374141
 0.749719   0.840641   0.728543
 0.552534   0.523948   0.00277646
 0.0834401  0.0128461  0.134685
OWQRvgAAAABJRU5ErkJg

Не волнуйтесь, если в качестве результата вы получили не изображение. Это ожидаемо, поскольку он в первую очередь распознается как числовой массив, а не изображение. Позже в документации по JuliaImages вы узнаете, как автоматически отображать массив как изображение.

Можно также выбрать интересующую нас область на более крупном изображении:

# создаем изображение, которое начинается с черного цвета в левом верхнем углу
# и становится ярче в правом нижнем углу
img = Array(reshape(range(0,stop=1,length=10^4), 100, 100))
# создаем копию
img_c = img[51:70, 21:70] # отображаем красную область
# создаем представление
img_v = @view img[16:35, 41:90] # отображаем синюю область
ZIL8z8Y8DHwyTkDEQAAAABJRU5ErkJg

Как вы, возможно, знаете, изменение значения представления влияет на исходное изображение, тогда как изменение копии не влияет:

fill!(img_c, 1) # красная область на исходном изображении не меняется
fill!(img_v, 0) # синяя область меняется
IoVwHsCwIAAAAASUVORK5C

Дополнительные пакеты расширяют возможности. Например, следующим образом:

using Unitful, AxisArrays
using Unitful: mm, s

img = AxisArray(rand(256, 256, 6, 50), (:x, :y, :z, :time), (0.4mm, 0.4mm, 1mm, 2s))

определяется 4-мерное изображение (3 пространственных измерения плюс одно временное) с указанными именем и физическим интервалом между пикселями для каждой координаты. Пакет AxisArrays поддерживает эффективные операции с такими массивами и может быть полезен для отслеживания не только расстояния между пикселями, но и принятой ориентации для многомерных изображений.

JuliaImages легко взаимодействует с AxisArrays и многими другими пакетами. Еще ряд примеров:

  • Пакет ImageMetadata (включенный в Images) позволяет «помечать» изображения пользовательскими метаданными.

  • Пакет IndirectArrays поддерживает индексированные изображения (цветовые карты).

  • Пакет MappedArrays позволяет представлять отложенные преобразования значений, облегчая работу с изображениями, которые могут не помещаться в память полностью.

  • Пакет ImageTransformations позволяет кодировать как немедленные, так и отложенные повороты, наклоны, деформации и т. д.

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

Элементы изображений — это цветовые составляющие пикселей

Элементы изображения называются пикселями, причем в JuliaImages пиксели рассматриваются как самостоятельные объекты. Например, это может быть Gray для пикселей в оттенках серого, RGB для пикселей цветов RGB, Lab для цветов Lab и т. д.

Чтобы создать пиксель, инициируйте структуру одного из этих типов:

Gray(0.0) # черный
Gray(1.0) # белый
RGB(1.0, 0.0, 0.0) # красный
RGB(0.0, 1.0, 0.0) # зеленый
RGB(0.0, 0.0, 1.0) # синий
KNxp5xB4Od4fBewi8Q+EdAiNxG4nbSNxG4jYSt5G4jcRtJG4jcRuJ20jcRuI2EreRuI3EbSRuI3EbidtI3EbiNhK3kbiNxG0kbiNxG4nbSNxG4jYSt5G4jcRtJG4jcRuJ20jcRuI2EreRuI3EbSRuI3EbidtI3Ebi9gGrpQW02AG0cQAAAABJRU5ErkJg

Изображение — это всего лишь массив пиксельных объектов:

julia> img_gray = rand(Gray, 2, 2)
2×2 Array{Gray{Float64},2} with eltype Gray{Float64}:
 Gray{Float64}(0.412225)   Gray{Float64}(0.755344)
 Gray{Float64}(0.0368978)  Gray{Float64}(0.198098)

julia> img_rgb = rand(RGB, 2, 2)
2×2 Array{RGB{Float64},2} with eltype RGB{Float64}:
 RGB{Float64}(0.709158,0.584705,0.186682)  …  RGB{Float64}(0.202959,0.435521,0.116792)
 RGB{Float64}(0.760013,0.693292,0.20883)      RGB{Float64}(0.0709577,0.888894,0.310744)

julia> img_lab = rand(Lab, 2, 2)
2×2 Array{Lab{Float64},2} with eltype Lab{Float64}:
 Lab{Float64}(14.7804,-55.7728,-50.3997)  …  Lab{Float64}(68.5718,-91.6112,127.303)
 Lab{Float64}(61.1431,62.8829,-55.5891)      Lab{Float64}(74.6444,-105.937,81.6293)
pHP+1eGPdxeGBakKkGqEqQqQaoSpCpBqhKkKkGqEqQqQaoSpCpBqhKkKkGqEqQqQaoSpCpBqhKkKkGqEqQqQaryB1t1Fg1tApEHAAAAAElFTkSuQmCC

Цветовые каналы не являются отдельным измерением массива изображения; вместо этого изображение представляет собой массив пикселей. Как видите, оба изображения img_rgb и img_lab имеют размер (а не или ).

Вместо типов Number рекомендуется использовать тип Gray, поскольку он указывает на то, что массив лучше всего интерпретировать как изображение в оттенках серого. Например, в средах Atom/Juno и Jupyter массив будет отображаться как изображение, а не числовая матрица. При использовании Gray вместо Number производительность не снижается.

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

Преобразование между цветами и отдельными цветовыми каналами

Преобразование между различными Colorant осуществляется очень просто:

julia> RGB.(img_gray) # Gray => RGB
2×2 Array{RGB{Float64},2} with eltype RGB{Float64}:
 RGB{Float64}(0.412225,0.412225,0.412225)     …  RGB{Float64}(0.755344,0.755344,0.755344)
 RGB{Float64}(0.0368978,0.0368978,0.0368978)     RGB{Float64}(0.198098,0.198098,0.198098)

julia> Gray.(img_rgb) # RGB => Gray
2×2 Array{Gray{Float64},2} with eltype Gray{Float64}:
 Gray{Float64}(0.576542)  Gray{Float64}(0.32965)
 Gray{Float64}(0.658013)  Gray{Float64}(0.578422)

В JuliaImages местами используется механизм трансляции. Если вы с ним не знакомы, обратитесь к документации.

Иногда для работы с другими пакетами может потребоваться преобразовать изображение RGB размером в числовой массив или наоборот. Для этого служат функции channelview и colorview. Например:

julia> img_CHW = channelview(img_rgb) # 3 * 2 * 2
3×2×2 reinterpret(reshape, Float64, ::Array{RGB{Float64},2}) with eltype Float64:
[:, :, 1] =
 0.709158  0.760013
 0.584705  0.693292
 0.186682  0.20883

[:, :, 2] =
 0.202959  0.0709577
 0.435521  0.888894
 0.116792  0.310744

julia> img_HWC = permutedims(img_CHW, (2, 3, 1)) # 2 * 2 * 3
2×2×3 Array{Float64, 3}:
[:, :, 1] =
 0.709158  0.202959
 0.760013  0.0709577

[:, :, 2] =
 0.584705  0.435521
 0.693292  0.888894

[:, :, 3] =
 0.186682  0.116792
 0.20883   0.310744

Чтобы преобразовать массив channelview в массив пикселей, убедитесь в том, что первое измерение — это цветовой канал, и используйте colorview:

julia> img_rgb = colorview(RGB, img_CHW) # 2 * 2
2×2 Array{RGB{Float64},2} with eltype RGB{Float64}:
 RGB{Float64}(0.709158,0.584705,0.186682)  …  RGB{Float64}(0.202959,0.435521,0.116792)
 RGB{Float64}(0.760013,0.693292,0.20883)      RGB{Float64}(0.0709577,0.888894,0.310744)

Работать с изображениями лучше всего в представлении colorview. Пользователям других языков может быть привычнее представление channelview, однако из-за преобразования изображения в обычный числовой массив теряется цветовая информация.

Мы используем порядок «канал-высота-ширина» (CHW) вместо HWC, поскольку это обеспечивает удобный с точки зрения памяти механизм индексирования для Array. По умолчанию в Julia первый индекс является самым быстрым (так как хранится в памяти рядом). Дополнительные сведения см. в рекомендации по производительности: Доступ к массивам в порядке их размещения в памяти (по столбцам).

С помощью `PermutedDimsArray` можно перевернуть ориентацию  блока памяти без создания копии, либо можно воспользоваться `permutedims`, если нужна  копия.

Для изображений типа Gray следующие фрагменты кода почти эквивалентны, за исключением того, что версия с созданием копирует данные, а версия с представлением — нет.

img_num = rand(4, 4)

img_gray_copy = Gray.(img_num) # создание
img_num_copy = Float64.(img_gray_copy) # создание

img_gray_view = colorview(Gray, img_num) # представление
img_num_view = channelview(img_gray_view) # представление

Шкала интенсивности 0—​1

В JuliaImages при отображении изображений по умолчанию предполагается, что 0 означает «черный», а 1 — «белый» (или «насыщенный» для изображения RGB).

Важно отметить, что это относится даже к уровням интенсивности, закодированным как 8-разрядные целые числа, которые сами по себе охватывают диапазон от 0 до 255 (а не от 0 до 1). Это возможно благодаря специальному числовому типу N0f8, который интерпретирует 8-разрядное «целое число» так, как если бы оно было масштабировано по 1/255, то есть значения от 0 до 1 кодируются в 256 шагов.

N0f8 означает «нормализованное (Normalized), с 0 битов для целой части и 8 битами для дробной части (8 fractional bits)». Есть и другие типы, например N0f16 для работы с 16-разрядными изображениями (и даже N2f14 для изображений, полученных с помощью 14-разрядной камеры, и т. д.).

julia> img_n0f8 = rand(N0f8, 2, 2)
2×2 reinterpret(N0f8, ::Matrix{UInt8}):
 0.435  0.725
 0.816  0.192

julia> float.(img_n0f8)
2×2 Matrix{Float32}:
 0.435294  0.72549
 0.815686  0.192157

Эта инфраструктура позволяет нам унифицировать «целочисленные» изображения и изображения с плавающей запятой и избежать потребности в специальных функциях преобразования (таких как im2double в MATLAB), которые изменяют значения пикселей, когда нужно лишь изменить тип (числовую точность и свойства) для представления пикселей.

Хотя это и не рекомендуется, но вы можете использовать rawview для получения базовых данных, хранящихся в памяти, и преобразовать их в UInt8 (или другой тип), если это непременно нужно.

julia> img_n0f8_raw = rawview(img_n0f8)
2×2 rawview(reinterpret(N0f8, ::Matrix{UInt8})) with eltype UInt8:
 0x6f  0xb9
 0xd0  0x31

julia> float.(img_n0f8_raw)
2×2 Matrix{Float64}:
 111.0  185.0
 208.0   49.0

Преобразование типа хранения, то есть фактического числового типа, без изменения типа цвета поддерживается следующими функциями:

julia> img = rand(Gray{N0f8}, 2, 2)
2×2 Array{Gray{N0f8},2} with eltype Gray{N0f8}:
 Gray{N0f8}(0.925)  Gray{N0f8}(0.267)
 Gray{N0f8}(0.345)  Gray{N0f8}(0.827)

julia> img_float32 = float32.(img) # Gray{N0f8} => Gray{Float32}
2×2 Array{Gray{Float32},2} with eltype Gray{Float32}:
 Gray{Float32}(0.92549)   Gray{Float32}(0.266667)
 Gray{Float32}(0.345098)  Gray{Float32}(0.827451)

julia> img_n0f16 = n0f16.(img_float32) # Gray{Float32} => Gray{N0f16}
2×2 Array{Gray{N0f16},2} with eltype Gray{N0f16}:
 Gray{N0f16}(0.92549)  Gray{N0f16}(0.26667)
 Gray{N0f16}(0.3451)   Gray{N0f16}(0.82745)

Если вы не хотите указывать конечный тип, на этот случай есть функция float:

julia> img_n0f8 = rand(Gray{N0f8}, 2, 2)
2×2 Array{Gray{N0f8},2} with eltype Gray{N0f8}:
 Gray{N0f8}(0.886)  Gray{N0f8}(0.98)
 Gray{N0f8}(0.522)  Gray{N0f8}(0.412)

julia> img_float = float.(img_n0f8) # Gray{N0f8} => Gray{Float32}
2×2 Array{Gray{Float32},2} with eltype Gray{Float32}:
 Gray{Float32}(0.886275)  Gray{Float32}(0.980392)
 Gray{Float32}(0.521569)  Gray{Float32}(0.411765)

Для преобразования наподобие представления без выделения новой памяти есть функция of_eltype из пакета MappedArrays:

julia> using MappedArrays
ERROR: ArgumentError: Package MappedArrays not found in current path.
- Run `import Pkg; Pkg.add("MappedArrays")` to install the MappedArrays package.

julia> img_float_view = of_eltype(Gray{Float32}, img_n0f8)
ERROR: UndefVarError: `of_eltype` not defined

julia> eltype(img_float_view)
ERROR: UndefVarError: `img_float_view` not defined

Массивы с произвольными индексами

Если над входным изображением выполняется какое-либо пространственное преобразование, каким образом пиксели (или вокселы) в преобразованном изображении будут сопоставляться с пикселями на входе? Благодаря поддержке в Julia массивов с индексами, начинающимися не с 1, можно сделать так, чтобы индексы массива представляли абсолютное положение в пространстве, что упрощает отслеживание одного и того же положения на нескольких изображениях. Дополнительные сведения см. в разделе Keeping track of location with unconventional indices.

Указатель функций

Дополнительные сведения о каждой из приведенных ниже категорий см. в разделе Сводка и справка по функциям. Приведенный ниже список доступен в REPL Julia по команде ?Images. Если ранее вы использовали другие фреймворки, вам также может быть интересен раздел Сравнение с другими фреймворками обработки изображений. Также можно обратиться к описанию пакетов ImageFeatures.jl и ImageSegmentation.jl, которые поддерживают ряд алгоритмов, важных для компьютерного зрения.

Конструкторы, преобразования и типажи:

  • Создание: используйте конструкторы специализированных пакетов, например AxisArray, ImageMeta и т. д.

  • «Преобразование»: colorview, channelview, rawview, normedview, PermutedDimsArray, paddedviews

  • Типажи: pixelspacing, sdims, timeaxis, timedim, spacedirections

Контраст и окрашивание:

  • clamp01, clamp01nan, scaleminmax, colorsigned, scalesigned

Алгоритмы:

  • Редукции: maxfinite, maxabsfinite, minfinite, meanfinite, sad, ssd, integral_image, boxdiff, gaussian_pyramid

  • Изменение размера и пространственные преобразования: restrict, imresize, warp

  • Фильтрация: imfilter, imfilter!, imfilter_LoG, mapwindow, imROF, padarray

  • Ядра фильтрации: Kernel. или KernelFactors., а затем ando[345], guassian2d, imaverage, imdog, imlaplacian, prewitt, sobel

  • Экспозиция: build_histogram, adjust_histogram, imadjustintensity, imstretch, imcomplement, AdaptiveEqualization, GammaCorrection, cliphist

  • Градиенты: backdiffx, backdiffy, forwarddiffx, forwarddiffy, imgradients

  • Обнаружение краев: imedge, imgradients, thin_edges, magnitude, phase, magnitudephase, orientation, canny

  • Обнаружение углов: imcorner, harris, shi_tomasi, kitchen_rosenfeld, meancovs, gammacovs, fastcorners

  • Обнаружение пятен: blob_LoG, findlocalmaxima, findlocalminima

  • Морфологические операции: dilate, erode, closing, opening, tophat, bothat, morphogradient, morpholaplace, feature_transform, distance_transform

  • Связанные компоненты: label_components, component_boxes, component_lengths, component_indices, component_subscripts, component_centroids

  • Интерполяция: bilinear_interpolation

Тестовые изображения и фантомы (см. также TestImages.jl):

  • shepp_logan