Skip to content

Repository files navigation

Manual Image Processing in C++

An educational C++ implementation of core image-processing operations and edge-based message encoding, built without relying on a high-level computer-vision library.

Overview

This project explores how fundamental image-processing operations work at matrix and pixel level. It implements a small grayscale image-processing pipeline using custom C++ classes for matrix storage, convolution, sharpening, Sobel edge detection, and message encoding/decoding.

The objective is not to replace libraries such as OpenCV. Instead, the project demonstrates the algorithms and low-level engineering concepts behind common image-processing operations, including dynamic memory management, copy semantics, operator overloading, kernel-based filtering, gradient calculation, and least-significant-bit manipulation.

Implemented Components

Image matrix representation

ImageMatrix stores a grayscale image as a dynamically allocated two-dimensional matrix of double values. It provides:

  • Construction from dimensions, another matrix, or a text-based image file
  • Deep-copy construction and assignment
  • Matrix addition and subtraction
  • Scalar multiplication
  • Bounds-checked pixel access and updates
  • Explicit resource cleanup

Two-dimensional convolution

Convolution applies a custom kernel to an image and supports:

  • Arbitrary kernel dimensions
  • Configurable stride
  • Optional zero padding
  • Output-dimension calculation
  • Deep copying of kernel data

For an input image (I) and kernel (K), each output value is computed from the weighted neighbourhood of the corresponding input location.

Image sharpening

ImageSharpening uses a 3 × 3 mean-blur kernel followed by an unsharp-masking operation:

[ I_{sharp} = I + k(I - I_{blurred}) ]

The resulting pixel values are clipped to the valid grayscale range of 0–255.

Sobel edge detection

EdgeDetector applies horizontal and vertical Sobel kernels:

[ G_x = \begin{bmatrix} -1 & 0 & 1 \ -2 & 0 & 2 \ -1 & 0 & 1 \end{bmatrix}, \qquad G_y = \begin{bmatrix} -1 & -2 & -1 \ 0 & 0 & 0 \ 1 & 2 & 1 \end{bmatrix} ]

The gradient magnitude is calculated as:

[ G = \sqrt{G_x^2 + G_y^2} ]

Pixels whose magnitude exceeds the image-wide average magnitude are returned as edge locations.

Message encoding and decoding

The project also experiments with hiding text at edge-pixel locations:

  • EncodeMessage selects detected edge positions and modifies pixel information using character transformations and least-significant-bit operations.
  • DecodeMessage reads least-significant bits from edge pixels, groups them into 7-bit segments, and converts the segments back to ASCII characters.
  • ImageProcessor connects sharpening, edge detection, encoding, and decoding into higher-level workflows.

This component is an educational steganography experiment rather than a production-ready or cryptographically secure encoding system.

Processing Pipeline

flowchart TD
    A[Grayscale matrix] --> B[Mean blur]
    B --> C[Unsharp masking]
    C --> D[Sobel gradients]
    D --> E[Edge locations]
    E --> F[Encode or decode message]
Loading

Project Structure

.
├── ImageMatrix.h/.cpp       # Matrix storage, copy semantics, and arithmetic
├── ImageLoader.h/.cpp       # Loading numeric grayscale matrices from text files
├── Convolution.h/.cpp       # Configurable 2D convolution
├── ImageSharpening.h/.cpp   # Mean filtering and unsharp masking
├── EdgeDetector.h/.cpp      # Sobel gradients and edge selection
├── EncodeMessage.h/.cpp     # Edge-based message encoding experiment
├── DecodeMessage.h/.cpp     # LSB extraction and ASCII reconstruction
├── ImageProcessor.h/.cpp    # High-level processing pipeline
├── main.cpp                 # Minimal development entry point
├── CMakeLists.txt           # CMake build configuration
└── README.md

Input Format

ImageLoader currently expects a text file containing whitespace-separated grayscale values. Each row represents one image row:

12 18 25 31
20 45 80 42
17 53 91 36
10 22 30 19

All rows should have the same number of values. Typical pixel values are in the range 0–255.

The current version does not directly decode PNG, JPEG, or other compressed image formats.

Build

Requirements

  • A C++14-compatible compiler
  • CMake 3.26 or newer for the provided CMake configuration

Build with CMake

git clone https://github.com/003733m/manualimageprocess.git
cd manualimageprocess
cmake -S . -B build
cmake --build build

Build directly with g++

g++ -std=c++14 *.cpp -I. -o manualimageprocess

Run the compiled development executable with:

./manualimageprocess

On Windows, run:

manualimageprocess.exe

Minimal Library Usage

The processing classes can be used from a custom main.cpp as follows:

#include <iostream>
#include "ImageMatrix.h"
#include "ImageSharpening.h"
#include "EdgeDetector.h"

int main() {
    ImageMatrix image("image.txt");

    ImageSharpening sharpener;
    ImageMatrix sharpened = sharpener.sharpen(image, 2.0);

    EdgeDetector detector;
    auto edges = detector.detectEdges(sharpened);

    std::cout << "Detected edge pixels: " << edges.size() << '\n';
    return 0;
}

Technical Concepts Demonstrated

  • C++ class design and separation of responsibilities
  • Dynamic two-dimensional array allocation
  • Destructors and deep-copy semantics
  • Copy constructors and copy-assignment operators
  • Operator overloading
  • Kernel-based spatial filtering
  • Padding, stride, and output-shape calculation
  • Unsharp masking
  • Sobel gradient estimation
  • Threshold-based edge selection
  • Bitwise operations and LSB-based data representation
  • CMake-based multi-file project organisation

Current Status and Limitations

This repository is an educational implementation and remains a work in progress.

  • main.cpp is currently a minimal matrix-construction test, not a complete command-line interface.
  • Image input is limited to whitespace-separated grayscale matrices.
  • Processed images are not yet exported to standard image formats.
  • The encoding pipeline does not currently include capacity validation or robust message-length metadata.
  • The encoding method is not designed for security or cryptographic use.
  • Automated tests and benchmark comparisons have not yet been added.
  • Manual memory management can be modernised using standard containers and RAII.

Roadmap

  • Replace raw matrix ownership with std::vector or another RAII-based container
  • Add dimension checks to matrix arithmetic
  • Add a complete command-line interface
  • Support standard grayscale image formats
  • Export processed matrices as image files
  • Add unit tests for matrix, convolution, and edge-detection operations
  • Add message-capacity and bounds validation
  • Store message length explicitly for reliable decoding
  • Add before/after examples and numerical validation
  • Compare manual outputs with a reference implementation such as OpenCV

Why This Project Matters

High-level computer-vision libraries make convolution and edge detection easy to call, but they can hide the underlying mechanics. Implementing these operations manually provides practical insight into how image matrices, kernels, gradients, memory ownership, and bit-level transformations work.

The project therefore serves as a foundation for understanding both classical computer vision and the tensor operations used in modern machine-learning systems.

Author

Mehmet Ali Toy
Computer Engineering graduate, Hacettepe University


This repository is maintained as an educational and portfolio project.

About

Manual image processing project that I created the parts of classic image process by manual processes like convolution, sharpening etc.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages