Counting
Black and White Pixels in a Digital Image
David R. Brooks, Ph.D.,
Research Professor, Department of Mechanical Engineering and
Mechanics
Drexel University
© October 2005
For her 8th grade science fair project,
my daughter decided to examine the effect of changing tree canopy
on the amount of solar energy reaching the ground. She wants
to compare pyranometer (solar irradiance) data in an open area
against data taken in a wooded area with deciduous trees. Starting
in the fall, the amount of sunlight decreases rapidly with the
approach of winter, but the trees lose their leaves at the same
time. Does this mean that the amount of sunlight will actually
increase in the wooded area? I
suggested that she regularly take digital pictures from the
pyranometer site, straight up through the canopy, using the
widest field of view available on the camera. This gives a
visual record of leaf cover, but how can she convert this
to a quantitative value? The solution is to convert the image
to a black-and-white bitmap image and count the black and
white pixels.
I use the freeware IrfanView
photo editing program on my computer, and one of its image
manipulation options is to produce a two-color black-and-white
image. For the purposes of my daughter's project, it is not
terribly important how the software makes a "black or white"
decision for a particular pixel, but only that it does it
consistently. Also, I used an option to resize the image,
which will force the software to do some pixel averaging.
Again, it is only consistency that is important. My digital
camera saves images in .jpg format, but you can save the image
from IrfanView in a number of other formats, including .bmp.
The next step is to write a program
that will process the .bmp image. There are many online sources
of information about the format of .bmp files -- just do an
online search using .bmp and format as search terms. Here is the page
I used, from Paul Bourke. (I didn't use any of his code.)
Basically, there are some "header" records that give information
about the file and image, followed by the image itself. When
IrfanView saves the image in a two-color file, the image part
of the file holds 8 pixels per byte -- that is, a pixel is
either "on" (white) or "off" black. Note that a two-color
image is much smaller than the original image because
it requires only one bit (1/8 byte) per pixel instead of as
many as 24 bits (3 bytes) per pixel (for "24-bit color").
To make the programming easier, I required
that the resized file have a width (in pixels) that is evenly
divisible by 8. I chose to resize images to 504 pixels wide
-- by default, IrfanView resizes the height of the image to
keep the same image shape. This is a reasonable size for images
displayed directly on a Web page without additional resizing.
A full-color image from 15 October, 2005, resized to 504 pixels
wide, is shown in Fig. 1.

Figure 1. Wooded site photographed
straight up from pyranometer site on 15 October 2005.
The derived black and white image is
shown in Fig. 2. The leaves have not yet started to change
color and drop from the trees. Analysis of the black and white
version of this image yields values of about 68% "canopy"
and 32% "sky."

Figure 2. Derived black and white image
for 15 October 2005.
I wrote the program bitmap.c in standard
C (with Microsoft's very old but still reliable MS-DOS
QuickC compiler), using the simplest approach I could think
of. Each byte is read as a single character and converted
to an "unsigned integer" as required. Larger integer values,
such as the size of the file in bytes, are extracted "manually"
by interpreting individual bytes rather than trying to declare
a variable type that occupies the required number of bytes.
I reasoned that the latter option might work differently with
different C compilers and might therefore give unreliable
results. By processing the file one byte at a time, using
a char data type, and knowing which values are located where
in the file, I can keep control over the interpretation of
the file contents. A separate function is written to extract
the "on/off" pixel information from each byte in the image.
Here is the source code for the C program.
/* Program bitmap.c reads uncompressed .bmp files.
Copyright 2005, David R. Brooks.
Written with Microsoft QuickC.
This program can be used without restriction for any educational
or other non-commercial purpose, but please acknowledge the source.
*/
#include
#include
#include
#include
void bw_count(unsigned char pixel, unsigned long *blk, unsigned long *wht);
void main() {
FILE *in;
unsigned char ch[50],p_ch;
char filename[20],yes_no;
unsigned int k,i,bits_per_pixel,offset;
unsigned long ht,wt,n_bytes;
unsigned long blk,wht,sum_blk,sum_wht;
/* File name no more than 8 characters, not including extension. */
printf("Give file name without its .bmp extension: " );
scanf("%s",filename);
strcat(filename,".bmp"); /* Add .bmp file extension to name. */
in=fopen(filename,"r");
if (in == NULL) {
printf("Can't find file. Abort.\n");
exit(1);
}
/* Read 14-byte file header. */
i=0;
printf("File header: \n");
while (i<14) {
fscanf(in,"%c",&ch[i]);
printf("%i ",ch[i]);
i++;
}
printf("\n");
printf("file size = %li\n",
(long)ch[4]*65536+(long)ch[3]*256+(long)ch[2]);
offset=(int)ch[11]*256+(int)ch[10];
printf("offset to image = %i\n",offset);
/* Read 40-byte image information header. */
i=0;
printf("Image header: \n");
while (i<40) {
fscanf(in,"%c",&ch[i]);
printf("%i ",ch[i]);
i++;
} printf("\n");
wt=(long)ch[6]*65536+(long)ch[5]*256+(long)ch[4];
ht=(long)ch[10]*65536+(long)ch[9]*256+(long)ch[8];
printf("image width, height, size: %li %li %li\n",wt,ht,wt*ht);
printf("image size from header: %li\n",
(long)ch[22]*65536+(long)ch[21]*256+(long)ch[20]);
bits_per_pixel=(int)ch[14];
printf("bits per pixel = %i\n",bits_per_pixel);
/* printf("number of colors = %i\n",(int)ch[33]*256+(int)ch[2]); */
fclose(in);
printf("Read image now? (y or n) ");
fflush(stdin);
scanf("%c",&yes_no);
if (yes_no == 'y') {
/* Open file as binary to read image. */
in=fopen(filename,"rb");
fseek(in,offset,SEEK_SET); /* Go to first image byte. */
printf("Enter -1 to read entire file, or # of bytes: ");
scanf("%li",&n_bytes);
if (n_bytes != -1) {
/* Read specified number of bytes. */
for (i=1; i<=n_bytes; i++) {
fread(&p_ch,1,1,in);
printf("%i ",p_ch);
}
}
else {
/* Read entire file, one row at a time. */
blk=0; wht=0; sum_blk=0; sum_wht=0; /* Initialize for B&W pixels. */
for (i=1; i<=ht; i++) {
printf("\nRow # %4i\n",i);
for (k=1; k<=wt/8; k++) {
fread(&p_ch,1,1,in);
printf("%i ",p_ch);
if (bits_per_pixel == 1) { /* Count black and white pixels. */
bw_count(p_ch,&blk,&wht);
sum_blk+=blk; sum_wht+=wht;
}
}
fread(&p_ch,1,1,in); /* Read end-of-row byte. */
}
printf("\n"); /* End of row. */
}
printf("\n... done\n");
fclose(in);
} /* End of read file loop. */
/* Display black and white pixel count. */
if ((bits_per_pixel == 1) && (n_bytes == -1))
printf("black white count: %li %li\n",sum_blk,sum_wht);
printf("Percent black and white: %.1lf %.1lf\n",
100.*sum_blk/(sum_blk+sum_wht),100.*sum_wht/(sum_blk+sum_wht));
fflush(stdin);
printf("Press any digit or character to quit... ");scanf("%c",&p_ch);
}
void bw_count(unsigned char p, unsigned long *blk, unsigned long *wht) {
/* For a black and white image (8 pixels per byte, either 0 or 1),
this function counts black and white pixels per byte.
*/
int i;
unsigned long n=1,b=0,w=0;
b=0; w=0;
for (i=0; i<=7; i++) {
/* printf("%li %li\n",n,p&n); */
if ((long)(p&n) == n) w++; else b++; /* Apply bitwise AND operator. */
n*=2;
}
*blk=b; *wht=w;
}
The program asks you to enter the name of the file and then displays some properties
of the file. You have the option to process part or all of a file. If you process
the entire file, the program displays the number of black and white pixels,
and the percentage of each. Figures 3 and 4 show some of the program's output
for the 15 October image shown in Fig. 1.

Figure 3. Some of the program's output for the 15 October
image shown in Fig. 1.

Figure 4. More of the program's output.
You are welcome to use this program, with or without modification,
for your own personal and educational purposes. You may not
use this program for any commercial purpose without my written
permission. The program code is offered as-is, without any
promises of any kind about its performance or applicability
to your needs. Please give an appropriate citation whenever
referring to this program:
bitmap.c written by David R. Brooks,
2005. 
|