cv2.rectangle not working when using a tuple from a np array as color

Viewed 641

I was trying to make rectangles with random colors in a script, but I came across this really weird bug where, when I use a hard-coded color everything works fine (like (255, 0, 0)), but when I try to make a random color using np.random.randint, it gives me the following error:

TypeError: function takes exactly 4 arguments (2 given)

This minimal code reproduces my problem.

import numpy as np
from cv2 import cv2
    
img = np.ones((900, 1200), dtype='uint8')
color = tuple(np.random.randint(0, 256, 3, dtype='uint8')) # Does not work
color = (127, 127, 127) # Works
cv2.rectangle(img, (100, 100), (300, 300), color, 4)
2 Answers

It's odd behavior indeed, but the following will work in your case:

Edit: Christoph's comment is correct. You simply need to call tolist() to convert the color to a python scalar.

Example:

import numpy as np
import cv2

img = np.ones((900, 1200), dtype='uint8')
color = np.random.randint(0, 256, 3, dtype=np.uint8)

cv2.rectangle(img, (100, 100), (300, 300), color.tolist(), 4)

cv2.imshow("Test", img)
cv2.waitKey()

Output:

output jpg

Solution: call the .tolist() method on the numpy array, then convert the list to a tuple using tuple().

What's going on:

OpenCV expects not just a tuple, but a tuple of Python integers.

Simply calling tuple() on anything iterable (lists, numpy arrays, ...) puts those elements, as they are, into the tuple. tuple() iterates over whatever you pass in. It could even be an object of a class you wrote, if you implement the required methods to make it an iterable. Guess what tuple("foo") does? It does ('f', 'o', 'o')

In the case of a numpy array, its elements are instances of numpy number types. From simply printing such an element, you can't tell the difference.

Observe:

>>> numbers = np.random.randint(0, 256, 3, dtype='uint8')
>>> numbers # it's a numpy ndarray
array([243, 188, 231], dtype=uint8)
>>> numbers[0] # looks like a regular Python integer?
243
>>> type(numbers[0]) # it's not a regular Python integer
<class 'numpy.uint8'>
>>> type(tuple(numbers)[0]) # even "converting" to a tuple doesn't fix it
<class 'numpy.uint8'>

And this is what needs to happen for OpenCV to be happy:

>>> numbers.tolist() # this is not enough for OpenCV
[243, 188, 231]
>>> type(_[0]) # type of elements is correct at least
<class 'int'>
>>> tuple(numbers.tolist()) # OpenCV wants this
(243, 188, 231)

NB: This is a common issue with OpenCV when it wants tuples of integers... e.g. when you try to pass in a point (for the corners of the rectangle) and the point happens to consist of floating point numbers. In that case, OpenCV doesn't like floats regardless of whether they're Python or numpy. It wants integers there. That behavior may change in the future, I hope.

Related