To illustrate, I've opened up this same color JPEG image:

once using the conversion
img = cv2.imread(path)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
and another by loading it in gray scale mode
img_gray_mode = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
Like you've documented, the diff between the two images is not perfectly 0, I can see diff pixels in towards the left and the bottom

I've summed up the diff too to see
import numpy as np
np.sum(diff)
# I got 6143, on a 494 x 750 image
I tried all cv2.imread() modes
Among all the IMREAD_ modes for cv2.imread(), only IMREAD_COLOR and IMREAD_ANYCOLOR can be converted using COLOR_BGR2GRAY, and both of them gave me the same diff against the image opened in IMREAD_GRAYSCALE
The difference doesn't seem that big. My guess is comes from the differences in the numeric calculations in the two methods (loading grayscale vs conversion to grayscale)
Naturally what you want to avoid is fine tuning your code on a particular version of the image just to find out it was suboptimal for images coming from a different source.
In brief, let's not mix the versions and types in the processing pipeline.
So I'd keep the image sources homogenous, e.g. if you have capturing the image from a video camera in BGR, then I'd use BGR as the source, and do the BGR to grayscale conversion cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
Vice versa if my ultimate source is grayscale then I'd open the files and the video capture in gray scale cv2.imread(path, cv2.IMREAD_GRAYSCALE)
python - Opencv - Grayscale mode Vs gray color conversion - Stack Overflow
c++ - How can I convert a cv::Mat to a gray scale in OpenCv? - Stack Overflow
Convert RGB into Grayscale - C++ - OpenCV
How does one convert a grayscale image to RGB in OpenCV (Python)? - Stack Overflow
Videos
To illustrate, I've opened up this same color JPEG image:

once using the conversion
img = cv2.imread(path)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
and another by loading it in gray scale mode
img_gray_mode = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
Like you've documented, the diff between the two images is not perfectly 0, I can see diff pixels in towards the left and the bottom

