Hello my friends! How are you? It’s been a long time since the last time I posted an article! In this one I would like to present you some digital image processing algorithms implemented with C++ and OpenCV.

Although, OpenCV supports most of these algorithms out-of-the-box what I am trying to show you actually is how you could implement these manually with C++. So, OpenCV is used here just only for opening and manipulating an image as a Mat object (OpenCV’s basic image container).

Below, are the digital image processing algorithms:

A. Adding noise to images

  • Gaussian noise
  • Salt & Pepper noise

B. Removing noise from images (using filters)

  • Mean filter
  • Median filter

C. Detecting edges in images (using detectors)

  • Prewitt detector
  • Sobel detector

D. Segmenting an image

  • Adaptive thresholding

The original input images that we have used with these algorithms are the following:

Flower (RGB image)

Flower (RGB image)

Flower (Grayscale image)

Flower (Grayscale image)

Lenna (RGB image)

Lenna (RGB image)

Lenna (Grayscale image)

Lenna (Grayscale image)

Rice (Grayscale image)

Rice (Grayscale image)

The output images generated by the algorithms are the following:

Gaussian noise (Flower grayscale image)

Gaussian noise (Flower grayscale image)

Gaussian noise (Flower RGB image)

Gaussian noise (Flower RGB image)

Salt & Pepper noise (Lenna grayscale image)

Salt & Pepper noise (Lenna grayscale image)

Salt & Pepper noise (Lenna RGB image)

Salt & Pepper noise (Lenna RGB image)

Mean filter (Flower grayscale image)

Mean filter (Flower grayscale image)

Mean filter (Flower RGB image)

Mean filter (Flower RGB image)

Median filter (Lenna grayscale image)

Median filter (Lenna grayscale image)

Median filter (Lenna RGB image)

Median filter (Lenna RGB image)

Sobel edge detector (Lenna RGB image)

Sobel edge detector (Lenna RGB image)

Sobel edge detector with thresholding (Lenna RGB image)

Sobel edge detector with thresholding (Lenna RGB image)

Prewitt edge detector (Flower RGB image)

Prewitt edge detector (Flower RGB image)

Prewitt edge detector with thresholding (Flower RGB image)

Prewitt edge detector with thresholding (Flower RGB image)

Adaptive thresholding (Rice grayscale image)

Adaptive thresholding (Rice grayscale image)

Now, let’s see the C++ implementation of the algorithms:

A. Adding noise to images

Gaussian noise

void gaussianNoise(Mat & image, const unsigned char mean, const unsigned char sigma)
{
	default_random_engine generator;

	normal_distribution distribution(mean, sigma);

	int imageChannels = image.channels();

	for (int row = 0; row < image.rows; row++)
	{
		for (int column = 0; column < image.cols; column++)
		{
			for (int channel = 0; channel < imageChannels; channel++)
			{
				unsigned char * pixelValuePtr = image.ptr(row) + (column * imageChannels) + channel;

				long newPixelValue = *pixelValuePtr + distribution(generator);

				*pixelValuePtr = newPixelValue > 255 ? 255 : newPixelValue < 0 ? 0 : newPixelValue;
			}
		}
	}
}

Salt & Pepper noise (in grayscale images)

void saltAndPepperNoise(Mat & image, const double noiseProbability)
{
	int imageChannels = image.channels();

        random_value_generator random;

	long noisePoints = noiseProbability * image.rows * image.cols * imageChannels;

	for (long i = 0; i < noisePoints; i++)
	{
		int row = random.get_integer(image.rows);

		int column = random.get_integer(image.cols);

		int channel = random.get_integer(imageChannels);

		unsigned char * pixelValuePtr = image.ptr(row) + (column * imageChannels) + channel;

		*pixelValuePtr = random.get_boolean() ? 255 : 0;
	}
}

Salt & Pepper noise (in RGB images)

void saltAndPepperNoise(Mat & image, const double noiseProbability)
{
	int imageChannels = image.channels();

        random_value_generator random; 

	long noisePoints = noiseProbability * image.rows * image.cols * imageChannels;

	for (long i = 0; i < noisePoints; i++)
	{
		int row = random.get_integer(image.rows);

		int column = random.get_integer(image.cols);

		unsigned char newValue = random.get_boolean() ? 255 : 0;

		for (int channel = 0; channel < imageChannels; channel++)
		{
			unsigned char * pixelValuePtr = image.ptr(row) + (column * imageChannels) + channel;

			*pixelValuePtr = newValue;
		}
	}
}

B. Removing noise from images (using filters)

Mean filter

void meanFiltering(Mat & image, const int kernelSize)
{
	Mat tempImage;

	image.copyTo(tempImage);

	int totalKernelElements = kernelSize * kernelSize;

	vector kernel(totalKernelElements, 1.0 / totalKernelElements);

	int imageChannels = image.channels();

	vector<vector> values(imageChannels);

	int halfSize{ kernelSize / 2 };

	for (int i{ halfSize }; i < tempImage.rows - halfSize; i++)
	{
		for (int j{ halfSize }; j < tempImage.cols - halfSize; j++)
		{
			for (int channel = 0; channel < imageChannels; channel++)
			{
				values[channel].clear();
			}

			for (int x = { -halfSize }; x <= halfSize; x++)
			{
				for (int y = { -halfSize }; y <= halfSize; y++)
				{
					for (int channel = 0; channel < imageChannels; channel++)
					{
						unsigned char * pixelValuePtr = tempImage.ptr(i + x) + ((j + y) * imageChannels) + channel;

						values[channel].push_back(*pixelValuePtr);
					}
				}
			}

			for (int channel = 0; channel < imageChannels; channel++)
			{
				vector channelValues = values[channel];

				long newPixelValue = inner_product(begin(channelValues), end(channelValues), begin(kernel), 0.0);

				unsigned char * pixelValuePtr = image.ptr(i) + (j * imageChannels) + channel;

				*pixelValuePtr = newPixelValue > 255 ? 255 : newPixelValue < 0 ? 0 : newPixelValue;
			}
		}
	}
}

