This project implements classical binary segmentation and morphological image processing from scratch, without using specialized image-processing libraries (no OpenCV, skimage, scipy, etc).
Only Pillow (for I/O), NumPy (array storage), and Matplotlib (optional visualization) are used.
The goal is to:
-
Implement one segmentation method
-
Implement several morphological operations
-
Compare segmentation results before and after morphology
-
Output both images and numeric metrics
Segmentation via global thresholding often produces noisy binary masks, especially on natural images.
Morphological operators—erosion, dilation, opening, closing—can improve the mask by removing noise, filling holes, and simplifying boundaries.
This project performs:
-
Segmentation: Otsu’s automatic global threshold (from scratch)
-
Morphology:
-
Erosion
-
Dilation
-
Opening (erosion → dilation)
-
Closing (dilation → erosion)
-
All algorithms implemented manually with nested loops.
The input for this experiment was a photograph (Harambe image).
It was converted to 8-bit grayscale (0–255).
Otsu chooses a threshold
The image is then binarized:
if pixel > t*: foreground (1)
else: background (0)
Because foreground may be either bright or dark, the code evaluates both:
-
bright foreground =
$img > t$ -
dark foreground =
$img < t$
Then selects the mask with a better foreground ratio.
Otsu threshold: 101
Chosen polarity: bright>thr
Foreground ratio (bright>thr): 49.2%
Foreground ratio (dark<thr): 50.3%
This means the image is roughly balanced, so either polarity produces similar results, but the algorithm selected bright > threshold.
Using a 5×5 cross-shaped structuring element:
-
Erosion: Removes isolated foreground pixels and shrinks boundaries.
-
Dilation: Grows regions; fills small gaps.
-
Opening = erosion → dilation: Removes small bright noise (“salt noise”).
-
Closing = dilation → erosion: Fills small dark holes (“pepper noise”) and smooths shapes.
The script computes:
-
sum: number of foreground pixels
-
fg%: percentage of foreground
-
components: 4-connected components
-
perimeter: approximate boundary length (4-neighborhood)
Segmented (raw) sum= 1935120 fg%= 49.21 comps= 3100 perim= 188732
After Opening sum= 1860919 fg%= 47.33 comps= 838 perim= 153484
After Closing sum= 1893324 fg%= 48.15 comps= 803 perim= 123694
XOR Seg vs Open sum= 74201 fg%= 1.89 comps= 13334 perim= 65986
XOR Open vs Close sum= 51987 fg%= 1.32 comps= 9627 perim= 47532
Foreground components dropped dramatically:
| Stage | # Components |
|---|---|
| Raw Segmentation | 3100 |
| After Opening | 838 |
| After Closing | 803 |
This is a 74% reduction after opening and almost 75% after closing — meaning the mask becomes significantly cleaner.
Perimeter (4-neighbor boundary length) decreased:
| Stage | Perimeter |
|---|---|
| Raw | 188,732 |
| After Opening | 153,484 |
| After Closing | 123,694 |
Closing smooths the boundaries further (–34% vs raw).
XOR maps show how much morphology changed:
| Comparison | Changed Pixels | % | Interpretation |
|---|---|---|---|
| Seg → Open | 74,201 | 1.89% | Opening removed speckles / small blobs |
| Open → Close | 51,987 | 1.32% | Closing filled holes & gaps |
Even though these are small percentages, they happen in high-value regions (boundaries and noise clusters), noticeably improving segmentation quality.
-
Otsu segmentation alone produces a noisy binary mask on natural images.
-
Opening drastically removes small bright noise and reduces fragment clusters.
-
Closing fills small holes and smooths boundaries further.
-
Component count dropped from 3100 → 803 showing significant structural improvement.
-
Perimeter dropped from 188k → 123k, indicating smoother, cleaner shapes.
-
XOR maps confirm meaningful modifications while preserving overall structure.
In conclusion, morphological post-processing clearly produces a more stable, cleaner segmentation result.





