3

I would like to apply a thresholding operation to several grayscale images with different threshold values so that the output, which is displayed as a matplotlib plot, will be 15 or so different images with levels of threshold applied to each. The problem I am facing is that it only displays one image after it runs but if I say print(dst.shape) in the loop it will print 15 image shapes.

I have tried putting the output dst in a list so that I could access them by index dst[2] but this returned an error.

maxValue = 255
dst = []
for thresh in range(0, 255, 51):
    for img in imageB, imageG, imageR:
        th, dst = cv2.threshold(img, thresh, maxValue, cv2.THRESH_BINARY)
        #print(dst.shape)
        #print(len(dst))
        plt.imshow(dst)

What I am trying to achieve is 15 different images from the loop. Is this a matplotlib issue? Am I required to create a figure of a specific size then access each variable in the dst list? If so, why when I print(len(dst)) does it only return the length of the rows in the image?

David Alford
  • 2,044
  • 3
  • 12
  • 35

2 Answers2

3

In the code you've shown you are assigning the thresholded image from cv2.threshold() to the name of the list, hence print(len(dst)) returning information about the lengh of an image rather than the length of a list. You have effectively overwritten your list with an image.

Plotting thresholded images in a loop:

import cv2
import numpy as np
import matplotlib.pyplot as plt

# Make a test image.
r = np.random.randint(0,255,10000).reshape(100,100)
g = np.random.randint(0,255,10000).reshape(100,100)
b = np.random.randint(0,255,10000).reshape(100,100)
img = np.dstack([r,g,b]).astype(np.uint8)

# Convert test image to grayscale.
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

fig, axs = plt.subplots(1,3, figsize=(6,2))
for thresh_value, ax in zip(range(75,255,75), axs.ravel()):
    T, thresh = cv2.threshold(img_gray, thresh_value, 255, cv2.THRESH_BINARY)
    ax.imshow(thresh, cmap='gray')
    ax.set_title(str(thresh_value))

plt.tight_layout()
plt.savefig('plot_img.png')

Producing:

enter image description here

Dodge
  • 3,219
  • 3
  • 19
  • 38
  • 1
    Very nice. `fig, axs = plt.subplots(1,3, figsize=(6,2))` helps solve the next question I would look into which would be how to set subplot positions while looping. Great response! – David Alford Aug 31 '19 at 03:15
2

You could use a figure with sub-plots, something like this:

fig = plt.figure()

step = 51
maxValue = 255
nrows = 3
ncols = maxValue // step

i = 1
for thresh in range(0, maxValue, step):
    for img in imageB, imageG, imageR:
        th, dst = cv2.threshold(img, thresh, maxValue, cv2.THRESH_BINARY)
        fig.add_subplot(nrows, ncols, i)
        plt.imshow(dst)
        i = i + 1

About your question why when I print(len(dst)) does it only return the length of the rows in the image?, see for example this question.

David Alford
  • 2,044
  • 3
  • 12
  • 35
dms
  • 1,260
  • 7
  • 12
  • What is the use of the bottom `plt.show()` here? It looks as if the `plt.imshow(dst)` accomplishes this task, yes? Also, very Pythonic way to do this and very helpful response. Will look deeper into understanding numPy arrays as I will need more knowledge on this to use OpenCV more efficiently. – David Alford Aug 31 '19 at 03:25
  • 1
    @DavidAlford, you're right, last `show()` is not really necessary. – dms Aug 31 '19 at 03:36