Engee documentation
Notebook

Image processing

In this example we will demonstrate how to apply Engee to image processing, break down the individual functions for image processing, look at how images can be accessed and how they can be processed and represented.

Let's start by selecting the required function libraries.

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

Loading an image

The load function allows you to save images to workspaces as a matrix.

In [ ]:
I = load("/user/start/examples/image_processing/processing_methods/IMG_001.jpg")
Out[0]:
No description has been provided for this image

Converting RGB to greyscale

Let's create a custom function for calculating average values for the sum of measurements of a three-dimensional RGB matrix.

RGB (red, green, blue) is an additive colour model that describes the way colour is encoded for colour reproduction using three colours, which are commonly called primary colours.

In [ ]:
function brightness(pixel)
    arr = pixel.r + pixel.g + pixel.b;
    return sum(arr) / length(arr)
end
Out[0]:
brightness (generic function with 1 method)

After defining the function, let's call it and render the image.

In [ ]:
Ibright = brightness.(I)
simshow(Ibright)
Out[0]:
No description has been provided for this image

As you can see from the figure above, we have reduced the images to shades of grey.

Corner detection

Corner detection attempts to identify points in an image that have a well-defined position and can be reliably labelled in multiple images of the same scene. Very often these points lie at the corners or edges of image objects - hence the name of the method.

Corner detection is useful in a number of computer vision tasks - image registration, motion detection and panoramic stitching. The method is based on the fact that if the location of the same points is known in two different images, it provides opportunities for aligning these images.

For corner detection, we will use the imcorner function, which has one additional parameter, which is the threshold percentage parameter for detecting points of interest.

In [ ]:
corners = imcorner(Ibright, Percentile(98.5));
┌ Error: handle_connection handler error
│   exception = (Base.IOError("write: broken pipe (EPIPE)", -32), Base.StackTraces.StackFrame[uv_write(s::TCPSocket, p::Ptr{UInt8}, n::UInt64) at stream.jl:1064, unsafe_write(s::TCPSocket, p::Ptr{UInt8}, n::UInt64) at stream.jl:1118, unsafe_write(c::HTTP.ConnectionPool.Connection, p::Ptr{UInt8}, n::UInt64) at ConnectionPool.jl:106, write(io::HTTP.ConnectionPool.Connection, s::String) at io.jl:244, write at io.jl:676 [inlined], unsafe_write(http::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Connection}, p::Ptr{UInt8}, n::UInt64) at Streams.jl:96, write(io::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Connection}, s::String) at io.jl:244, write(io::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Connection}, s::Base.CodeUnits{UInt8, String}) at basic.jl:758, (::HTTP.Handlers.var"#1#2"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}})(stream::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.ConnectionPool.Connection}) at Handlers.jl:61, #invokelatest#2 at essentials.jl:729 [inlined], invokelatest at essentials.jl:726 [inlined], handle_connection(f::Function, c::HTTP.ConnectionPool.Connection, listener::HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, readtimeout::Int64, access_log::Nothing) at Servers.jl:447, (::HTTP.Servers.var"#16#17"{HTTP.Handlers.var"#1#2"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}}, HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, Set{HTTP.ConnectionPool.Connection}, Int64, Nothing, Base.Semaphore, HTTP.ConnectionPool.Connection})() at task.jl:484])
└ @ HTTP.Servers /root/.julia/packages/HTTP/Kxa7P/src/Servers.jl:461

Once the points of interest are detected, we mark them in red by converting the greyscale images to RGB and assigning unit values to the first matrix measurement at the points of interest.

In [ ]:
img_copy = RGB.(Ibright)
img_copy[corners] .= RGB(1.0, 0.0, 0.0)
Out[0]:
No description has been provided for this image
┌ Warning: Output swatches are reduced due to the large size (1×29420).
│ Load the ImageShow package for large images.
└ @ Colors /root/.julia/packages/Colors/mIuXl/src/display.jl:159

As we can see, the corners were found on the most contrasting objects in the image. In this case, these are clouds and the sea.

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

Detecting the boundaries of the depicted objects

Another frequent task in image processing is to find boundaries of objects. For this purpose let's use the Kernel.sobel function, which allows to translate images into the frequency domain. Below we present a custom function of boundary detection.

In [ ]:
function find_energy(image)
    energy_y = imfilter(brightness.(image), Kernel.sobel()[1])
    energy_x = imfilter(brightness.(image), Kernel.sobel()[2])    
    return sqrt.(energy_x.^2 + energy_y.^2)
end
Out[0]:
find_energy (generic function with 1 method)

Next, let's apply the custom boundary detection function to the original image and display the result of image processing.

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

Based on the processing results, we can unambiguously determine the boundaries of each object in the image, and this data can be subsequently applied to further image processing.

Conclusion

As our example of working with a picture has shown, Engee is excellent for image processing. Given the extensive mathematical functionality of the environment, we can describe any specific processing algorithm using custom functions.