JuliaPy / PyCall.jl

Package to call Python functions from the Julia language
MIT License
1.46k stars 187 forks source link

PyCall cv2 drawings error #980

Closed liupgd closed 2 years ago

liupgd commented 2 years ago

Error occurred when invoking python cv2 drawing functions.

Code

using PyCall
cv2=pyimport("cv2")
img= cv2.imread("someimage.jpg")
cv2.circle(img, (2642, 415), 50, (0, 0, 255), 20) 

Error info

julia> cv2.circle(img, (2642, 415), 50, (0, 0, 255), 20) ERROR: PyError ($(Expr(:escape, :(ccall(#= /home/leo/.julia/packages/PyCall/7a7w0/src/pyfncall.jl:43 =# @pysym(:PyObject_Call), PyPtr, (PyPtr, PyPtr, PyPtr), o, pyargsptr, kw))))) <class 'cv2.error'> error("OpenCV(4.5.4-dev) :-1: error: (-5:Bad argument) in function 'circle'\n> Overload resolution failed:\n> - Layout of the output array img is incompatible with cv::Mat\n> - Expected Ptr for argument 'img'\n")

Stacktrace: [1] pyerr_check @ ~/.julia/packages/PyCall/7a7w0/src/exception.jl:62 [inlined] [2] pyerr_check @ ~/.julia/packages/PyCall/7a7w0/src/exception.jl:66 [inlined] [3] _handle_error(msg::String) @ PyCall ~/.julia/packages/PyCall/7a7w0/src/exception.jl:83 [4] macro expansion @ ~/.julia/packages/PyCall/7a7w0/src/exception.jl:97 [inlined] [5] #107 @ ~/.julia/packages/PyCall/7a7w0/src/pyfncall.jl:43 [inlined] [6] disable_sigint @ ./c.jl:458 [inlined] [7] __pycall! @ ~/.julia/packages/PyCall/7a7w0/src/pyfncall.jl:42 [inlined] [8] _pycall!(ret::PyObject, o::PyObject, args::Tuple{Array{UInt8, 3}, Tuple{Int64, Int64}, Int64, Tuple{Int64, Int64, Int64}, Int64}, nargs::Int64, kw::Ptr{Nothing}) @ PyCall ~/.julia/packages/PyCall/7a7w0/src/pyfncall.jl:29 [9] _pycall!(ret::PyObject, o::PyObject, args::Tuple{Array{UInt8, 3}, Tuple{Int64, Int64}, Int64, Tuple{Int64, Int64, Int64}, Int64}, kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}) @ PyCall ~/.julia/packages/PyCall/7a7w0/src/pyfncall.jl:11 [10] (::PyObject)(::Array{UInt8, 3}, ::Vararg{Any}; kwargs::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}}) @ PyCall ~/.julia/packages/PyCall/7a7w0/src/pyfncall.jl:86 [11] (::PyObject)(::Array{UInt8, 3}, ::Vararg{Any}) @ PyCall ~/.julia/packages/PyCall/7a7w0/src/pyfncall.jl:86 [12] top-level scope @ REPL[119]:1

Same error happend in cv2.rectangle, cv2.ellipse. And all these codes work well in python.

Possible cause

Julia array converted to python numpy is not C_CONTIGUOUS:

julia> PyObject(img).flags
PyObject   C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

While in python cv2, Arrays are needed to be C_CONTIGUOUS. One possible solution is PyReverseDims:

img = cv2.imread("someimage.jpg")
pyimg = PyReverseDims(img)
out=cv2.circle(pyimg, (100, 200), 50, (0, 0, 255), 20);
outimg=PyReverseDims(out).data;
cv2.imwrite("out.jpg", outimg);

But the out.jpg doesn't change, no cirlces drawn. How should I do? Thx.

liupgd commented 2 years ago

I tried one solution. As PyReverseDims can get continuous memory object but not suitable for cv2.circle, I permuted the data in advance, then the PyReverseDims can get the right continuous object:

img = cv2.imread("someimage.jpg")
rimg = permutedims(img, ndims(img):-1:1) 
# I also tried PermutedDimsArray which doesn't work. PermutedDimsArray only creates a permuted view, 
# while permutedims creates a new array with copy.(I hope there will be better solutions without data copy.)
pyimg = PyReverseDims(rimg)   # now, pyimg is a PyObject with flag C_CONTIGUOUS=True
out=cv2.circle(pyimg,  (100, 200), 50, (0, 0, 255), 20); # cv2.rectangle, ellipse should also work now.

I would be happy if anyone could offer better solutions. Thx.

stevengj commented 2 years ago

Layout of the output array img is incompatible with cv::Mat

The basic problem here is that Julia arrays are column-major and cv is expecting row-major. Reversing the dimensions and then using PyReverseDims array is the right solution.