C: grayscale histogram and a global threshold
Count how many pixels have each brightness level and separate dark from bright pixels at the mean.
#include <stdio.h>
int main(void) {
unsigned char image[4][4] = {
{12, 20, 200, 210},
{15, 25, 220, 205},
{10, 30, 215, 230},
{18, 22, 225, 240},
};
int bins[8] = {0};
int total = 0;
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
bins[image[r][c] / 32]++;
total += image[r][c];
}
}
int mean = total / 16;
printf("histogram:");
for (int b = 0; b < 8; b++) {
printf(" %d", bins[b]);
}
printf("\nmean brightness: %d\n", mean);
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
putchar(image[r][c] > mean ? '#' : '.');
}
putchar('\n');
}
return 0;
}histogram: 8 0 0 0 0 0 5 3 mean brightness: 118 ..## ..## ..## ..##
Explanation
A histogram counts, for each brightness level, how many pixels have that value. It summarizes an image's tonal range without storing its layout. A cluster of dark values and a cluster of bright values show up as two peaks, which suggests a natural place to separate foreground from background.
The simplest global threshold is the mean brightness. Pixels above it become foreground (1) and the rest background (0). Methods such as Otsu's choose the threshold that best separates the two groups. This program uses 8 brightness bins of width 32 to keep the output short and then thresholds a small 4×4 image at its mean.
Explore the dry run
#include <stdio.h> int main(void) { unsigned char image[4][4] = { {12, 20, 200, 210}, {15, 25, 220, 205}, {10, 30, 215, 230}, {18, 22, 225, 240}, }; int bins[8] = {0}; int total = 0; for (int r = 0; r < 4; r++) { for (int c = 0; c < 4; c++) { bins[image[r][c] / 32]++; total += image[r][c]; } } int mean = total / 16; printf("histogram:"); for (int b = 0; b < 8; b++) { printf(" %d", bins[b]); } printf("\nmean brightness: %d\n", mean); for (int r = 0; r < 4; r++) { for (int c = 0; c < 4; c++) { putchar(image[r][c] > mean ? '#' : '.'); } putchar('\n'); } return 0;}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Each pixel is placed in a bin by its brightness: 0–31 in bin 0, 32–63 in bin 1, and so on. | — | — | |
| The mean of the 16 pixels is the threshold: (152 + 1745) / 16 = 118. | mean = 118 | — | |
| Eight pixels are dark (bin 0) and eight are bright (bins 6 and 7). The mean lies between the two clusters. | mean = 118 | histogram: 8 0 0 0 0 0 5 3 mean brightness: 118 | |
| Pixels brighter than 118 become #, the rest a dot. | mean = 118 | histogram: 8 0 0 0 0 0 5 3 mean brightness: 118 ..## ..## ..## ..## |
Cost and common mistakes
O(p) time for p pixels and O(bins) extra space.
- A pixel value of 255 divided by 32 is 7, so 8 bins of width 32 cover 0 to 255 with no out-of-range index.
- A mean threshold works when the dark and bright groups are about the same size. Otherwise a method like Otsu's is better.
- unsigned char holds 0 to 255. Adding pixels needs a wider type such as int.
Try it yourself
How many bins would an exact 256-level histogram need?
Reveal answer and reasoning
256 bins, one for each possible 8-bit brightness value.