Сообщество Engee

День 3

Автор
avatar-igarajaigaraja
Notebook

План занятия

  1. DataFrames
  2. Импорт данных
  3. Чтение и запись файлов
  4. Обработка изображений
  5. Логирование данных в формате gif
  6. Перенос кода из MATLAB
  7. Проект

Установка пакетов

In [ ]:
# Pkg.add(["CSV", "MAT", "DataFrames", "CSV", "MATLAB", "MAT", "FileIO", "EzXML", "ImageShow", "Images", "ImageMorphology", "Noise", "ImageBinarization", " "ImageDraw", "TickTock"])

DataFrames

Пакет DataFrames.jl предоставляет инструментарий для работы с
табличными данными в Julia. Это отличное и
универсальное средство для анализа и обработки данных.

Пакет DataFrames.jl занимает центральное место в экосистеме
Julia для работы с данными и тесно интегрирован с рядом
других библиотек.

In [ ]:
using DataFrames

data = DataFrame(pet=["cat", "dog", "dog", "fish", "cat", missing, "cat", "fish"],
                 children=[4., 6, 3, 3, 2, 3, 5, 4],
                 salary=[90, 24, 44, 27, 32, 59, 36, 27])
Out[0]:
8×3 DataFrame
Rowpetchildrensalary
String?Float64Int64
1cat4.090
2dog6.024
3dog3.044
4fish3.027
5cat2.032
6missing3.059
7cat5.036
8fish4.027
In [ ]:
data[1,2]

Сортировка столбца

In [ ]:
sorted_data = sort(data, :salary) #  По возрастанию значений в столбце
Out[0]:
8×3 DataFrame
Rowpetchildrensalary
String?Float64Int64
1dog6.024
2fish3.027
3fish4.027
4cat2.032
5cat5.036
6dog3.044
7missing3.059
8cat4.090

Сортировка по двум столбцам

In [ ]:
sorted_data = sort(data, [:children, :salary])
Out[0]:
8×3 DataFrame
Rowpetchildrensalary
String?Float64Int64
1cat2.032
2fish3.027
3dog3.044
4missing3.059
5fish4.027
6cat4.090
7cat5.036
8dog6.024

Обращение к строке

In [ ]:
row_3 = data[3, 2]
Out[0]:
3.0

Убрать строки с missing

In [ ]:
filtered_data = dropmissing(data)
Out[0]:
7×3 DataFrame
Rowpetchildrensalary
StringFloat64Int64
1cat4.090
2dog6.024
3dog3.044
4fish3.027
5cat2.032
6cat5.036
7fish4.027

Удалить целый столбец

In [ ]:
selected_data = select!(data, Not(:children))
Out[0]:
8×2 DataFrame
Rowpetsalary
String?Int64
1cat90
2dog24
3dog44
4fish27
5cat32
6missing59
7cat36
8fish27

Компиляция строк

Функция parse в Julia используется для преобразования строкового представления данных в соответствующий числовой или другой базовый тип.

In [ ]:
num1 = parse(Int, "5")   
num2 = parse(Float64, "1.23")  
print("Результаты: $(num1+num2)")
Результаты: 6.23

Функция Meta.parse в Julia используется для преобразования строки, содержащей выражение или вызов функции, в объект типа Expr (выражение Julia).

Важное отличие от parse:

  1. parse преобразует строки только в конечные значения заданного типа, такие, как числа, логические значения или символы. Оно не поддерживает сложные выражения или вызовы функций.
  2. Meta.parse работает исключительно с кодом, представленным в виде строки, и преобразует его в объект Expr, который можно анализировать или передать для выполнения.
    Пример использования:
In [ ]:
cmd = Meta.parse.("1+1")
Out[0]:
:(1 + 1)

Функция eval в Julia используется для выполнения выражений, представленных в виде объектов типа Expr (абстрактное синтаксическое дерево) или других валидных выражений. Она позволяет динамически запускать код, что делает её мощным инструментом для задач метапрограммирования, но требует осторожного использования из-за потенциальных рисков выполнения нежелательного кода.

In [ ]:
eval(cmd)
Out[0]:
2

Далее рассмотрим пример с CSV-файлом. Для этого подгрузим дополнительные библиотеки.

In [ ]:
using CSV

Выполним чтение CSV.

In [ ]:
DataFrameCSV = CSV.read("$(@__DIR__)/Resources/data_eval.csv", DataFrame)
Out[0]:
9×1 DataFrame
RowTime_and_Ampl
String31
1-1.08,-96.6667
2-1.0799,-126.667
3-1.0798,-143.333
4-1.0797,-166.667
5-1.0796,-176.667
6-1.0795,-166.667
7-1.0794,-146.667
8-1.0793,-126.667
9-1.0792,-96.6667

