Image Smoother

Updated on 02 June, 2025
Image Smoother header image

Problem Statement

The goal is to transform an image, represented as a two-dimensional grayscale matrix, using a 3x3 image smoothing filter. Each cell in the image corresponds to a pixel with a grayscale value between 0 and 255. The smoothing filter works by calculating the average grayscale value of the center cell and its surrounding eight cells, then rounding this average down to the nearest whole number. If a surrounding cell doesn't exist because the center cell is on an edge or corner, only existing surrounding cells are included in the average. The outcome of this procedure is a new image where each pixel's value is smoothed based on the local average of neighboring pixel values.

Examples

Example 1

Input:

img = [[1,1,1],[1,0,1],[1,1,1]]

Output:

[[0,0,0],[0,0,0],[0,0,0]]

Explanation:

For the points (0,0), (0,2), (2,0), (2,2): floor(3/4) = floor(0.75) = 0
For the points (0,1), (1,0), (1,2), (2,1): floor(5/6) = floor(0.83333333) = 0
For the point (1,1): floor(8/9) = floor(0.88888889) = 0

Example 2

Input:

img = [[100,200,100],[200,50,200],[100,200,100]]

Output:

[[137,141,137],[141,138,141],[137,141,137]]

Explanation:

For the points (0,0), (0,2), (2,0), (2,2): floor((100+200+200+50)/4) = floor(137.5) = 137
For the points (0,1), (1,0), (1,2), (2,1): floor((200+200+50+200+100+100)/6) = floor(141.666667) = 141
For the point (1,1): floor((50+200+200+200+200+100+100+100+100)/9) = floor(138.888889) = 138

Constraints

  • m == img.length
  • n == img[i].length
  • 1 <= m, n <= 200
  • 0 <= img[i][j] <= 255

Approach and Intuition

Let's break down the approach step-by-step to understand the application of the image smoothing filter:

  1. Create a new matrix of the same dimensions as the input to hold the smoothed values.
  2. For every pixel, compute the average based on itself and its neighbors.
    • The neighbors of a pixel include the cells up, down, left, right, and diagonally adjacent.
  3. Calculate averages, adjusting for edge cases:
    • For cells on the top or bottom row, omit the non-existent top or bottom neighbors.
    • For cells on the left or right edges, omit the non-existent left or right neighbors.
    • Only include valid neighbors within the image boundaries when calculating the average.
  4. Ensure that the floor of the computed average is used as the final value, according to the problem statement.
  5. Assign this floored value to the respective cell in the new matrix.

From the Examples:

  • Example 1:

    • The corner points have fewer than 8 neighbors, and their calculated values consider fewer neighbors. All resulting average calculations fall within a range that when floored, result in a value of 0.
    • While center points (1,1) have the maximum of 8 neighbors, their summed values still floor to 0.
  • Example 2:

    • For corners, despite having different grayscale values in different neighborhoods, the floor division after averaging results in specific numbers like 137 for corners due to fewer neighbors.
    • Edges and the center have more samples to average and various grayscale values, leading to a slight increase in the smoothed values.

By respecting the constraint on image dimensions (1 to 200 for both width and height) and values (0 to 255 for grayscale values), this approach efficiently calculates a new, smoother image.

Solutions

  • C++
  • Java
  • JavaScript
  • Python
cpp
class Solution {
public:
    vector<vector<int>> smoothImage(vector<vector<int>>& matrix) {
        int rows = matrix.size();
        int cols = matrix[0].size();

        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                int total = 0;
                int elements = 0;

                for (int ri = r - 1; ri <= r + 1; ri++) {
                    for (int ci = c - 1; ci <= c + 1; ci++) {
                        if (0 <= ri && ri < rows && 0 <= ci && ci < cols) {
                            total += matrix[ri][ci] & 255;
                            elements++;
                        }
                    }
                }

                matrix[r][c] |= (total / elements) << 8;
            }
        }

        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                matrix[r][c] >>= 8;
            }
        }

        return matrix;
    }
};

The C++ solution provided implements an image smoothing algorithm. It uses a 2D matrix to represent the image, where each element in the matrix corresponds to a pixel's intensity value. The algorithm proceeds in two primary steps to modify the original matrix into its smoothed form:

  1. Calculate the average intensity of each pixel based on itself and its eight surrounding pixels (neighbors). This is performed for all pixels in the matrix. Important points for this step include:

    • Border pixels have fewer neighbors, so the average is only taken over the existing neighbors within the matrix bounds.
    • The algorithm uses bitwise AND operation (& 255) to ensure that only the least significant 8 bits of the integer are considered during summing up the values.
  2. Once the average for all pixels is calculated, it is stored temporarily in the higher bits of each matrix element using a bitwise OR and left shift operation (|=(total / elements) << 8). This is to temporarily hold the smoothed values without losing the original values during calculations for all pixels.

  3. In the final step, the values are normalized by right-shifting by 8 bits (>> 8), effectively moving the temporary smoothed values into the position of the original ones and discarding the old pixel values.

This method effectively smooths the image in a way that each pixel's new value is influenced by its neighboring pixels, leading to less noise and more uniform regions in the image. The entire smoothing operation modifies the matrix in place, making it an efficient solution in terms of space complexity.

java
class Solution {
    public int[][] smoothImage(int[][] matrix) {
        int rows = matrix.length;
        int cols = matrix[0].length;

        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                int total = 0;
                int neighbours = 0;

                for (int i = row - 1; i <= row + 1; i++) {
                    for (int j = col - 1; j <= col + 1; j++) {
                        if (i >= 0 && i < rows && j >= 0 && j < cols) {
                            total += matrix[i][j] & 255;
                            neighbours++;
                        }
                    }
                }
                
                matrix[row][col] |= (total / neighbours) << 8;
            }
        }

        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                matrix[i][j] >>= 8;
            }
        }

        return matrix;
    }
}

