OpenCV's cv::Mat to JUCE's Image

Before I go re-invent the wheel, does anyone have a function to convert a 3-channel OpenCV cv::Mat to JUCE’s Image?

Example function that manipulates a OpenCV cv::Mat object and returns a JUCE Image object:

Image convert_opencv_mat_to_juce_image()
{
	const std::string filename	= "samples/data/HappyFish.jpg";
	const int desired_width		= 320;
	const int desired_height	= 200;

	// read in an example image, either colour or greyscale to ensure
	// we can handle both when converting a cv::Mat to a JUCE Image
	cv::Mat original = cv::imread(filename, cv::IMREAD_COLOR); // cv::IMREAD_GRAYSCALE

	// resize the image to the final dimensions we'll need (see size defined at top of function)
	cv::Mat resized;
	cv::resize(original, resized, cv::Size(desired_width, desired_height), 0, 0, cv::INTER_AREA);

	// if the image has 1 channel we'll convert it to greyscale RGB
	// if the image has 3 channels (RGB) then all is good,
	// (anything else will cause this function to throw)
	cv::Mat source;
	switch (resized.channels())
	{
		case 1:
			cv::cvtColor(resized, source, cv::COLOR_GRAY2BGR);
			break;
		case 3:
			source = resized;	// nothing to do, use the image as-is
			break;
		default:
			throw std::logic_error("cv::Mat has an unexpected number of channels (" + std::to_string(resized.channels()) + ")");
	}

	// create a JUCE Image the exact same size as the OpenCV mat we're using as our source
	Image image(Image::RGB, source.cols, source.rows, false);

	// iterate through each row of the image, copying the entire row as a series of consecutive bytes
	const size_t number_of_bytes_to_copy = 3 * source.cols; // times 3 since each pixel contains 3 bytes (RGB)
	Image::BitmapData bitmap_data(image, 0, 0, source.cols, source.rows, Image::BitmapData::ReadWriteMode::writeOnly);
	for (int row_index = 0; row_index < source.rows; row_index ++)
	{
		uint8_t * src_ptr = source.ptr(row_index);
		uint8_t * dst_ptr = bitmap_data.getLinePointer(row_index);

		std::memcpy(dst_ptr, src_ptr, number_of_bytes_to_copy);
	}

	return image;
}

I’m running on Intel/AMD aka little-endian hardware. If your specific hardware uses a different format for byte ordering between OpenCV and JUCE, then I think inserting this line after the switch block will resolve the red-blue channel swap:

cv::cvtColor(source, source, cv::COLOR_BGR2RGB); // BGR -> RGB
2 Likes