Выделим из полученного DataFrame столбец с данными.

In [ ]:
Data = DataFrameCSV.Time_and_Ampl
Out[0]:
9-element Vector{String31}:
 "-1.08,-96.6667"
 "-1.0799,-126.667"
 "-1.0798,-143.333"
 "-1.0797,-166.667"
 "-1.0796,-176.667"
 "-1.0795,-166.667"
 "-1.0794,-146.667"
 "-1.0793,-126.667"
 "-1.0792,-96.6667"

Как видно, мы получили вектор из строк. А теперь добавим к каждой строке квадратные скобки для того, чтобы получить набор команд на добавления элементов в вектор, и выполним эти операции.

In [ ]:
DataVec = eval.(Meta.parse.("[".*Data.*"]"))
Out[0]:
9-element Vector{Vector{Float64}}:
 [-1.08, -96.6667]
 [-1.0799, -126.667]
 [-1.0798, -143.333]
 [-1.0797, -166.667]
 [-1.0796, -176.667]
 [-1.0795, -166.667]
 [-1.0794, -146.667]
 [-1.0793, -126.667]
 [-1.0792, -96.6667]

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

Следующий пример, который мы рассмотрим, – это исполнение TXT-файла.

In [ ]:
txt = open(io->read(io, String), "$(@__DIR__)/Resources/data_eval.txt") # Читаем TXT
Out[0]:
"1.0\n15.726465174717665\n118.19779715124804\n564.5945977782826\n1922.5628210505968\n4961.486649014338\n10069.27911225147\n16457.781390988064\n22003.338138220104\n24301.413576125095\n22293.787678765948\n17018.378514886626\n10791.5437594143\n5653.533013094259\n2423.11034196715\n836.6005210914026\n227.22904721973316\n46.794190857389\n6.873719057490497\n0.6421978090261691\n0.028701721170992484"

Как видно, мы получили строку с числами и переходами на новую строку в текстовом документе после каждого значения. Далее заменяем \n на запятые и оборачиваем строку в [].

In [ ]:
data_str = "[" * replace(txt, "\n" => ",") * "]"
Out[0]:
"[1.0,15.726465174717665,118.19779715124804,564.5945977782826,1922.5628210505968,4961.486649014338,10069.27911225147,16457.781390988064,22003.338138220104,24301.413576125095,22293.787678765948,17018.378514886626,10791.5437594143,5653.533013094259,2423.11034196715,836.6005210914026,227.22904721973316,46.794190857389,6.873719057490497,0.6421978090261691,0.028701721170992484]"

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

In [ ]:
data_vector = eval(Meta.parse(data_str))
Out[0]:
21-element Vector{Float64}:
     1.0
    15.726465174717665
   118.19779715124804
   564.5945977782826
  1922.5628210505968
  4961.486649014338
 10069.27911225147
 16457.781390988064
 22003.338138220104
 24301.413576125095
 22293.787678765948
 17018.378514886626
 10791.5437594143
  5653.533013094259
  2423.11034196715
   836.6005210914026
   227.22904721973316
    46.794190857389
     6.873719057490497
     0.6421978090261691
     0.028701721170992484

В результате мы получили вектор из 21 значения, с которым можем дальше взаимодействовать как с обычным вектором, например, округлить его элементы до целых чисел.

In [ ]:
plot(eval(Meta.parse("round.(data_vector)")))
Warning: attempting to remove probably stale pidfile
  path = "/user/.jlassetregistry.lock"
@ Pidfile /usr/local/ijulia-core/packages/Pidfile/DDu3M/src/Pidfile.jl:260
Out[0]:

Создание в цикле набор переменных по некоторому правилу.

In [ ]:
for i=1:3, j=1:3, s='a':'c' 
    v = Symbol("$(s)_$(i)_$(j)")
    @eval $v = 10*$i + $j
end
b_2_3
Out[0]:
23

Чтение и запись данных в различные типы файлов

In [ ]:
path = "$(@__DIR__)/"
Out[0]:
"/user/Winter_school/"

CSV