I've summed up the diff too to see
import numpy as np
np.sum(diff)
# I got 6143, on a 494 x 750 image
I tried all cv2.imread() modes
Among all the IMREAD_ modes for cv2.imread(), only IMREAD_COLOR and IMREAD_ANYCOLOR can be converted using COLOR_BGR2GRAY, and both of them gave me the same diff against the image opened in IMREAD_GRAYSCALE
The difference doesn't seem that big. My guess is comes from the differences in the numeric calculations in the two methods (loading grayscale vs conversion to grayscale)
Naturally what you want to avoid is fine tuning your code on a particular version of the image just to find out it was suboptimal for images coming from a different source.
In brief, let's not mix the versions and types in the processing pipeline.
So I'd keep the image sources homogenous, e.g. if you have capturing the image from a video camera in BGR, then I'd use BGR as the source, and do the BGR to grayscale conversion cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
Vice versa if my ultimate source is grayscale then I'd open the files and the video capture in gray scale cv2.imread(path, cv2.IMREAD_GRAYSCALE)
cv2.imread(path, 0) (or rather cv2.imread(path, cv2.IMREAD_GRAYSCALE)) asks the image file reading code to load the image as grayscale. For most file types, some 3rd party library is used to read them. If this library supports grayscale conversion, we’ll be using that library’s conversion routine.
Otherwise, we’re using OpenCV’s implementation of the conversion to grayscale.
There’s no reason to assume one implementation is better than the other, the differences observed are likely due to a different computation order, or to a different assumption about the whitepoint. But note that if the file has a color profile embedded, the 3rd party library might be able to use it to do the conversion, and so will have whitepoint information available. This color profile does not get loaded into OpenCV, so OpenCV will always make an assumption about the whitepoint.
Reading the image directly as grayscale is possibly a bit more efficient. If the 3rd party library converts each pixel as it’s read, we won’t need a temporary memory space to store the full color image (which takes up 3x as much memory as the grayscale image). For a format such as JPEG, which stores intensity and color information separately, reading as grayscale also avoids a lot of computation (we’re directly outputting the intensity value, rather than computing the RGB values and then converting those back to intensity).
Reading directly as grayscale has the possibility of giving different results if the image file gets converted to a different format, as then a different grayscale conversion will be used. Say, you convert your JPEG file to PNG, then using IMREAD_GRAYSCALE will use different libraries to do the grayscale conversion, using OpenCV’s conversion code will ensure both files read identically.
Using the C++ API, the function name has slightly changed and it writes now:
#include <opencv2/imgproc/imgproc.hpp>
cv::Mat greyMat, colorMat;
cv::cvtColor(colorMat, greyMat, CV_BGR2GRAY);
The main difficulties are that the function is in the imgproc module (not in the core), and by default cv::Mat are in the Blue Green Red (BGR) order instead of the more common RGB.
OpenCV 3
Starting with OpenCV 3.0, there is yet another convention.
Conversion codes are embedded in the namespace cv:: and are prefixed with COLOR.
So, the example becomes then:
#include <opencv2/imgproc/imgproc.hpp>
cv::Mat greyMat, colorMat;
cv::cvtColor(colorMat, greyMat, cv::COLOR_BGR2GRAY);
As far as I have seen, the included file path hasn't changed (this is not a typo).
May be helpful for late comers.
#include "stdafx.h"
#include "cv.h"
#include "highgui.h"
using namespace cv;
using namespace std;
int main(int argc, char *argv[])
{
if (argc != 2) {
cout << "Usage: display_Image ImageToLoadandDisplay" << endl;
return -1;
}else{
Mat image;
Mat grayImage;
image = imread(argv[1], IMREAD_COLOR);
if (!image.data) {
cout << "Could not open the image file" << endl;
return -1;
}
else {
int height = image.rows;
int width = image.cols;
cvtColor(image, grayImage, CV_BGR2GRAY);
namedWindow("Display window", WINDOW_AUTOSIZE);
imshow("Display window", image);
namedWindow("Gray Image", WINDOW_AUTOSIZE);
imshow("Gray Image", grayImage);
cvWaitKey(0);
image.release();
grayImage.release();
return 0;
}
}
}
I am promoting my comment to an answer:
The easy way is:
You could draw in the original 'frame' itself instead of using gray image.
The hard way (method you were trying to implement):
backtorgb = cv2.cvtColor(gray,cv2.COLOR_GRAY2RGB)
is the correct syntax.
Alternatively, cv2.merge() can be used to turn a single channel binary mask layer into a three channel color image by merging the same layer together as the blue, green, and red layers of the new image. We pass in a list of the three color channel layers - all the same in this case - and the function returns a single image with those color channels. This effectively transforms a grayscale image of shape (height, width, 1) into (height, width, 3)
To address your problem
I did some thresholding on an image and want to label the contours in green, but they aren't showing up in green because my image is in black and white.
This is because you're trying to display three channels on a single channel image. To fix this, you can simply merge the three single channels
image = cv2.imread('image.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray_three = cv2.merge([gray,gray,gray])
Example
We create a color image with dimensions (200,200,3)

image = (np.random.standard_normal([200,200,3]) * 255).astype(np.uint8)
Next we convert it to grayscale and create another image using cv2.merge() with three gray channels
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gray_three = cv2.merge([gray,gray,gray])
We now draw a filled contour onto the single channel grayscale image (left) with shape (200,200,1) and the three channel grayscale image with shape (200,200,3) (right). The left image showcases the problem you're experiencing since you're trying to display three channels on a single channel image. After merging the grayscale image into three channels, we can now apply color onto the image

contour = np.array([[10,10], [190, 10], [190, 80], [10, 80]])
cv2.fillPoly(gray, [contour], [36,255,12])
cv2.fillPoly(gray_three, [contour], [36,255,12])
Full code
import cv2
import numpy as np
# Create random color image
image = (np.random.standard_normal([200,200,3]) * 255).astype(np.uint8)
# Convert to grayscale (1 channel)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Merge channels to create color image (3 channels)
gray_three = cv2.merge([gray,gray,gray])
# Fill a contour on both the single channel and three channel image
contour = np.array([[10,10], [190, 10], [190, 80], [10, 80]])
cv2.fillPoly(gray, [contour], [36,255,12])
cv2.fillPoly(gray_three, [contour], [36,255,12])
cv2.imshow('image', image)
cv2.imshow('gray', gray)
cv2.imshow('gray_three', gray_three)
cv2.waitKey()
Hello
I have a 3 channel Grayscale image (28 by 28 pixel, .jpg) I want to turn it into 1 channel image, which means keep it grayscale, keep all the details and simply get rit of the 2 extra channels here is an example of what I have:
file = r"path" test_image = cv2.imread(file) # Preview sample image plt.imshow(test_image)
results:
image example print('shape of test_image: ', test_image.shape , ' height :',test_image.shape[0] , ' width :', test_image.shape[1])
shape of test_image: (28, 28, 3) height : 28 width : 28 PS: I m using OpenCV for image preprocessing and python 3.8
Thank you for your time!