image processing
object detection
R programming
computer vision
image analysis

R Count objects in a picture

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

Counting objects in an image is usually a segmentation problem, not a counting problem. In R, the usual workflow is to convert the image into a binary mask, clean that mask, label connected components, and count the labels that remain.

A Practical Workflow

For many pictures, the pipeline is the same:

  • load the image
  • convert to grayscale if color is not important
  • choose a threshold that separates foreground from background
  • remove small specks and fill small holes
  • label connected regions
  • optionally filter regions by size or shape

The EBImage package is a strong fit for this because it already provides the morphology and labeling functions needed for object counting.

Example With EBImage

The following example assumes a reasonably clean image where the objects are darker than the background. If your image has the opposite contrast, flip the threshold comparison.

r
1library(EBImage)
2
3img <- readImage("coins.png")
4gray <- channel(img, "gray")
5
6binary <- gray < 0.6
7clean <- opening(binary, makeBrush(5, shape = "disc"))
8clean <- fillHull(clean)
9
10labels <- bwlabel(clean)
11count <- max(labels)
12
13print(count)
14display(paintObjects(labels, img, col = "red"))

bwlabel assigns an integer id to each connected component. The largest label value is therefore the number of detected objects, assuming the mask is already clean enough that one real object maps to one connected region.

The overlay produced by paintObjects is worth checking every time. A numeric count without a visual check is risky because bad segmentation can still produce a plausible-looking number.

Choosing A Better Threshold

Thresholding is where most counting pipelines succeed or fail. A fixed threshold like 0.6 is simple, but lighting differences can make it unreliable across images. A data-driven threshold is safer when image brightness varies.

r
1library(EBImage)
2
3img <- readImage("cells.png")
4gray <- channel(img, "gray")
5
6cutoff <- otsu(gray)
7binary <- gray > cutoff
8
9labels <- bwlabel(binary)
10print(max(labels))

otsu estimates a threshold from the histogram. It works best when foreground and background intensities form reasonably separate groups. If the image has shadows, gradients, or cluttered texture, you may need local thresholding or more preprocessing first.

Remove Noise Before Counting

Raw threshold output often contains dust, reflections, or tiny fragments around edges. If you count immediately, each fragment may become a false object.

Morphological operations help stabilize the mask:

  • 'opening removes small bright artifacts and disconnects thin noise'
  • 'closing can bridge tiny gaps in an object boundary'
  • 'fillHull fills holes inside an object after thresholding'

A common pattern is:

r
1library(EBImage)
2
3img <- readImage("sample.png")
4gray <- channel(img, "gray")
5binary <- gray > 0.5
6
7clean <- opening(binary, makeBrush(3, shape = "disc"))
8clean <- closing(clean, makeBrush(5, shape = "disc"))
9clean <- fillHull(clean)
10
11labels <- bwlabel(clean)
12print(max(labels))

The brush size matters. A brush that is too small leaves noise behind. A brush that is too large can merge nearby objects into one region, which lowers the count.

Filter By Size Or Shape

Sometimes the mask still includes particles that are technically connected components but are not objects you want to count. In that case, compute region features and keep only labels that meet your criteria.

r
1library(EBImage)
2
3img <- readImage("cells.png")
4gray <- channel(img, "gray")
5binary <- gray > otsu(gray)
6labels <- bwlabel(fillHull(binary))
7
8features <- computeFeatures.shape(labels)
9keep <- which(features[, "s.area"] >= 200)
10filtered <- rmObjects(labels, setdiff(seq_len(max(labels)), keep))
11
12print(length(keep))
13display(colorLabels(filtered))

This is useful when dust, bubbles, or compression artifacts create tiny objects that pass the threshold but should not be counted. Area is the simplest filter, though shape descriptors can also help when the objects have a known geometry.

When The Simple Approach Is Not Enough

Connected-component counting works well when objects are separated. It breaks down when objects touch each other heavily, overlap, or fade into the background. In those cases, you may need edge detection, watershed segmentation, or a model-based approach instead of plain thresholding.

For example, a pile of touching coins may appear as one blob after thresholding. The count from bwlabel will then be too small even if the mask looks clean. That is not a coding bug; it means the segmentation method is too simple for the image structure.

Common Pitfalls

  • Using a fixed threshold on images with changing lighting. The count may drift from image to image.
  • Counting labels before cleaning the mask. Small specks and holes quickly distort the result.
  • Merging nearby objects with an oversized morphology brush. This undercounts real objects.
  • Trusting the number without displaying the labeled result. A visual overlay catches mistakes fast.
  • Assuming touching objects can be separated by thresholding alone. Some images need a more advanced segmentation step.

Summary

  • In R, object counting is usually done by segmenting first and counting connected components second.
  • 'EBImage gives you the core tools: thresholding, morphology, labeling, and feature extraction.'
  • A simple pipeline is readImage, channel, thresholding, mask cleanup, then bwlabel.
  • Filtering by area helps remove small false detections.
  • Always inspect a labeled overlay so the numeric count matches the image you actually care about.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.