In [ ]:
df = DataFrame(rand(10, 10), :auto)
Out[0]:
10×10 DataFrame
Rowx1x2x3x4x5x6x7x8x9x10
Float64Float64Float64Float64Float64Float64Float64Float64Float64Float64
10.05645730.382050.9370950.100440.7441060.01984410.7539160.2712270.6906830.50977
20.02488830.5760970.2040070.2953640.7263010.6920020.6586510.3633350.621280.458824
30.1566630.9561680.1660020.3938910.4116090.2798830.4333860.6906920.1297840.884186
40.4378850.00473810.4860910.6088620.4212620.6204260.5045170.002788480.1622790.390929
50.8525120.004224320.8446350.1951220.8486950.2376670.2075310.4546520.5291820.358245
60.8347250.8134920.5592130.9946010.1071070.4948710.5758840.8813210.6811030.694399
70.4573490.535830.7588720.5250470.9754470.4129330.4746880.1818120.4090770.623677
80.8175030.1054750.9027010.0300560.04071940.721270.4849290.927150.07206040.203886
90.01316110.4180240.3693890.9775130.4425950.9452970.6190530.4450240.5320390.0540346
100.8789460.964050.2685820.08325670.245140.05102960.4209860.008093340.8847530.81559
In [ ]:
CSV.write(path * "Resources/data.csv", df)
Out[0]:
"/user/Winter_school/Resources/data.csv"
In [ ]:
Matrix(CSV.read(path * "Resources/data.csv", DataFrame))
Out[0]:
10×10 Matrix{Float64}:
 0.0564573  0.38205     0.937095  …  0.271227    0.690683   0.50977
 0.0248883  0.576097    0.204007     0.363335    0.62128    0.458824
 0.156663   0.956168    0.166002     0.690692    0.129784   0.884186
 0.437885   0.0047381   0.486091     0.00278848  0.162279   0.390929
 0.852512   0.00422432  0.844635     0.454652    0.529182   0.358245
 0.834725   0.813492    0.559213  …  0.881321    0.681103   0.694399
 0.457349   0.53583     0.758872     0.181812    0.409077   0.623677
 0.817503   0.105475    0.902701     0.92715     0.0720604  0.203886
 0.0131611  0.418024    0.369389     0.445024    0.532039   0.0540346
 0.878946   0.96405     0.268582     0.00809334  0.884753   0.81559

TXT

In [ ]:
write(path * "Resources/data.txt", "Пример")
Out[0]:
12
In [ ]:
open(io->read(io, String), path * "Resources/data.txt")
Out[0]:
"Пример"

MAT

In [ ]:
using MATLAB, MAT
In [ ]:
mat"p = genpath($path); addpath(p);"
In [ ]:
a = [1 2 3]
mat"a=$a"
mat"save($path + string('Resources/data.mat'),'a')"
In [ ]:
b = matopen(path * "Resources/data.mat")
read(b, "a")
Out[0]:
1×3 Matrix{Int64}:
 1  2  3

JLD2

In [ ]:
using FileIO
In [ ]:
save(path * "Resources/example.jld2", Dict("A" => "test", "B" => 12))
In [ ]:
aaa = load(path * "Resources/example.jld2")
Out[0]:
Dict{String, Any} with 2 entries:
  "B" => 12
  "A" => "test"
In [ ]:
aaa["B"]
Out[0]:
12
In [ ]:
b = load(path * "Resources/example.jld2", "B")
Out[0]:
12

bin

In [ ]:
open("Resources/file.bin", "w") do io
    write(io, Int32(12))   # Запишем целое число типа Int32
    write(io, Float64(3.14))  # Запишем вещественное число типа Float64
    write(io, "Test\n")  # Запишем строку
end
Out[0]:
5
In [ ]:
open("Resources/file.bin", "r") do io
    num_int = read(io, Int32)  # Считаем целое число типа Int32
    num_float = read(io, Float64)  # Считаем вещественное число типа Float64
    str = String(read(io))  # Считаем оставшуюся часть файла как строку

    println("Целое число: ", num_int)
    println("Вещественное число: ", num_float)
    print("Прочитанная строка: ", str)
end
Целое число: 12
Вещественное число: 3.14
Прочитанная строка: Test

xml

In [ ]:
using EzXML
In [ ]:
using EzXML
doc = parsexml("""
<primates>
    <genus name="Homo">
        <species name="sapiens">Human</species>
    </genus>
    <genus name="Pan">
        <species name="paniscus">Bonobo</species>
        <species name="troglodytes">Chimpanzee</species>
    </genus>
</primates>
""")
# Сохраняем документ в файл
write("Resources/file.xml", doc)
Out[0]:
290
In [ ]:
open("Resources/file.xml", "r") do io
    str = String(read(io))
    println(str)
