Getting Started with Image Processing in Julia: Satellite Image Segmentation Step-by-Step

Introduction

Digital image processing is everywhere—from the filters on your phone to advanced medical imaging and satellite analysis. But why should developers and tech enthusiasts care about the algorithms behind the scenes when tools like Photoshop or neural networks seem to do it all? And what makes Julia a compelling language for image processing?

In this article, we kick off a series exploring image processing with Julia and the Engee computational environment. We’ll answer common questions, such as:

  • Why is image processing still a hot topic in computer science?
  • What real-world problems does it solve?
  • How can you implement both classic and modern algorithms yourself?

Let’s dive in by segmenting a satellite image—balancing theory with hands-on Julia code. Whether you’re a beginner or an experienced developer, you’ll find practical insights and code you can adapt to your own projects.


What Is Digital Image Processing?

At its core, digital image processing (DIP) means manipulating images on a computer using mathematical algorithms. Think of an image as a matrix of pixel values—DIP lets you adjust color, brightness, contrast, remove noise, detect objects, and enhance image quality.

While most people rely on user-friendly editors for personal photos, DIP algorithms power much more:

  • Medicine & Biology: Enhance X-rays, MRI, CT, ultrasound; detect tumors; analyze cell microscopy.
  • Computer Vision & Robotics: Face, gesture, and vehicle recognition; robot navigation; augmented reality.
  • Remote Sensing & GIS: Analyze satellite/aerial images for forestry, agriculture, urban planning, disaster monitoring.
  • Industry & Automation: Detect defects in manufacturing, optical sorting, robotic assembly.
  • Security, Entertainment, AI, Art Digitization: And much more!

These algorithms run on everything from desktop apps to supercomputers, GPUs, FPGAs, and even compact ASICs in drones. For this series, we’ll use Engee, a browser-based environment supporting Julia and Python, with built-in GPU acceleration.


Problem Statement: Segmenting Satellite Images

We’ll focus on a practical example: segmenting a satellite image to separate green areas (like parks or forests) from urban structures.

Image segmentation means dividing an image into meaningful regions or objects. For example:

  • In medicine: Isolate tumors from healthy tissue in MRI scans.
  • In self-driving cars: Detect roads, pedestrians, and vehicles.
  • In photography: Replace backgrounds.
  • In agriculture: Analyze plant health from drone images.

In our case, we’ll perform binary segmentation—classifying each pixel as either «object» (forest) or «background» (city), resulting in a mask of 1s and 0s.

We’ll walk through a straightforward algorithm involving:

  1. Preprocessing
  2. Binarization
  3. Morphological operations
  4. Overlaying the resulting mask

Why Julia for Image Processing?

If you’ve used MATLAB for image processing, Julia will feel familiar—its syntax is concise and expressive. But Julia offers more:

  • Speed: Native matrix operations are 10–100x faster than Python’s standard stack.
  • Multithreading: No Global Interpreter Lock (GIL); easy parallelism.
  • JIT Compilation & Type Specialization: Great for real-time video, 3D reconstruction, and neural networks.

Key Julia Packages:

  • Images.jl: Core image processing (filters, morphology, color spaces)
  • ImageSegmentation.jl: Classic segmentation (k-means, watershed)
  • ImageFeatures.jl: Keypoint detection (SIFT, ORB)
  • Flux.jl, Metalhead.jl: Deep learning and pretrained models
  • ONNX.jl, Torch.jl: Interoperability with ONNX and PyTorch
  • Benchmarking and SIMD macros for performance tuning

We’ll use Engee’s interactive scripts for a MATLAB-like experience—no installation required, results are easy to visualize and share.


Step 1: Loading and Visualizing the Image

Start by importing the necessary Julia libraries:

using Images, ImageShow, ImageContrastAdjustment, ImageBinarization, ImageMorphology

Load your satellite image:

I = load("$(@__DIR__)/map_small.jpg")  # Load the color image

To decide how to segment, let’s inspect the color channels:

CV = channelview(I)  # Split into RGB channels
[ RGB.(CV[1,:,:], 0.0, 0.0) RGB.(0.0, CV[2,:,:], 0.0) RGB.(0.0, 0.0, CV[3,:,:]) ]