Median filter

void medianFiltering(Mat & image, const int kernelSize)
{
	Mat tempImage;

	image.copyTo(tempImage);

	int imageChannels = image.channels();

	vector<vector> values (imageChannels);

	int halfSize{ kernelSize / 2 };

	int pos = { kernelSize * kernelSize / 2 };

	for (int i{ halfSize }; i < tempImage.rows - halfSize; i++)
	{
		for (int j{ halfSize }; j < tempImage.cols - halfSize; j++)
		{
			for (int channel = 0; channel < imageChannels; channel++)
			{
				values[channel].clear();
			}

			for (int x = { -halfSize }; x <= halfSize; x++)
			{
				for (int y = { -halfSize }; y <= halfSize; y++)
				{
					for (int channel = 0; channel < imageChannels; channel++)
					{
						unsigned char * pixelValuePtr = tempImage.ptr(i + x) + ((j + y) * imageChannels) + channel;

						values[channel].push_back(*pixelValuePtr);
					}
				}
			}

			for (int channel = 0; channel < imageChannels; channel++)
			{
				sort(begin(values[channel]), end(values[channel]));

				unsigned char * pixelValuePtr = image.ptr(i) + (j * imageChannels) + channel;

				*pixelValuePtr = values[channel][pos];
			}
		}
	}
}

C. Detecting edges in images (using detectors)

Prewitt detector

void prewittEdgeDetection(Mat & image, const unsigned char threshold)
{
        vector xKernel = { -1, 0, 1, -1, 0, 1, -1, 0, 1 };

        vector yKernel = { -1, -1, -1, 0, 0, 0, 1, 1, 1 }; 

	edgeDetection(image, xKernel, yKernel);

	image = image > threshold;
}

Sobel detector

void sobelEdgeDetection(Mat & image, const unsigned char threshold)
{
        vector xKernel = { -1, 0, 1, -2, 0, 2, -1, 0, 1 };

        vector yKernel = { -1, -2, -1, 0, 0, 0, 1, 2, 1 };

        edgeDetection(image, xKernel, yKernel);

        image = image > threshold;
}

Filter-based edge detector

void edgeDetection(Mat & image, vector & xKernel, vector & yKernel)
{
	Mat tempImage;

	if (image.channels() != 1)
	{
		cvtColor(image, tempImage, COLOR_BGR2GRAY);

		tempImage.copyTo(image);
	}
	else
	{
		image.copyTo(tempImage);
	}

	int kernelSize = sqrt (xKernel.size());

	vector values;

	int halfSize{ kernelSize / 2 };

	for (int i{ halfSize }; i < tempImage.rows - halfSize; i++)
	{
		for (int j{ halfSize }; j < tempImage.cols - halfSize; j++)
		{
			values.clear();

			for (int x = { -halfSize }; x <= halfSize; x++)
			{
				for (int y = { -halfSize }; y <= halfSize; y++)
				{
					unsigned char * pixelValuePtr = tempImage.ptr(i + x) + (j + y);

					values.push_back(*pixelValuePtr);
				}
			}

			long gX = inner_product(begin(values), end(values), begin(xKernel), 0);

			long gY = inner_product(begin(values), end(values), begin(yKernel), 0);

			long newPixelValue = abs(gX) + abs(gY);

			unsigned char * pixelValuePtr = image.ptr(i) + j;

			*pixelValuePtr = newPixelValue > 255 ? 255 : newPixelValue < 0 ? 0 : newPixelValue;
		}
	}
}

D. Segmenting an image

Adaptive thresholding

void adaptiveThresholdingSegmentation(Mat & image, const int kernelSize)
{
    Mat tempImage;

    if (image.channels() != 1)
    {
        cvtColor(image, tempImage, COLOR_BGR2GRAY);

        tempImage.copyTo(image);
    }
    else
    {
        image.copyTo(tempImage);
    }

    int totalKernelElements = kernelSize * kernelSize;

    vector kernel(totalKernelElements, 1.0 / totalKernelElements);

    vector values;

    int halfSize{ kernelSize / 2 };

    for (int i{ halfSize }; i < tempImage.rows - halfSize; i++)
    {
        for (int j{ halfSize }; j < tempImage.cols - halfSize; j++)
        {
            values.clear();

            for (int x = { -halfSize }; x <= halfSize; x++)
            {
                for (int y = { -halfSize }; y <= halfSize; y++)
                {
                    unsigned char * pixelValuePtr = tempImage.ptr(i + x) + (j + y);

                    values.push_back(*pixelValuePtr);
                }
            }

            long averageValue = inner_product(begin(values), end(values), begin(kernel), 0.0);

            unsigned char * pixelValuePtr = image.ptr(i) + j;

            *pixelValuePtr = *pixelValuePtr > averageValue ? 0 : 255;
        }
    }
}

Happy coding until next time!