Краткое руководство
Если вы хорошо разбираетесь в 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
Не волнуйтесь, если в качестве результата вы получили не изображение. Это ожидаемо, поскольку он в первую очередь распознается как числовой массив, а не изображение. Позже в документации по 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] # отображаем синюю область
Как вы, возможно, знаете, изменение значения представления влияет на исходное изображение, тогда как изменение копии не влияет:
fill!(img_c, 1) # красная область на исходном изображении не меняется
fill!(img_v, 0) # синяя область меняется
Дополнительные пакеты расширяют возможности. Например, следующим образом:
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) # синий
Изображение — это всего лишь массив пиксельных объектов:
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)
Цветовые каналы не являются отдельным измерением массива изображения; вместо этого изображение представляет собой массив пикселей. Как видите, оба изображения img_rgb
и img_lab
имеют размер (а не или ).
Вместо типов |
Такой подход упрощает создание универсального кода, который может обрабатывать как изображения в оттенках серого, так и цветные изображения без необходимости введения дополнительных циклов или проверок цветового измерения. Он также обеспечивает более эффективную поддержку трехмерных изображений в оттенках серого, которые могут иметь размер 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)
Работать с изображениями лучше всего в представлении |
Мы используем порядок «канал-высота-ширина» (CHW) вместо HWC, поскольку это обеспечивает удобный с точки зрения памяти механизм индексирования для |
С помощью `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
Эта инфраструктура позволяет нам унифицировать «целочисленные» изображения и изображения с плавающей запятой и избежать потребности в специальных функциях преобразования (таких как |
Хотя это и не рекомендуется, но вы можете использовать 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