You’ll notice that green areas (forests) appear darker in the blue channel. We’ll use this for segmentation.


Step 2: Preprocessing

Preprocessing improves segmentation by smoothing out noise and enhancing contrast. We’ll apply a smoothing filter (kernel) to the blue channel:

BLUE = CV[3,:,:]  # Blue channel
kernel_average = ones(7,7) .* 1/(7^2)  # 7x7 averaging kernel
avgIMG = imfilter(BLUE, kernel_average)  # Apply smoothing

Now, lighter pixels correspond to urban areas, darker to forests.


Step 3: Binarization

Binarization converts the image to black and white (0 or 1), separating object from background. The simplest method is thresholding:

binTHRESH = avgIMG .> 0.25  # Pixels > 0.25 are 'city', else 'forest'

Alternatively, use Otsu’s method for automatic thresholding:

binOTSU = binarize(avgIMG, Otsu())

Both methods work well here. We’ll proceed with the simple threshold.


Step 4: Morphological Operations

Morphological operations clean up the binary mask—removing noise, filling gaps, and smoothing shapes. Common operations include closing (fill small holes) and area opening (remove small objects):

se = Kernel.gaussian(3) .> 0.0025  # Circular structuring element
closeBW = closing(binTHRESH, se)  # Close small holes
openBW = area_opening(closeBW; min_area = 500) .> 0  # Remove small blobs

Invert and further clean the mask:

fillBW = area_opening(.!openBW; min_area = 5000)
MASK_AVG = closing(fillBW, se)

Step 5: Visualizing the Segmentation

Overlay the mask on the original image to see the result:

sv1_AVG = StackedView(CV[1,:,:] + (MASK_AVG./3), CV[2,:,:] + (MASK_AVG./3), CV[3,:,:])

forest_AVG = colorview(RGB, sv1_AVG)

This highlights the segmented forest area in yellow. The result is promising, though parameters were tuned for this specific image.


Bonus: Nonlinear Filtering for Better Segmentation

Linear filters (like averaging) are just the start. Nonlinear filters can separate regions based on texture, not just intensity. For example, a range filter computes the difference between max and min pixel values in a window—urban areas tend to have higher local variation than forests.

First, enhance contrast:

IMG = adjust_histogram(Gray.(I), LinearStretching(dst_minval = -1, dst_maxval = 1.8))

Define and apply the range filter:

function range(array)
    (minval, maxval) = extrema(array)

    return maxval - minval
end
rangeIMG = mapwindow(range, IMG, (7,7))
BW_RNG = binarize(rangeIMG, Otsu())

Apply the same morphological cleanup as before.


Wrapping Up: A Custom Segmentation Function

Combine the steps into a reusable Julia function:

function mySegRange(I)
    IMG = adjust_histogram(Gray.(I), LinearStretching(dst_minval = -1, dst_maxval = 1.8))
    rangeIMG = mapwindow(range, IMG, (7,7))
    BW_RNG = binarize(rangeIMG, Otsu())
    # Morphological cleanup (as above)
    se = Kernel.gaussian(3) .> 0.0025
    closeBW = closing(BW_RNG, se)

    openBW = area_opening(closeBW; min_area = 500) .> 0
    fillBW = area_opening(.!openBW; min_area = 5000)
    MASK_RNG = closing(fillBW, se)
    return MASK_RNG
end

Apply it to a larger image and visualize the result. This approach enables automated analysis—such as calculating the area of forests or urban zones based on pixel counts and map scale.


Conclusion & Next Steps

We’ve covered the basics of digital image processing and segmentation using Julia and Engee, blending theory with hands-on code. While our script is tailored to a specific example, the techniques are widely applicable. For more robust solutions, consider adaptive parameter selection or advanced algorithms.

Tips:

  • Experiment with different filters and thresholds for your images.
  • Explore Julia’s ecosystem—many packages offer state-of-the-art methods.
  • Try Engee for interactive, cloud-based development.

In the next part, we’ll explore color-based segmentation and smarter algorithms. Stay tuned, and happy coding!

Resources: