Opencv: ValueError

Question:

I have detected contours and stored them in cnts and i am accessing them one by one, c_list is the list of contours which are of interest to me.
I want to check if the contour i am accessing now is already been accessed before or not by using this code:

if not (np.all(cnts[c] in c_list)):
      while hierarchy[0][k][2] != -1:
        k = hierarchy[0][k][2]
        c_list.append(cnts[k])
        s = s+1

still i am getting error

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Can someone please correct me what i am doing wrong ?

Asked By: warl0ck

||

Answers:

Your issue is not really related to OpenCV, it comes from numpy.

Consider the following examples:

>>> import numpy as np
>>> [1,3] in [[1,3],[4,5]]
True

>>> np.array([1,3]) in [[1,3],[4,5]]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

In comments you specified (what we could’ve asserted from OpenCV itself, but this information should have been in your question to begin with) that the objects you’re comparing are probably something like

c_list = [[[[291, 267]], [[288, 268]], [[289, 267]]]]
cnts = np.array([[[291, 267]], [[288, 268]], [[289, 267]]])
# test for cnts[0] in c_list

The problem is that an indexed element of a numpy.ndarray is still a numpy.ndarray, but as you’ve seen with my first example, numpy arrays behave differently when you try to use the usual logical operations on them paired with native lists. This makes sense because in native python the only obvious way of handling in with list-valued operands is to test if the second list has a list-valued element equal to the first list. With numpy, you often want to do elementwise tests on your arrays, this is why the same expression leads to an error.

Now, fixing your problem is more complicated than one would expect. It’s not entirely clear what you’re trying to achieve, and there’s probably a more logical way to do it. Anyway, consider these modified examples:

>>> [1,3] in [[1,3],[4,5]]
True
>>> [1,3] in [[1,2],[4,5]]
False
>>> np.array([1,3]) in np.array([[1,3],[4,5]])
True
>>> np.array([1,3]) in np.array([[1,2],[4,5]])
True
>>> np.array([1,3]) in np.array([[0,2],[4,5]])
False

While using in between two ndarrays doesn’t produce an error, it behaves in a surprising way: it returns True if there are any common elements between the two arrays! This is obviously not what you want to do.

As I see it, you have two options. You can either convert all your ndarrays to lists. What I mean:

>>> np.array([1,3]).tolist() in np.array([[1,3],[4,5]]).tolist()
True
>>> np.array([1,3]).tolist() in np.array([[1,2],[4,5]]).tolist()
False

This would need for you to call c_list.append(cnts[k].tolist()) and test if not (cnts[c].tolist() in c_list):, but it’s usually not a very good idea to convert numpy arrays to native python lists, mainly due to memory considerations. If your contours have a lot of elements (which depends on whether you’re passing cv2.CHAIN_APPROX_SIMPLE to findContours()), this might be a strong constraint.

The other option is to do it fully with numpy. If I understand correctly that you want to check if the array np.array([[a,b]]) is in the list of arrays [np.array([[c,d]]), np.array([[f,g]]),...], then you can use elementwise equality tests making use of array broadcasting, and using np.all() and np.any() to reduce the result. Example:

>>> to_find = np.array([[1,3]])
>>> in_which = [np.array([[2,4]]),np.array([[1,3]]),np.array([[5,6]])]
>>> to_find in in_which
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
>>> np.all(to_find==in_which,axis=-1).any()
True

The same with a list that doesn’t contain your template:

>>> to_find = np.array([[1,3]])
>>> in_which = [np.array([[2,4]]),np.array([[1,2]]),np.array([[5,6]])]
>>> np.all(to_find==in_which,axis=-1).any()
False