-
-
Notifications
You must be signed in to change notification settings - Fork 56.2k
Description
System Information
opencv 4.12.0.88
OS: Ubuntu 22.04
Python 3.10+
Detailed description
PR #26826 changed the typing of cv2.typing.Scalar
:
Scalar = _typing.Union[_typing.Sequence[float], float]
Based on the conversation in that PR and related issue, the original problem seems to be that for calls from Python -> C++, for functions typed as taking a Scalar
, a Python float
and int
gets implicitly converted to a 4 channel Scalar
type as a convenience to mimic the constructor for Scalar
:
opencv/modules/python/src2/cv2_convert.cpp
Lines 403 to 405 in 9cdd525
if (PyFloat_Check(o) || PyInt_Check(o)) { | |
s = PyFloat_AsDouble(o); | |
} else { |
opencv/modules/core/include/opencv2/core/types.hpp
Lines 676 to 690 in 9cdd525
template<typename _Tp> class Scalar_ : public Vec<_Tp, 4> | |
{ | |
public: | |
//! default constructor | |
Scalar_(); | |
Scalar_(_Tp v0, _Tp v1, _Tp v2=0, _Tp v3=0); | |
Scalar_(_Tp v0); | |
Scalar_(const Scalar_& s); | |
Scalar_(Scalar_&& s) CV_NOEXCEPT; | |
Scalar_& operator=(const Scalar_& s); | |
Scalar_& operator=(Scalar_&& s) CV_NOEXCEPT; | |
template<typename _Tp2, int cn> |
The code which highlighted the problem:
>>> cv2.line(image, (1, 1), (8, 8), color=255)
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 255, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 255, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 255, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 255, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 255, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 255, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 255, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 255, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
Note that the following works as well and conforms to the original typing:
>>> cv2.line(image, (1, 1), (8, 8), color=(255,0,0,0))
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 255, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 255, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 255, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 255, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 255, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 255, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 255, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 255, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ cat test.py
import cv2
import numpy as np
image = np.zeros((10, 10), dtype=np.uint8)
cv2.line(image, (1, 1), (8, 8), color=(255,0,0,0))
(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ mypy test.py
Success: no issues found in 1 source file
Now that the typing has changed, all functions that return a Scalar
:
(xtf-py3.12) xtf-py3.12vfazio@vfazio4 /mnt/development/xtf $ grep -r "\-> cv2.typing.Scalar" .venv/lib/python3.12/site-packages/cv2/
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def mean(src: cv2.typing.MatLike, mask: cv2.typing.MatLike | None = ...) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def mean(src: UMat, mask: UMat | None = ...) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def sumElems(src: cv2.typing.MatLike) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def sumElems(src: UMat) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def trace(mtx: cv2.typing.MatLike) -> cv2.typing.Scalar: ...
.venv/lib/python3.12/site-packages/cv2/__init__.pyi:def trace(mtx: UMat) -> cv2.typing.Scalar: ...
have to contend with the fact that Scalar
is now either a Sequence[float]
or float
. In reality, they will likely only be Sequence[float]
due to the Scalar
being marshaled back to Python as a 4 element tuple
:
opencv/modules/python/src2/cv2_convert.cpp
Lines 414 to 417 in 9cdd525
PyObject* pyopencv_from(const Scalar& src) | |
{ | |
return Py_BuildValue("(dddd)", src[0], src[1], src[2], src[3]); | |
} |
All callers have to cast this value to drop the float
condition since it will never be true.
value = cast(Sequence[float], cv2.mean(ssim_map))
Since the conversion is uni-directional, arguments into functions can be typed Sequence[float] | float
, but return values, if they're actually backed by Scalar
, should only return Sequence[float]
.
This can be solved by:
- Having a type for each direction
- Allows flexibility in argument types
- single value conversion path can still be utilized
- this complicates typing and stub generation
- Revert the type change
- A single value will still work, callers can use a pragma to ignore the typing error or convert to a sequence type to comply with type checks
- Allows a future deprecation of the single value conversion path
Steps to reproduce
(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ cat test2.py
import cv2
import numpy as np
image = np.zeros((10, 10), dtype=np.uint8)
x = cv2.mean(image)
reveal_type(x)
z = tuple(x)
(venv) vfazio@vfazio4 /tmp/tmp.bCOmcUd04M $ mypy test2.py
test2.py:6: note: Revealed type is "Union[typing.Sequence[builtins.float], builtins.float]"
test2.py:7: error: Argument 1 to "tuple" has incompatible type "Sequence[float] | float"; expected "Iterable[float]" [arg-type]
Found 1 error in 1 file (checked 1 source file)
Issue submission checklist
- I report the issue, it's not a question
- I checked the problem with documentation, FAQ, open issues, forum.opencv.org, Stack Overflow, etc and have not found any solution
- I updated to the latest OpenCV version and the issue is still there
- There is reproducer code and related data files (videos, images, onnx, etc)