The "Image Smoother" problem requires constructing a new image where each pixel's value is the average of that pixel and its surrounding eight pixels' values in the original image, including itself. The solution involves creating a smoother effect in the image, which is accomplished through a pixel-based averaging technique implemented in Java.

The code begins by obtaining the dimensions of the input matrix representing the image. It iterates through each pixel of the matrix, processing pixel by pixel. For each pixel, it collects the sum of the values of itself and its neighbors within the matrix boundaries. The count of valid neighboring pixels is also maintained. These are then used to compute the average value for each pixel.

To avoid altering the original image matrix while still using it for reference:

  1. Store the result temporarily by shifting the average to higher bits, effectively doubling the array's purpose without needing an auxiliary memory immediately.
  2. After processing all pixels, shift back the bits to get the final averaged value.

The solution handles boundary pixels by checking if each neighboring pixel falls within the valid range of indices. This edge handling ensures that the averaging only considers the actual available pixels, preventing out-of-bound errors and preserving the data integrity of the resulting image.

In summary, this Java solution modifies the image in-place with an effective approach that manages both calculation and memory efficiently. It uses bit manipulation to store intermediate results and ensure the final outcome is smoothly updated without additional memory overhead.

js
/**
 * @param {number[][]} image
 * @return {number[][]}
 */
var smoothImage = function(image) {
    const rows = image.length;
    const cols = image[0].length;

    for (let row = 0; row < rows; row++) {
        for (let col = 0; col < cols; col++) {
            let totalSum = 0;
            let cellCount = 0;

            for (let dx = row - 1; dx <= row + 1; dx++) {
                for (let dy = col - 1; dy <= col + 1; dy++) {
                    if (dx >= 0 && dx < rows && dy >= 0 && dy < cols) {
                        totalSum += image[dx][dy] & 255;
                        cellCount++;
                    }
                }
            }

            image[row][col] |= (totalSum / cellCount) << 8;
        }
    }

    for (let row = 0; row < rows; row++) {
        for (let col = 0; col < cols; col++) {
            image[row][col] >>= 8;
        }
    }

    return image;
};

The given JavaScript function smoothImage takes a 2D array image representing pixel values and returns the same array where each pixel's new value is computed as the average of its own value and the values of its eight neighboring pixels, effectively smoothing the image.

Detailed Explanation:

  1. Begin by determining the dimensions of the image using rows for the number of rows and cols for the number of columns.
  2. Iterate through each pixel of the image using nested loops. Each pixel is referenced as image[row][col].
  3. Initialize totalSum to accumulate the sum of pixel values within the 3x3 grid including the center pixel and its neighbors.
  4. Initialize cellCount to keep track of the number of valid pixel values added to totalSum (i.e., pixels that are not outside the image bounds).
  5. Use another pair of nested loops to traverse the 3x3 grid around the current pixel image[row][col]. The loops are controlled by dx and dy, iterating over the possible neighbor positions.
  6. Check if the neighbor pixel is within the image boundaries using conditions and if so, add its value to totalSum and increment cellCount.
  7. After processing all neighbors for the current pixel, compute the average of the surrounding pixel values which is (totalSum / cellCount). Store this value temporarily by shifting it left by 8 bits and OR it with the original pixel value to prevent immediate overriding during the process.
  8. Once all pixels are processed, use another set of nested loops to replace the original pixel values with the computed averages by shifting the values right by 8 bits to retrieve the smoothed values.
  9. Finally, return the smoothed image.

Performance Consideration: This solution iteratively alters the original image array in-place and uses bit manipulation to store intermediate results. This approach may not be intuitive but is efficient in terms of space complexity as it avoids using additional arrays for interim data storage.

python
class Solution:
    def smoothImage(self, matrix: List[List[int]]) -> List[List[int]]:
        rows = len(matrix)
        cols = len(matrix[0])

        for r in range(rows):
            for c in range(cols):
                avg_sum = 0
                neighbor_count = 0

                for row_offset in (r - 1, r, r + 1):
                    for col_offset in (c - 1, c, c + 1):
                        if 0 <= row_offset < rows and 0 <= col_offset < cols:
                            avg_sum += matrix[row_offset][col_offset] & 255
                            neighbor_count += 1
                
                matrix[r][c] |= (avg_sum // neighbor_count) << 8

        for r in range(rows):
            for c in range(cols):
                matrix[r][c] >>= 8
            
        return matrix

The provided Python code defines a method smoothImage within a class Solution to perform image smoothing by calculating the average of neighboring elements for each cell in a 2D matrix. Here's a step-by-step explanation of how the code achieves this:

  1. Initialize rows and cols to capture the dimensions of the input matrix.
  2. Iterate over each element of the matrix using nested loops (by row r and column c).
  3. For each element, compute the sum and count of valid neighboring elements. Valid neighbors are within bounds of the matrix dimensions and include the element itself and its surrounding eight possible neighbors.
  4. Store the average value of these neighbors in the high 8 bits of the current matrix cell using a bitwise operation.
  5. After processing all matrix elements with the average values encoded in the high bits, iterate over the matrix again.
  6. In this second iteration, right-shift the values by 8 bits, effectively updating the matrix with the smoothed values computed from the first pass.

The algorithm ensures that the original matrix space is used to store both the original and new values to minimize the additional space required, making it more space efficient. The matrix values are handled using bitwise operations to combine and then separate the values, optimizing the in-place modification process.

Comments

No comments yet.