Simple seam carving project written in Matlab

← Previous page

Last updated Mer 4 Mar 2024 12:42:01 EST

In this article we explore seam carving, a content-aware image resizing technique that enables images to be resized or reshaped without distorting the most important content within the image.

Brief explanation

Unlike traditional scaling methods that uniformly scale down or crop images, seam carving works by identifying and removing or duplicating paths of pixels called "seams" that traverse the image from one edge to another. These seams are selected based on an energy map of the image, where lower energy values are assigned to less important areas of the image, such as empty skies or uniform backgrounds, and higher energy values to areas with more detail or importance. By iteratively removing or duplicating these least noticeable paths, seam carving allows for reducing or expanding the size of an image in a way that preserves the visual integrity of its key features, making it especially useful for adapting images to fit different screen sizes or aspect ratios without noticeable distortion.

How does it work?

Energy map calculation

The process begins with the computation of an "energy map" of the image. This map is a representation of the image where each pixel's value corresponds to its importance or saliency. The energy of a pixel is typically calculated based on its contrast with neighboring pixels, using gradients or edge detection algorithms like the Sobel operator. High-energy pixels are those that are part of significant features in the image, such as edges, textures, or objects, while low-energy pixels belong to less important areas, like homogeneous backgrounds.

function energy = computeEnergy(image)
    % Convert image to grayscale
    grayImage = rgb2gray(image);

    % Apply Gaussian filter
    sigma = 1.5; % You can adjust the value based on your preference
    kernelSize = 7; % You can adjust the size based on your preference
    gaussFilter = fspecial('gaussian', [kernelSize, kernelSize], sigma);
    smoothedImage = conv2(double(grayImage), gaussFilter, 'same');

    % Compute gradients using imgradientxy
    [gradX, gradY] = imgradientxy(smoothedImage);

    % Compute energy as the magnitude of the gradients
    energy = sqrt(gradX.^2 + gradY.^2);
end

Seam Identification

A seam is defined as a continuous path of pixels running from one edge of the image to the opposite edge (top to bottom or left to right) that minimizes the total energy. The path of a seam is constrained so that it moves in a straight or diagonal direction from one pixel to the next, ensuring the seam is connected and does not skip any part of the image. The identification of the optimal seam is typically achieved through dynamic programming, which efficiently searches for the path with the lowest cumulative energy.

function [optimalSeam, seamEnergy] = findOptimalSeam(energy)
    [rows, cols] = size(energy);

    % Initialize seam energy matrix and backtracking matrix
    seamEnergy = zeros(rows, cols);

    % Copy the first row from the energy matrix
    seamEnergy(1, :) = energy(1, :);

    % Compute cumulative energy and backtracking matrix
    for i = 2:rows
        for j = 1:cols
            if j == 1
                % Handle leftmost edge case
                [minVal, index] = min([seamEnergy(i-1, j), seamEnergy(i-1, j+1)]);
                seamEnergy(i, j) = energy(i, j) + minVal;
            elseif j == cols
                % Handle rightmost edge case
                [minVal, index] = min([seamEnergy(i-1, j-1), seamEnergy(i-1, j)]);
                seamEnergy(i, j) = energy(i, j) + minVal;
            else
                % General case
                [minVal, index] = min([seamEnergy(i-1, j-1), seamEnergy(i-1, j), seamEnergy(i-1, j+1)]);
                seamEnergy(i, j) = energy(i, j) + minVal;
            end
        end
    end

    % Find the starting index of the optimal seam
    [~, startCol] = min(seamEnergy(rows, :));

    % Backtrack to find the optimal seam
    optimalSeam = zeros(rows, 1);
    optimalSeam(rows) = startCol;

    for i = rows-1:-1:1
        if startCol == 1
            [~, index] = min([seamEnergy(i, startCol), seamEnergy(i, startCol+1)]);
            startCol = startCol + index - 1;
        elseif startCol == cols
            [~, index] = min([seamEnergy(i, startCol-1), seamEnergy(i, startCol)]);
            startCol = startCol - 2 + index;
        else
            [~, index] = min([seamEnergy(i, startCol-1), seamEnergy(i, startCol), seamEnergy(i, startCol+1)]);
            startCol = startCol - 2 + index;
        end
        optimalSeam(i) = startCol;
    end
end

Seam Removal or Duplication

Once the seam with the lowest energy is identified, the process continues with either the removal or duplication of this seam. Removing a seam narrows the image by one pixel in width or height (depending on the seam's orientation), effectively reducing the size of the image while attempting to preserve its most important content. Conversely, duplicating a seam increases the size of the image by adding a line of pixels adjacent to the low-energy seam, which is less likely to disrupt the visual coherence of the image.

function updatedImage = removeSeam(image, seam)
    [height, width, channels] = size(image);
    updatedImage = zeros(height, width - 1, channels, 'uint8');

    for i = 1:height
        colToRemove = seam(i);
        updatedImage(i, :, :) = [image(i, 1:colToRemove-1, :), image(i, colToRemove+1:end, :)];
    end
end

Iterative Process

Seam carving is typically an iterative process. To achieve the desired image size, seams are removed or added one at a time, with the energy map and seams recalculated after each modification. This iterative approach allows the algorithm to adapt dynamically to the changing content of the image, ensuring that the most visually significant features are preserved even as the image's dimensions are altered.

function createSeamCarvingVideo(inputImagePath, outputVideoPath)
    % Load the original color image
    originalImage = imread(inputImagePath);
    [height, width, col] = size(originalImage);fprintf("h:%d w:%d \n\nProgress:     ",height,width)
    copyImage = originalImage; %% useless: could use original
    maxFrames = width - 1; % Number of frames needed to reduce the width to 1
    outputVideo = VideoWriter(outputVideoPath, 'MPEG-4');
    open(outputVideo);
    for frame = 1:maxFrames 

      % Compute energy of the original image
      energyImage = computeEnergy(copyImage);

      % Find and visualize the optimal seam on the original image
      [optimalSeam, ~] = findOptimalSeam(energyImage);

       for i = 1:size(optimalSeam, 1)
            copyImage(i, optimalSeam(i), 1) = 255; % Set red channel to maximum
            copyImage(i, optimalSeam(i), 2) = 0;   % Set green channel to minimum
            copyImage(i, optimalSeam(i), 3) = 0;   % Set blue channel to minimum
       end
      %imwrite(copyImage,sprintf("out1_%dpre.png", frame ));
      paddedImage = padarray( copyImage , [0,frame-1], 0,'post');
      prog = (100*(frame/maxFrames));

      fprintf(1,'\b\b\b\b%3.0f%%',prog);  
      writeVideo(outputVideo, paddedImage);
      copyImage = removeSeam(copyImage, optimalSeam);
      %imwrite(copyImage,sprintf("out2_%d.png", frame ));
    end
    % Create a VideoWriter object
    close (outputVideo);
    end

Execute

To execute, simply add the input image to the inputImagePath variable. Add the path for the output video in the outputVideoPath variable. Call createSeamCarvingVideo with inputImagePath and outputVideoPath and wait!


inputImagePath = 'path/to/your/image.jpeg';
outputVideoPath = 'path/to/output/video.mp4';
createSeamCarvingVideo(inputImagePath, outputVideoPath);

Conclusion

Try it out and let me know!