3

I am trying to use HogDescriptor and I am getting this error. I saw in the document that the constructor can take more then one argument. I am working in python 3.6 and opencv 3.2

This is my code:

def _extract_feature(X):
    """Performs feature extraction

        :param X:       data (rows=images, cols=pixels)
        :param feature: which feature to extract
                        - "hog":  HOG features
        :returns:       X (rows=samples, cols=features)
    """

    X = [cv2.cvtColor(x, cv2.COLOR_BGR2GRAY) for x in X]

    # operate on smaller image
    small_size = (32, 32)
    X = [cv2.resize(x, small_size) for x in X]

    # histogram of gradients
    block_size = (small_size[0] / 2, small_size[1] / 2)
    block_stride = (small_size[0] / 4, small_size[1] / 4)
    cell_size = block_stride
    num_bins = 9
    hog = cv2.HOGDescriptor(small_size, block_size, block_stride,
                            cell_size, num_bins)
    X = [hog.compute(x) for x in X]

    X = [x.flatten() for x in X]
    return X

So why am I getting the error:

TypeError: HogDescriptor takes at most 1 argument (5 given)

Update: I installed python 2.7 and tried it and it works.

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
neca
  • 119
  • 1
  • 10
  • I'm not sure what documentation you're talking about, since the Python bindings don't really have any documentation - there are plenty of questions here complaining about that very fact. Exactly where did you see it could take five arguments? – Daniel Roseman Feb 24 '17 at 21:35
  • http://docs.opencv.org/2.4/modules/gpu/doc/object_detection.html – neca Feb 24 '17 at 21:38
  • @DanielRoseman `help(cv2.HOGDescriptor)` shows it. Also [in source](https://github.com/opencv/opencv/blob/3.1.0/modules/objdetect/include/opencv2/objdetect.hpp#L351) the overload is prefixed by `CV_WRAP`. Not being able to call this constructor seems like a bug in the mapping to Python -- my guess is that it's due to the multiple overloads. – Dan Mašek Feb 24 '17 at 22:37

2 Answers2

4

The issue you're observing is due to combination of several factors:

  • PEP-238 – Changing the Division Operator
  • Mapping to Python and dispatching of overloaded C++ functions (as done in OpenCV)

Division in Python

The meaning of operator / has changed between Python 2.x and Python 3.x.

From PEP-238:

  • Classic division will remain the default in the Python 2.x series; true division will be standard in Python 3.0

For example, in Python 2.x you have

>>> small_size = (32, 32)
>>> block_size = (small_size[0] / 2, small_size[1] / 2)
>>> print(block_size)
(16, 16)

And in Python 3.x you have

>>> small_size = (32, 32)
>>> block_size = (small_size[0] / 2, small_size[1] / 2)
>>> print(block_size)
(16.0, 16.0)

In order to get the same result, your Python 3.x code would need to use operator //

>>> small_size = (32, 32)
>>> block_size = (small_size[0] // 2, small_size[1] // 2)
>>> print(block_size)
(16, 16)

Mapping and Handling of Overloads

Without going into detail (that would warrant a separate question), the overload resolution depends on the types of arguments. The matching of types is quite strict, e.g. you may not pass a floating point number to an integer argument.

The C++ signature of the constructor in question (truncated to only the relevant arguments) is

HOGDescriptor (Size _winSize
    , Size _blockSize
    , Size _blockStride
    , Size _cellSize
    , int _nbins, ...)

cv::Size is a class that contains two integers. In the Python API, it is represented as a tuple containing two integers.

Therefore, to call this overload in Python, we need to provide:

  • 4x a tuple containing two integers (for _winSize, _blockSize, _blockStride, and _cellSize)
  • 1x an integer (for _nbins)

We can test this out in the Python interpreter.

>>> import cv2
>>> cv2.HOGDescriptor((1,1),(1,1),(1,1),(1,1),1)
<HOGDescriptor 0393D0F0>

This matches our expectations. Let's try some other options.

>>> cv2.HOGDescriptor((1,1,1),(1,1),(1,1),(1,1),1)
TypeError: HOGDescriptor() takes at most 1 argument (5 given)
>>> cv2.HOGDescriptor((1,),(1,1),(1,1),(1,1),1)
TypeError: HOGDescriptor() takes at most 1 argument (5 given)

We see the tuples need to have 2 values, no more, no less.

>>> cv2.HOGDescriptor([1,1],(1,1),(1,1),(1,1),1)
TypeError: HOGDescriptor() takes at most 1 argument (5 given)

Lists won't do, it has to be a tuple.

>>> cv2.HOGDescriptor((1.0,1.0),(1,1),(1,1),(1,1),1)
TypeError: HOGDescriptor() takes at most 1 argument (5 given)

Tuples of floats won't work either.

>>> cv2.HOGDescriptor((1,1),(1,1),(1,1),(1,1),1.0)
TypeError: HOGDescriptor() takes at most 1 argument (5 given)

Nor can we substitute an float for an integer.

Pulling it All Together

Considering the above, in terms of the values you pass the constructor, the two variants are:

  • Python 2.x: cv2.HOGDescriptor((32,32),(16,16),(8,8),(8,8),9)
  • Python 3.x: cv2.HOGDescriptor((32,32),(16.0,16.0),(8.0,8.0),(8.0,8.0),9)

We can clearly see that the parameters in Python 3.x don't satisfy the requirements of the overload.

As to why we get the misleading error message, that seems like another topic warranting a separate question, and I haven't done all the research necessary to answer this authoritatively.

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
2

HOGDescriptor is an overloaded function.

If you look at the c++ documentation, it shows 4 versions - one with no parameters, one with 12, one with string and one with an option I'm not immediately familiar with.

You've tried to use one with 5 arguments. There isn't a function for that. You need to use either none, all 12 or one. If you look at this StackOverflow question, this person has supplied an xml file for the control specification - this fits the string filename option and hence is a valid function.

Community
  • 1
  • 1
Alan
  • 2,914
  • 2
  • 14
  • 26
  • Out of the 12 parameters, the last 7 provide default values. In general, the OpenCV Python API mappings reflect this as well. i.e. you *should* be able to call the constructor with only 5 parameters provided, the defaults being used for the rest. This particular [constructor overload](https://github.com/opencv/opencv/blob/master/modules/objdetect/include/opencv2/objdetect.hpp#L365) is prefixed by `CV_WRAP`, so it was intended to be exposed in Python. Even help(cv2.HOGDescriptor) lists it. The fact that you can't call it seems like a bug to me. – Dan Mašek Feb 24 '17 at 22:20
  • 1
    So, Dan Mašek, I don't know if it was a bug with opencv and python 3.6 version, but I installed python 2.7 and tried it and it works. – neca Feb 24 '17 at 22:35
  • On further testing, it turns out to be a slightly misleading error message cause by how the overload resolution works. It's very picky about the parameters -- the first 4 need to be tuples with 2 integers each, the fifth one an integer. If this does not match, the overload is not selected. The misleading error is most likely a side effect of Python implementation. – Dan Mašek Feb 24 '17 at 22:59
  • @neca OK, and that now leads us to the real cause of the problem - [PEP-238](https://www.python.org/dev/peps/pep-0238/). The meaning of operator `/` changed. In your Python 3 variant, `32/2` gives a float, not an int. And since parameters don't match, the overload fails. :) – Dan Mašek Feb 24 '17 at 23:02
  • 1
    You can restore int division using the floor operation (//). (small_size[0] // 2, small_size[1] // 2) gives int. Well spotted on the integer arguments, I missed that! Why don't you post an answer so it can be accepted? – Alan Feb 24 '17 at 23:15
  • @Alan Wrote it up. As you can see from the comments, it took me some time and work to figure that out as well :D I'm still using Python 2.7, so I wasn't even aware of this change until now. – Dan Mašek Feb 24 '17 at 23:58