Guilherme Correa
Guilherme Correa

Reputation: 594

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

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)

Upvotes: 2

Views: 1582

Answers (2)

Christoph Rackwitz
Christoph Rackwitz

Reputation: 15576

Solution: call the .tolist() method on the numpy array. That will convert the elements into Python integers. With today's OpenCV, that is sufficient.

Previous versions additionally needed a tuple() call around that because they only accepted tuples. Newer versions also accept lists here.

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'>

Older OpenCV versions additionally weren't satisfied with the list, they specifically wanted a tuple:

>>> 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.

Upvotes: 2

Abstract
Abstract

Reputation: 995

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

Upvotes: 3

Related Questions