end
<?xml version="1.0" encoding="UTF-8"?>
<primates>
    <genus name="Homo">
        <species name="sapiens">Human</species>
    </genus>
    <genus name="Pan">
        <species name="paniscus">Bonobo</species>
        <species name="troglodytes">Chimpanzee</species>
    </genus>
</primates>

Типы и трансформация изображений

Цель данной демонстрации – показать способы задания изображений и базовые принципы их преобразования.

In [ ]:
using Images # Библиотека обработки изображений
using ImageShow # Библиотека отрисовки изображений

Типы цветовых пространств

Любое изображение – это просто массив пиксельных объектов. Элементы изображения называются пикселями, а Julia Images рассматривает пиксели как первоклассные объекты. Например, у нас есть Gray-пиксели в оттенках серого, RGB-пиксели цвета, Lab-пиксели цвета.

In [ ]:
img_rgb = [RGB(1.0, 0, 0.0), RGB(0.0, 1.0, 0), RGB(0.0, 0.0, 1.0)]
Out[0]:
No description has been provided for this image
In [ ]:
dump(img_rgb)
Array{RGB{Float64}}((3,))
  1: RGB{Float64}
    r: Float64 1.0
    g: Float64 0.0
    b: Float64 0.0
  2: RGB{Float64}
    r: Float64 0.0
    g: Float64 1.0
    b: Float64 0.0
  3: RGB{Float64}
    r: Float64 0.0
    g: Float64 0.0
    b: Float64 1.0
In [ ]:
img_gray = rand(Gray, 3, 3)
Out[0]:
No description has been provided for this image
In [ ]:
dump(img_gray)
Array{Gray{Float64}}((3, 3))
  1: Gray{Float64}
    val: Float64 0.3721701442786429
  2: Gray{Float64}
    val: Float64 0.8622258538257974
  3: Gray{Float64}
    val: Float64 0.07049907443882875
  4: Gray{Float64}
    val: Float64 0.47496871917172834
  5: Gray{Float64}
    val: Float64 0.2252223485429724
  6: Gray{Float64}
    val: Float64 0.565220271526039
  7: Gray{Float64}
    val: Float64 0.5465868668761524
  8: Gray{Float64}
    val: Float64 0.09580064016498291
  9: Gray{Float64}
    val: Float64 0.030602784342960265

Перевод между типами объектов

In [ ]:
dump(Gray.(img_rgb)) # RGB => Gray
Array{Gray{Float64}}((3,))
  1: Gray{Float64}
    val: Float64 0.29899999499320984
  2: Gray{Float64}
    val: Float64 0.5870000123977661
  3: Gray{Float64}
    val: Float64 0.11400000005960464
In [ ]:
RGB.(img_gray) # Gray => RGB
Out[0]:
No description has been provided for this image

Трансформация изображений

Для начала загрузим изображение из файла .jpg.

In [ ]:
img = load( "$(@__DIR__)/Resources/img.jpg" )
Out[0]:
No description has been provided for this image

Увеличим контрастность загруженного изображения.

In [ ]:
alg = Equalization(nbins = 256)
img_adjusted = adjust_histogram(img, alg)
Out[0]:
No description has been provided for this image

Уменьшим размер изображения в четыре раза относительно исходника.

In [ ]:
img_small = imresize(img_adjusted, ratio=1/4)
Out[0]:
No description has been provided for this image

Также imresize позволяет изменять размер с использованием ручного задания размерностей, например:

imresize(img, (400, 400)).

In [ ]:
print(size(img_adjusted), " --> ", size(img_small))
(982, 1120) --> (246, 280)

Аффинное преобразование – отображение плоскости
или пространства в себя.

Преобразование задаётся матрицей трансформации изображения по принципу, описанному на картинке ниже.
image.png

Далее объявим функцию аффинной трансформации изображения, в которой:

  1. theta – это матрица трансформации;
  2. img – это входное изображение;
  3. out_size – размеры выходного изображения;
  4. grid – решётка пиксельной индексации.
In [ ]:
# Вспомогательная функция контроля размерностей
function C_B_V(x, max_val)
    x[x .> max_val - 1] .= max_val - 1
    x[x .< 1] .= 1
    return x
end

