Работа с матрицами пикселей
Работа с матрицами пикселей
Pkg.add(["Images", "FileIO", "ImageShow"])
using Images, FileIO, ImageShow
Изображения с использованием пакета Images представляют собой многомерные массивы — по сути, матрицы, где каждый элемент содержит значение цвета (например, RGB). Такой подход позволяет легко манипулировать пикселями, используя стандартные операции работы с массивами: индексацию, срезы, поэлементные вычисления и т.д.
В этом примере мы решаем следующую задачу: загружаем произвольное изображение, делим его на равные вертикальные полосы, затем собираем отдельно полосы с нечётными номерами и отдельно с чётными, формируя два новых изображения. Наконец, выводим их рядом для визуального сравнения.
Этот пример наглядно демонстрирует:
- как загрузить и отобразить изображение;
- как получить его размеры;
- как рассчитать количество полос и их ширину с учётом остатка;
- как создать новые массивы (изображения) и заполнить их пикселями из исходного;
- как использовать циклы и условные операторы для обработки каждого пикселя.
Таким образом, мы осваиваем базовые приёмы работы с изображениями как с матрицами, что является фундаментом для более сложных алгоритмов компьютерного зрения и обработки графики, код написан с использованием пакетов Images, FileIO и ImageShow.
Шаг 1. Загрузка изображения и получение его характеристик
original_img = load("IMG.jpg")
display(original_img)
height, width = size(original_img)
println("height: $height, width: $width")
В этом блоке мы:
- Загружаем изображение из файла
IMG.jpgс помощью функцииloadиз пакетаFileIO. Результат сохраняется в переменнуюoriginal_img. Теперьoriginal_img— это многомерный массив (матрица), каждый элемент которого содержит цвет пикселя в форматеRGB(или оттенки серого, если изображение чёрно-белое). - Выводим изображение на экран с помощью
display. Это возможно благодаря пакетуImageShow, который обеспечивает отображение графических объектов. - Получаем размеры изображения:
height(высота, количество строк) иwidth(ширина, количество столбцов). Функцияsizeвозвращает кортеж, который мы распаковываем в две переменные. - Печатаем полученные значения в консоль с помощью
println. Это позволяет убедиться, что изображение загружено корректно, и узнать его разрешение.
Важно: для работы с цветными изображениями пакет Images автоматически представляет их в виде матрицы элементов типа RGB (или RGB4). Каждый такой элемент содержит поля r, g, b (красный, зелёный, синий) со значениями от 0 до 1. Таким образом, мы можем обращаться к отдельным пикселям, например original_img[100, 200] даст цвет пикселя в строке 100, столбце 200.
На этом этапе мы подготовили исходные данные для дальнейшей обработки.
Шаг 2. Определяем параметры полос
n_stripes = min(height, width)
base_width = div(width, n_stripes)
remainder = width % n_stripes
widths = [i <= remainder ? base_width + 1 : base_width for i in 1:n_stripes]
max_width = maximum(widths)
odd_stripes = []
even_stripes = []
start_col = 1;
Здесь мы готовим всё необходимое для разделения изображения на вертикальные полосы:
-
n_stripes = min(height, width)
Количество полос выбирается равным минимальному из размеров изображения. Это гарантирует, что полос не будет больше, чем строк или столбцов, и каждая полоса будет иметь достаточную ширину (в пикселях) для наглядности. -
base_width = div(width, n_stripes)
Вычисляем базовую ширину полосы как целочисленное деление общей ширины на количество полос. Так как ширина может не делиться нацело, часть полос получится на один пиксель шире. -
remainder = width % n_stripes
Остаток от деления — сколько полос будут иметь дополнительный пиксель. -
widths = [i <= remainder ? base_width + 1 : base_width for i in 1:n_stripes]
Создаём массивwidths, где для каждой полосыiуказана её ширина. Первыеremainderполос получают ширинуbase_width + 1, остальные —base_width. Таким образом, все полосы имеют почти одинаковую ширину, отличаясь максимум на 1 пиксель, что позволяет равномерно покрыть всю ширину изображения. -
max_width = maximum(widths)
Находим максимальную ширину среди всех полос. Это потребуется позже, когда мы будем создавать изображения для хранения полос: каждая полоса будет представлена прямоугольником одинаковой высоты (равной высоте исходного изображения) и одинаковой шириныmax_width, чтобы их можно было легко склеивать. Более узкие полосы будут дополнены чёрными пикселями справа. -
odd_stripes = []иeven_stripes = []
Инициализируем два пустых массива для хранения самих полос: вodd_stripesбудем складывать полосы с нечётными номерами, вeven_stripes— с чётными. -
start_col = 1
Переменная-счётчик, которая будет указывать на начало очередной полосы в исходном изображении при последующем проходе.
На этом этапе мы полностью подготовили данные для цикла извлечения полос: знаем, сколько их, какой они ширины, и готовы их разделять по чётности.
Шаг 3. Извлекаем полосы из исходного изображения
for i in 1:n_stripes
w = widths[i]
end_col = start_col + w - 1
stripe_rect = original_img[:, start_col:end_col]
stripe_img = similar(original_img, height, max_width)
fill!(stripe_img, RGB(0, 0, 0))
stripe_img[:, 1:w] = stripe_rect
if isodd(i)
push!(odd_stripes, stripe_img)
else
push!(even_stripes, stripe_img)
end
start_col += w
end
В этом цикле мы последовательно проходим по всем полосам слева направо и выполняем следующие действия:
-
Определяем границы полосы:
Используя текущее значениеstart_col(левый край) и ширинуw, вычисляемend_col— правый край полосы. Так мы знаем, какой диапазон столбцов исходного изображения соответствует i-й полосе. -
Вырезаем прямоугольник:
stripe_rect = original_img[:, start_col:end_col]— берём все строки (:) и нужные столбцы. Получается матрица размеромheight × w, содержащая пиксели данной полосы. -
Создаём стандартизированный прямоугольник:
stripe_img = similar(original_img, height, max_width)создаёт новую матрицу (изображение) такого же типа, как исходное, но размеромheight × max_width. Это нужно, чтобы все полосы имели одинаковую ширину — тогда их будет легко склеивать в итоговые изображенияimg1иimg2.
Затемfill!(stripe_img, RGB(0,0,0))заполняет этот прямоугольник чёрным цветом (все компоненты равны 0). -
Копируем пиксели полосы:
stripe_img[:, 1:w] = stripe_rect— вставляем вырезанный фрагмент в левую часть созданного прямоугольника. Если полоса была уже, чемmax_width, справа останутся чёрные пиксели (фон). -
Сортируем по чётности:
С помощьюisodd(i)проверяем номер полосы. Нечётные полосы добавляем в массивodd_stripes, чётные — вeven_stripes. Так мы разделяем полосы на две группы для последующего создания двух отдельных изображений. -
Сдвигаем начало:
start_col += w— увеличиваем счётчик на ширину только что обработанной полосы, чтобы в следующей итерации начать с того места, где закончилась текущая.
В результате после цикла мы имеем два массива, каждый из которых содержит набор матриц (полос) одинакового размера height × max_width, причём все пиксели из исходного изображения распределены по этим полосам в соответствии с их порядком.
Шаг 4. Формируем два изображения из полос разной чётности
img1 = similar(original_img, height, max_width * length(odd_stripes))
img2 = similar(original_img, height, max_width * length(even_stripes))
fill!(img1, RGB(0, 0, 0))
fill!(img2, RGB(0, 0, 0))
col_offset = 1
for stripe in odd_stripes
w = size(stripe, 2)
img1[:, col_offset:col_offset+w-1] = stripe
col_offset += w
end
col_offset = 1
for stripe in even_stripes
w = size(stripe, 2)
img2[:, col_offset:col_offset+w-1] = stripe
col_offset += w
end
На этом этапе мы объединяем отдельные полосы в два цельных изображения:
-
Создание холстов:
img1иimg2создаются с помощьюsimilar, что гарантирует тот же тип пикселей, что и у исходного изображения. Их высота равна высоте оригинала (height), а ширина вычисляется какmax_width(максимальная ширина полосы), умноженная на количество полос соответствующей группы.
Например, если у нас 10 полос, то вodd_stripesбудет 5 полос (нечётные), значит ширинаimg1=max_width * 5.
Затем оба изображения заполняются чёрным цветом (RGB(0,0,0)) — это будет фон, на который мы будем накладывать пиксели полос. -
Сборка изображения из нечётных полос:
Переменнаяcol_offsetотслеживает текущую позицию (столбец) в итоговом изображении, начиная с 1. В цикле по всем полосам изodd_stripesмы:- определяем фактическую ширину текущей полосы
w(она равнаmax_width, так как все полосы были дополнены до этого размера); - вставляем матрицу полосы
stripeв соответствующий диапазон столбцовimg1[:, col_offset:col_offset+w-1]; - увеличиваем
col_offsetнаw, переходя к месту для следующей полосы.
- определяем фактическую ширину текущей полосы
-
Сборка изображения из чётных полос:
Аналогичный цикл выполняется дляeven_stripes, формируяimg2.
В результате мы получаем два изображения, каждое из которых представляет собой склеенные по горизонтали полосы исходной картинки:
img1содержит полосы 1, 3, 5, … (нечётные);img2содержит полосы 2, 4, 6, … (чётные).
Обратите внимание: поскольку все полосы были приведены к единой ширине max_width, в img1 и img2 между фрагментами нет промежутков — они расположены вплотную. Чёрные области справа от более узких полос (если исходная ширина полосы была меньше max_width) сохраняются, поэтому на стыках могут быть вертикальные чёрные полоски — это артефакт выравнивания, но он не мешает дальнейшему сравнению.
Шаг 5. Объединяем два изображения для сравнения и выводим результат
h1, w1 = size(img1)
h2, w2 = size(img2)
max_h = max(h1, h2)
side_by_side = similar(img1, max_h, w1 + w2 + 10)
fill!(side_by_side, RGB(1, 1, 1))
side_by_side[1:h1, 1:w1] = img1
side_by_side[1:h2, w1+11:w1+w2+10] = img2
display(side_by_side)
println("height_img1: $h1, height_img2: $h2")
println("width_img1: $w1, width_img2: $w2")
На этом завершающем этапе мы:
-
Получаем размеры сформированных изображений:
h1, w1 = size(img1)иh2, w2 = size(img2). Оба изображения имеют одинаковую высоту (heightисходной картинки), но ширина может различаться, если количество нечётных и чётных полос отличается (например, при нечётном общем количестве полос). В нашем примере оба оказались по 980 пикселей, потому что исходная ширина 1960, количество полос 980, и нечётных/чётных поровну. -
Создаём общий холст
side_by_side:
max_h = max(h1, h2)— выбираем максимальную высоту (они равны, но код универсален).
side_by_side = similar(img1, max_h, w1 + w2 + 10)— создаём изображение-холст такого же типа, какimg1, высотойmax_hи шириной, равной сумме ширин двух изображений плюс 10 пикселей промежутка.
fill!(side_by_side, RGB(1,1,1))— заливаем холст белым цветом (все компоненты равны 1). Белый фон создаёт визуальный разделитель между двумя картинками. -
Размещаем изображения на холсте:
side_by_side[1:h1, 1:w1] = img1— копируемimg1в левую часть холста (строки 1:h1, столбцы 1:w1).
side_by_side[1:h2, w1+11:w1+w2+10] = img2— вставляемimg2справа, начиная с позицииw1+11(оставляем 10 пикселей белого промежутка послеimg1, плюс один пиксель индексации — поэтому+11). Диапазон столбцов захватывает ровноw2столбцов. -
Отображаем результат:
display(side_by_side)показывает итоговое составное изображение, где слева — полосы с нечётными номерами, справа — с чётными. Белая полоса между ними позволяет легко визуально разделить картинки. -
Выводим размеры в консоль:
printlnсообщает нам точные размеры полученных изображений. Это полезно для проверки и понимания того, как исходное изображение было преобразовано.
Вывод
Мы прошли полный путь от загрузки изображения до визуализации результата его необычной «разрезки». Этот пример наглядно показывает, как работать с изображениями воспренимая их как обычные матрицы: извлекать фрагменты, создавать новые массивы, заполнять их пикселями и склеивать. Такой подход лежит в основе многих задач компьютерного зрения и обработки графики — от простого кропа до сложных фильтров и преобразований.
Интересный факт: Несмотря на то, что мы разделили исходное изображение на две группы полос (нечётные и чётные), визуально новые изображения выглядят идентично, это связанно с исходной симметрией изображения.

