-1

I have a simple code that loads RGB image, converts it to grayscale and then runs Canny edge detector algorithm. The returned image contains only 0 and 255 values, and yet when showing the image using matplotlib it shows the image as grayscale (and not black and white).

How can I fix this?

My code -

import cv2
import matplotlib.pyplot as plt

in_img = cv2.imread('colored_image.jpeg')
gray_in_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray_in_img, 100, 155)
fig = plt.figure(1)
ax = fig.add_subplot(1, 3, 1)
ax.imshow(cv2.cvtColor(in_img, cv2.COLOR_BGR2RGB))
ax = fig.add_subplot(1, 3, 2)
ax.imshow(gray_in_img, cmap='gray')
ax = fig.add_subplot(1, 3, 3)
ax.imshow(edges, cmap='gray')
plt.show()

The output figure is:

RGB image, grayscale image, and edges image

Zooming in the last image we can see that in contains variety of gray intensities instead of black and white only:

enter image description here

whereas I'd like the last image to be a black and white image, such as:

enter image description here

When I've debugged the code, I've checked that the values of edges are indeed only 0 and 255.

The original RGB image:

enter image description here

Red
  • 26,798
  • 7
  • 36
  • 58
ChikChak
  • 936
  • 19
  • 44
  • @john-hen When I added `ax.imshow([[255, 0], [0, 255]], cmap='gray')` I've gotten 4 squares - white, black, black and white as expected. – ChikChak Nov 11 '21 at 09:16
  • Adjust the image size of the subplots and remove padding, which will more clearly show the edges, can be done with [Displaying different images with actual size in matplotlib subplot](https://stackoverflow.com/q/28816046/7758804) and [Removing white space around a saved image](https://stackoverflow.com/q/11837979/7758804), which doesn't require Tkinter. [Code](https://i.stack.imgur.com/6wsHY.png) and [Plot](https://i.stack.imgur.com/C6TXf.jpg). Essentially there isn't an issue, and making the image larger and removing the white boarder already have answers. So this is a duplicate. – Trenton McKinney Nov 20 '21 at 00:04

2 Answers2

1

The edges image is black and white only:

import numpy as np

np.unique(edges)
#array([  0, 255], dtype=uint8)

When you zoom in on the interactive backend, you'll see that the edges are indeed black and white only.

By default, imshow uses 'antialiased' interpolation which leads to some gray values for small scale images. You can use 'none' or 'nearest' instead to prevent this:

ax.imshow(edges, cmap='gray', interpolation='nearest')

When you save the image to a compressed format (e.g. png) you'll also see some gray values in the saved image due to compression. You can prevent this by using a lossless compression or no compression, e.g. save as tif or as png with pil_kwargs={'compress_level': 0}.


Update as per discussion in comments below:
To see the full exact image with all details in pure black and white you need to scale your edges image so that 1 image pixel = 1 display pixel (you can use tkinter to get your screen's dpi setting):

import tkinter
dpi = tkinter.Tk().winfo_fpixels('1i')

fig,ax = plt.subplots(dpi=dpi)
plt.axis('off')                                              # no ticks/tick labels
fig.subplots_adjust(0,0,1,1)                                 # no margins
fig.set_size_inches(edges.shape[1]/dpi, edges.shape[0]/dpi)  # 1 dot = 1 pixel
ax.imshow(edges, cmap='gray')

Result (to be viewed at 100 % zoom of your web browser)

Stef
  • 28,728
  • 2
  • 24
  • 52
  • Using `interpolation='nearest'`, I get the following: https://ibb.co/JQQxZJV, which is OK for the black&white issue, however it seems like the figure isn't true to the original - the edges are much fewer. I understand that using `np.unique` I can be sure that the image contains only `0` and `255`, however I don't understand why can't I view the image as the image I've added in the post (the one before the last). – ChikChak Nov 13 '21 at 21:16
  • If you show all 3 images side by side, the displayed image is just too small to have 1 image pixel = 1 display pixel, therefore you need interpolation (antialiasing) for a pleasant visual impression or - if you switch it off - you'll loose some details. If you show your third image large enough (or zoom in) you'll get something like the last but one image in your post. – Stef Nov 13 '21 at 21:22
  • you can easily verify this effect by opening the last but one image in your post in a new tab (firefox: right click on image -> open image in new tab): at 100 % it'll be crisp black and with, as soon as you zoom in or out a bit (say 110 or 90 %) the bright white lines turn gray. – Stef Nov 13 '21 at 21:27
0

I would suggest setting the interpolation keyword argument in the ax.imshow() method to "none", and enlarging the subplots with the figsize keyword argument to show more details:

import cv2
import matplotlib.pyplot as plt

in_img = cv2.imread('colored_image.png')
gray_in_img = cv2.cvtColor(in_img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray_in_img, 100, 155)
fig = plt.figure(1, figsize=(16, 5))

ax = fig.add_subplot(1, 3, 1)
ax.imshow(cv2.cvtColor(in_img, cv2.COLOR_BGR2RGB))
ax = fig.add_subplot(1, 3, 2)
ax.imshow(gray_in_img, cmap='gray')
ax = fig.add_subplot(1, 3, 3)
fig.tight_layout()
ax.imshow(edges, cmap='gray', interpolation="none")
plt.show()

Output (the third image might still seem gray, but that's the anti aliasing from your browser; click to see the image in large):

enter image description here

Note the fig.tight_layout() line I added to have the subplots a bit larger.

Red
  • 26,798
  • 7
  • 36
  • 58