marcoresk marcoresk - 19 days ago 5
Python Question

OpenCv doesn't accept multiple draw instructions in a function (in Python)

I am following this opencv tutorial about pose estimation and "augmented reality", and modifying it to work in real time with a webcam. All works well but I found this strange fact.

I have to show some code first.
The tutorial tells me to define an external draw function like

def draw(img, corners, imgpts):
corner = tuple(corners[0].ravel())
img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
return img


to draw the 3 axes on my chessboard, then display it in my video with

imgpts, jac = cv2.projectPoints(axis, rvecs, tvecs, mtx, dist)
img = draw(img,corners,imgpts)
# Draw and display the corners
cv2.imshow('REALITY',img)


In this way, the script draws only the blue (X) line.
But if I define 3 different functions like

def drawx(img, corners, imgpts):
corner = tuple(corners[0].ravel())
img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
#img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
#img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
return img


leaving uncommented every time a different line, then draw the lines in sequence on the same image like this

imgx = drawx(img,corners,imgpts)
imgy = drawy(img,corners,imgpts)
imgz = drawz(img,corners,imgpts)

# Draw and display the corners
cv2.imshow('REALITY',img)


I obtain the goal of the tutorial, exactly like this, plus the fact I obtained a script to work in real time.

This is a workaround, but it works.
My question is: why openCV does not draw the three lines in the same function?
It depends on my openCV version (2.4.8)? It depends on my python version (2.7)?

UPDATE1

After Micka's request (I hope to undertand it well) I modified my original draw function as

def draw(img, corners, imgpts):
corner = tuple(corners[0].ravel())
img = cv2.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
img = cv2.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
img = cv2.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
print imgpts
return img


And the result (one set of points as example) is

[[[ 323.7434082 162.16279602]]

[[ 329.28009033 350.18307495]]

[[ 513.23809814 349.89364624]]

[[ 512.85174561 161.18948364]]

[[ 281.99362183 157.28944397]]

[[ 290.17071533 384.95501709]]

[[ 512.66680908 384.78421021]]

[[ 512.18347168 156.10710144]]]


printing them in another way

print tuple(imgpts[0].ravel())
print tuple(imgpts[1].ravel())
print tuple(imgpts[2].ravel())


gives something like

(351.51596, 55.176319)
(352.62543, 254.72102)
(542.78979, 256.04565)


UPDATE 2
In the comments they suggest to print 3 "simple" lines in a function. I wrote

def draw_test(img):
cv2.line(img, (0,0), (200,10), (255,0,0), 5)
cv2.line(img, (0,0), (200,200), (0,255,0), 5)
cv2.line(img, (0,0), (10,200), (0,0,255), 5)
return img


and surprisingly this time all the 3 lines were printed.

Answer

Simplest code that reproduces this problem and matches what you've got there is as follows.

import cv2
import numpy as np


img1 = np.zeros((300,300,3), np.uint8)
img2 = np.zeros((300,300,3), np.uint8)

def draw_1(img):
    img = cv2.line(img, (0,0), (200,10), (255,0,0), 5)
    img = cv2.line(img, (0,0), (200,200), (0,255,0), 5)
    img = cv2.line(img, (0,0), (10,200), (0,0,255), 5)
    return img

def draw_2(img):
    cv2.line(img, (0,0), (200,10), (255,0,0), 5)
    cv2.line(img, (0,0), (200,200), (0,255,0), 5)
    cv2.line(img, (0,0), (10,200), (0,0,255), 5)
    return img

draw_1(img1)
draw_2(img2)

cv2.imwrite("lines_1.png", img1)
cv2.imwrite("lines_2.png", img2)

Lines 1:

Bad result

Lines 2:

Good result

Function draw_1 here corresponds to your original draw function, and draw_2 corresponds to what you had in UPDATE 2.

Notice that in draw_1, you assign the result of cv2.line back to img. This is a problem, since according to the documentation cv2.line returns None.

Hence, your first call will turn your image to None, and the remaining two line are drawn to "Nothing".