function transform(theta, img, out_size)
    grid = grid = zeros(3, out_size[1]*out_size[2])
    grid[1, :] = reshape(((-1:2/(out_size[1]-1):1)*ones(1,out_size[2])), 1, size(grid,2))
    grid[2, :] = reshape((ones(out_size[1],1)*(-1:2/(out_size[2]-1):1)'), 1, size(grid,2))
    grid[3, :] = ones(Int, size(grid, 2))

    # Умножение theta на grid
    T_g = theta * grid

    # Вычисление координат x, y
    x = (T_g[1, :] .+ 1) .* (out_size[2]) / 2
    y = (T_g[2, :] .+ 1) .* (out_size[1]) / 2

    # Округление координат
    x0 = ceil.(x)
    x1 = x0 .+ 1
    y0 = ceil.(y)
    y1 = y0 .+ 1

    # Обрезание значений x0, x1, y0, y1
    x0 = C_B_V(x0, out_size[2])
    x1 = C_B_V(x1, out_size[2])
    y0 = C_B_V(y0, out_size[1])
    y1 = C_B_V(y1, out_size[1])

    # Вычисление базовых координат
    base_y0 = y0 .* out_size[1]
    base_y1 = y1 .* out_size[1]

    # Работа с изображением
    im_flat = reshape(img, :)

    # Обрабатываем координаты
    A = (x1 .- x) .* (y1 .- y) .* im_flat[Int.(base_y0 .+ x0 .+ 1)]
    B = (x1 .- x) .* (y .- y0) .* im_flat[Int.(base_y1 .+ x0 .+ 1)]
    C = (x .- x0) .* (y1 .- y) .* im_flat[Int.(base_y0 .+ x1 .+ 1)]
    D = (x .- x0) .* (y .- y0) .* im_flat[Int.(base_y1 .+ x1 .+ 1)]

    # Расчет результата
    result = reshape((A .+ B .+ C .+ D), (out_size[1], out_size[2]))
    return result
end
Out[0]:
transform (generic function with 1 method)

Для начала применим эту функцию к изображению в оттенках серого.

In [ ]:
img_sg = Gray.(img_small)
theta = [2 0.3 0; -0.3 2 0]

img_transfor = transform(theta, img_sg, [size(img_sg,1),size(img_sg,2)])
Out[0]:
No description has been provided for this image

Как мы видим, размер уменьшен в два раза и выполнен поворот.
Для получения обратного преобразования находим обратную
матрицу от матрицы трансформации. Это можно сделать при
помощи функции inv.

Теперь применим данную функцию к изображению формата RGB.
Для начала преобразуем изображения формата RGB к канальному
представлению.

In [ ]:
img_CHW = channelview(img_small);
print(size(img_small), " --> ", size(img_CHW))
(246, 280) --> (3, 246, 280)
In [ ]:
RGB.(img_CHW[1,:,:], 0.0, 0.0) # red
Out[0]:
No description has been provided for this image
In [ ]:
RGB.(img_CHW[1,:,:], img_CHW[1,:,:], img_CHW[1,:,:]) # Gray
Out[0]:
No description has been provided for this image
In [ ]:
 
In [ ]:
img_CHW_new = zeros(size(img_CHW))

for i in 1:size(img_CHW,1)
   img_CHW_new[i,:,:] = transform(theta, img_CHW[i,:,:], [size(img_CHW,2),size(img_CHW,3)])
   # img_CHW_new = similar(img_CHW,Float64)
end 
RGB.(img_CHW_new[1,:,:], img_CHW_new[2,:,:], img_CHW_new[3,:,:])
#  RGB.(map(i -> img_CHW_new[i,:,:],1:3)...) 
Out[0]:
No description has been provided for this image

Морфологические операции

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

In [ ]:
using ImageMorphology

img = Gray.(img)
Out[0]:
No description has been provided for this image

Эрозия

Функция erode выполняет минимальную фильтрацию по ближайшим соседям. По умолчанию это 8 связей в 2d, 27 связей в 3d и т.д. Вы можете указать список измерений, которые хотите включить в связность, например, region = [1,2], чтобы исключить третье измерение из фильтрации.

Ниже приведен пример того, как эрозия работает на бинарном изображении с использованием 3x3 структурирующего элемента.

image.png

In [ ]:
img_erode = @. Gray(img < 0.8); # keeps white objects white
img_erosion1 = erode(img_erode)
img_erosion2 = erode(erode(img_erode))
mosaicview(img_erode, img_erosion1, img_erosion2; nrow = 1)
Out[0]:
No description has been provided for this image

Расширение

Функция dilate выполняет максимизирующую фильтрацию по своим ближайшим соседям. По умолчанию работает так же, как erode.

image.png

In [ ]:
img_dilate = @. Gray(img > 0.9);
img_dilate1 = dilate(img_dilate)
img_dilate2 = dilate(dilate(img_dilate))
mosaicview(img_dilate, img_dilate1, img_dilate2; nrow = 1)
Out[0]:
No description has been provided for this image

Открытие

Операция морфологии открытия эквивалентна dilate(erode(img)). В функции opening(img, [region]) region позволяет контролировать размеры, над которыми выполняется эта операция.
Открытие может удалять небольшие яркие пятна и соединять небольшие темные трещины.

In [ ]:
img_opening = @. Gray(1 * img > 0.5);
img_opening1 = opening(img_opening)
img_opening2 = opening(opening(img_opening))
mosaicview(img_opening, img_opening1, img_opening2; nrow = 1)
Out[0]:
No description has been provided for this image

Закрытие

Закрытие операции морфологии эквивалентно erode(dilate(img)). Функция closing(img, [region]) работает аналогично opening. Закрытие может удалить небольшие темные пятна и соединить небольшие светлые трещины.

In [ ]:
img_closing = @. Gray(1 * img > 0.5);
img_closing1 = closing(img_closing)
img_closing2 = closing(closing(img_closing))
mosaicview(img_closing1, img_closing1, img_closing2; nrow = 1)
Out[0]:
No description has been provided for this image

Top-hat

Функция tophat определяется как изображение за вычетом его морфологического открытия. tophat(img, [region]) работает аналогично opening и closing. Эта операция возвращает яркие пятна изображения, которые меньше структурирующего элемента.

In [ ]:
img_tophat = @. Gray(1 * img > 0.2);
img_tophat1 = tophat(img_tophat)
img_tophat2 = tophat(tophat(img_tophat))
mosaicview(img_tophat, img_tophat1, img_tophat2; nrow = 1)
Out[0]:
No description has been provided for this image

Bottom-Hat

Операция морфологии Bottom-Hat определяется как морфологическое закрытие изображения за вычетом исходного изображения. bothat(img, [region]) определяется аналогично tophat. Эта операция возвращает темные пятна изображения, которые меньше структурирующего элемента.

In [ ]:
img_bothat = @. Gray(1 * img > 0.5);
img_bothat1 = bothat(img_tophat)
img_bothat2 = bothat(bothat(img_tophat))
img_bothat3 = bothat(bothat(bothat(img_tophat)))
mosaicview(img_bothat, img_bothat1, img_bothat2; nrow = 1)
Out[0]:
No description has been provided for this image

Градиент морфологии

Морфологический градиент является разницей между расширением и эрозией изображения. morphogradient(img, [region]) определяется так же, как и tophat.

In [ ]:
img_gray = @. Gray(0.8 * img > 0.7);
img_morphograd = morphogradient(@. Gray(0.8 * img > 0.4))
mosaicview(img_gray, img_morphograd; nrow = 1)
Out[0]:
No description has been provided for this image

Морфологический оператор Лапласа

Морфологический оператор Лапласа возращает значение, которое определяется как арифметическая разность между внутренним и внешним градиентом. Функция morpholaplace(img, [region]) определяется аналогично tophat.

In [ ]:
img_gray = @. Gray(0.8 * Gray.(img) > 0.7);
img_morpholap = morpholaplace(img_gray)
mosaicview(img_gray, img_morpholap; nrow = 1)
Out[0]:
No description has been provided for this image

In [ ]:
using Noise
using ImageBinarization
using ImageDraw

Загрузим изображение и выполним его пороговую обработку. Это изображение мы будем использовать в дальнейшей работе, присвоив ему формат оттенков серого цвета, поскольку указанные ниже операции применяются к изображениям в формате Gray.

In [ ]:
img_input = binarize((img), UnimodalRosin()) .> 0.5
Out[0]:
982×1120 BitMatrix:
 0  0  0  0  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 1  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 1  1  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 1  1  1  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 1  1  1  1  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0
 1  1  1  1  1  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 1  1  1  1  1  1  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 ⋮              ⋮              ⋮        ⋱        ⋮              ⋮           
 0  0  0  0  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0  …  0  0  0  0  0  0  0  0  0  0  0  0
 0  0  0  0  0  0  0  0  0  0  0  0  0     0  0  0  0  0  0  0  0  0  0  0  0

Выделение границ

Функция convexhull выводит внешнюю наиболее похожую на покрытие границу изображения и возвращает вершины объекта границ в виде массива CartesianIndex.

In [ ]:
cordinates = convexhull(img_input)
img_convex = RGB.(copy(img_input))
push!(cordinates, cordinates[1])
draw!(img_convex, Path(cordinates), RGB(1))
img_convex
Out[0]:
No description has been provided for this image

Заполнение изображения

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

In [ ]:
img_noise = salt_pepper(img_input, 0.5)
fill_image_1 = imfill(img_noise, (0.1, 1))
fill_image_2 = imfill(img_noise, (0.1, 10)) # this configuration gets us best results
fill_image_3 = imfill(img_noise, (1, 10))
fill_image_4 = imfill(img_noise, (5, 20)) # objects of smaller sizes gets left out
Gray.([img_noise fill_image_1 fill_image_2 fill_image_3 fill_image_4])
Out[0]:
No description has been provided for this image

Прореживание изображения

Операция прореживания применяет алгоритм Го, который определяет, какие пиксели оставить.

In [ ]:
img_thinning = thinning(img_input, algo = GuoAlgo());
Gray.([img_input img_thinning])
Out[0]:
No description has been provided for this image

Очистка границ

Метод Clearborder можно использовать для очистки объектов, связанных с границей изображения.

In [ ]:
cleared_img_1 = clearborder(img_input, 20); # 20 is the width of border that's examined
cleared_img_2 = clearborder(img_input, 30); # notice how it remove the inner circle even if it's outside its range
cleared_img_3 = clearborder(img_input, 30, 1);

Цвет по умолчанию для удаления — 0, что означает удаление RGB(0), но теперь, поскольку он равен 1, он очищает все изображение из-за алгоритма заливки.

In [ ]:
Gray.([img_input cleared_img_1 cleared_img_2 cleared_img_3])
Out[0]:
No description has been provided for this image

Обработка изображений

В данном примере мы продемонстируем, как применять Engee для обработки изображений, разберём отдельные функции для обработки изображений, рассмотрим возможности обращения к изображениям и методы их обработки и представления.

Обнаружение углов

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

После обнаружения точек интереса отметим их красным цветом, переведя изображения в оттенках серого в RGB и присвоив первому измерению матрицы значения единицы в точках интереса.

In [ ]:
img = load( "$(@__DIR__)/Resources/img.jpg" )

corners = imcorner(img, Percentile(98.5));

img_copy = RGB.(img)
img_copy[corners] .= RGB(1.0, 0.0, 0.0)

simshow(img_copy) 
Out[0]:
No description has been provided for this image

Обнаружение границ изображенных объектов

Ещё одной частой задачей при обработке изображений является
нахождение границ объектов. Для этого воспользуемся функцией
Kernel.sobel, которая позволяет перевести изображения в частотную область.

In [ ]:
function brightness(pixel)
    arr = pixel.r + pixel.g + pixel.b;
    return sum(arr) / length(arr)
end

function find_energy(image)
    energy_y = imfilter(brightness.(image), Kernel.sobel()[1])
    energy_x = imfilter(brightness.(image), Kernel.sobel()[2])    
    return hypot.(energy_x, energy_y) # sqrt.(energy_x.^2 + energy_y.^2)
end

simshow(find_energy(img))
Out[0]:
No description has been provided for this image

По результатам обработки мы однозначно можем определить границы каждого объекта на изображении, и эти данные впоследствии могут быть применены к дальнейшей обработке изображения.

Игра «Жизнь»

Это клеточный автомат, игра без игроков, в которой пользователь создаёт начальное состояние, а потом лишь наблюдает за её развитием. В игре можно создать процессы с полнотой по Тьюрингу, что позволяет реализовать любую машину Тьюринга.

image.png

Правила игры таковы.

  1. В пустой клетке, с которой соседствуют три живые клетки, зарождается жизнь.
  2. Eсли у живой клетки есть две или три живые соседки, то эта клетка продолжает жить.
  3. Если живых соседей меньше двух или больше трёх клетка умирает.

Ссылка: https://engee.com/community/ru/catalogs/projects/igra-zhizn_1

Сравнение по скорости MATLAB и Engee

В данном примере мы сравним скорость выполнения простого скрипта, реализованого в MATLAB и в Engee. Для этого нам понадобятся две библиотеки MATLAB для подключения вычислительного ядра MATLAB и TickTock для замера временных затрат при выполнении скриптов в Engee.

In [ ]:
using TickTock
In [ ]:
mat"""
tic
N = 100;        	
x = (1:N)/N;      	
y = sin(8*pi*x);		
I = repmat(y,100,1);
pcolor(I);              
colormap(bone);      	
toc
"""

tick()
N = 100;
x = (1:N)/N;
y = sin.(8π.*x);
I = repeat(y, 100, 1)
heatmap(hcat(I...)', c=:bone)
tock()

heatmap(hcat(I...), c=:bone)
>> >> >> >> >> >> >> >> [Warning: MATLAB has disabled some advanced graphics rendering features by
switching to software OpenGL. For more information, click <a
href="matlab:opengl('problems')">here</a>.] 
>> >> Elapsed time is 10.705114 seconds.
[ Info:  started timer at: 2025-02-12T10:05:19.421
[ Info:          0.538546208s: 538 milliseconds
Out[0]:

Проект

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

В данном проеке нам понадобятся следующие библиотеки:

  1. Images,
  2. FileIO.
In [ ]:
# Генерируем данные
heights = vcat(0:50:1000,950:-50:0);  # Высота от 0 до 1000 и обратно
angles = rand(-30:30, length(heights)); # Случайные углы наклона
frames = [] # Объявление пустого кадра

# Основной цикл создания кадров
@gif for (i, h) in enumerate(heights)
    angle = angles[i]
    
    # Создаём пустой график
    p = plot(; xlims=(-1, 1), ylims=(-1, 1), framestyle=:none, grid=false, size=(400, 400))
    
    # Рисуем дрон (просто линию в виде перекрестия)
    x = [-0.2, 0.2]
    y = [0, 0]
    rotated_x = cosd(angle) .* x .- sind(angle) .* y
    rotated_y = sind(angle) .* x .+ cosd(angle) .* y
    plot!(rotated_x, rotated_y, linewidth=3, color=:black)
    
    # Добавляем стрелку изменения высоты
    arrow_dir = (i == 1 || heights[i] > heights[i-1]) ? "↑" : "↓"
    # Добавляем высоту и угол
    annotate!(-0.8, 0.8, "Height: $h m $arrow_dir")
    annotate!(-0.8, 0.6, "Tilt: ($angle)°")
    
    push!(frames, p)
end
[ Info: Saved animation to /user/Winter_school/tmp.gif
Out[0]:
No description has been provided for this image

Теперь рассмотрим итоговый проект.

Инициализация.

In [ ]:
using CSV

# Читаем данные из файла
df_read = CSV.read("$(@__DIR__)/Resources/data_drone.csv", DataFrame)

# Проверка первых строк
first(df_read, 5)
Out[0]:
5×3 DataFrame
RowВремяВысотаУгол
Int64Float64Float64
100.0-4.0
213.0-5.0
325.0-3.0
438.0-3.0
5410.0-3.0
In [ ]:
max_idx = 350;
t = df_read.Время[1:max_idx]
heights = df_read.Высота[1:max_idx]
angles = df_read.Угол[1:max_idx]

H = plot(t, heights, title="Высота")
A = plot(t, angles, title="Угол наклона")
plot(H, A, layout=(2, 1), legend=false)
Out[0]:
In [ ]:
max_y_h = maximum(heights)+10

# Уравнение единичной окружности
θ = range(0, 2π, length=100)
x_circle = cos.(θ)
y_circle = sin.(θ)
x = [-1, 1]

# Загрузка изображения дрона
using Images
drone_img = load("$(@__DIR__)/Resources/drone.jpg") 
Out[0]:
No description has been provided for this image

Алгоритм.

In [ ]:
@gif for i in 1:length(t)
    ti = t[i]
    ai = angles[i]
    hi = heights[i]

    # Высота
    h_plt = scatter([ti], [hi], 
        markershape=:circle, markersize=15, markercolor=:red, framestyle=:box, 
        xlims = (ti-10, ti+10), ylims = (0, max_y_h), 
        legend=false, grid=false, 
        title="Высота: $hi m $((i == 1 || heights[i] > heights[i-1]) ? "↑" : "↓")")

    # Тангаж
    tang = plot([x_circle, (cosd(-ai) .* x)], [y_circle, (sind(-ai) .* x)], 
        linewidth=3, color=:black, framestyle=:none, 
        title="Отклонение: $(ai)°")

    # Дрон
    rotated_array = channelview(Gray.(imrotate(drone_img, π*(ai+90)/ 180)))
    rotated_array[rotated_array .== 0] .=  1

    dron = plot(framestyle=:none, grid=false, title = "Время: $(t[i])sec")
    heatmap!(
        1:size(rotated_array, 2), 1:size(rotated_array, 1),
        permutedims(rotated_array, (2, 1)),
        colorbar=false, c=:grays
    )
    # Объединение графиков
    Tng_Dr = plot(dron, tang, layout = grid(2, 1, heights=[0.8, 0.2]))
    grup = plot(Tng_Dr, h_plt, layout= grid(1, 2, width = [0.75, 0.25]), legend=false)
end
[ Info: Saved animation to /user/Winter_school/tmp.gif
Out[0]:
No description has been provided for this image