I have a python script that uses OpenCV, code works in python 2.7 but not python 3.7 and I am not sure why

Viewed 152

So I am working on a project, and someone gave me some of their code that they created in python 2.7 to implement into it. The project however runs on python 3.7 and when I tried to execute it I kept getting errors related to the marker function. Would someone please be able to look at it and tell me what is missing to get the version to execute? I attached the image used to test out the function as well as the code.

Below is the error I get:

Traceback (most recent call last):
  File "/home/pi/Downloads/distance_to_camera_2 (1).py", line 94, in <module>
    width_array=process_component(labels_im)
  File "/home/pi/Downloads/distance_to_camera_2 (1).py", line 71, in process_component
    x,y,w,h = cv2.boundingRect(cnts[0])
TypeError: Expected cv::UMat for argument 'array'

This is the code:

import numpy as np
import cv2
from matplotlib import pyplot as plt



# Find distance from camera to object using Python and OpenCV
def distance_to_camera(knownWidth, focalLength, perWidth):
    # compute and return the distance from the maker to the camera
    return (knownWidth * focalLength) / perWidth



KNOWN_WIDTH = 8
focalLength = 545
# put your image here
img = cv2.imread("/home/pi/Downloads/many_blob.png")
cv2.imshow("img", img)
cv2.waitKey(1000)
image = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)[1]  # ensure binary 127
cv2.imshow("image", image)
cv2.waitKey(1000)
kernel = np.ones((5,5),np.uint8)
erosion = cv2.erode(image,kernel,iterations = 5)
dilate=cv2.dilate(erosion,kernel,iterations = 5)
edged = cv2.Canny(dilate, 0, 128)
cv2.imshow("edged", edged)
cv2.waitKey(1000)

connectivity=8
num_labels,labels_im = cv2.connectedComponents(edged,connectivity)

# Function only for labels display  (debuging only)
def imshow_components(labels):
    # Map component labels to hue val
    label_hue = np.uint8(179*labels/np.max(labels))
    blank_ch = 255*np.ones_like(label_hue)
    labeled_img = cv2.merge([label_hue, blank_ch, blank_ch])

    # cvt to BGR for display
    labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_HSV2BGR)

    # set bg label to black
    labeled_img[label_hue==0] = 0
    #labeled_img[labels==0] = 0

    cv2.imshow('labeled.png', labeled_img)
    cv2.waitKey(1000)
    cv2.imwrite('labeled_img.png',labeled_img)
    #cv2.imwrite('label_hue.png',label_hue)

def process_component(labels):
    width = np.zeros(np.max(labels))
    for i in range(1,np.max(labels)+1):
        tmp_im= labels.copy()
        tmp_im[:] = 0
        tmp_im[labels==i] = 255
        file="imlabel_%d.png"%(i, )
        cv2.imwrite(file,tmp_im)
        tmp_im = tmp_im.astype(np.uint8)
        cnts = cv2.findContours(tmp_im, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

        # bounding box of the countour
        x,y,w,h = cv2.boundingRect(cnts[0])
        width[i-1] = w
        tmp_im=cv2.rectangle(tmp_im,(x,y),(x+w,y+h),(255,0,0),2)
        # center = center of the bounding box
        center=(x+w/2,y+h/2)
        cv2.circle(tmp_im, center, 3, (255,0,0), 2, 8, 0)

        cv2.imshow(file, tmp_im)
        cv2.waitKey(1000)
    return width

width_array=process_component(labels_im)
imshow_components(labels_im)
cv2.imwrite('labels_img.png',labels_im)

for i in range(1,np.max(labels_im)+1):
    w=width_array[i-1]
    #marker = find_marker(image)
    dist_cm = distance_to_camera(KNOWN_WIDTH, focalLength, w)
    print("distance en cm = %d",dist_cm)

This is my first time posting on stack overflow so if I should post anything else for people to help me out please do tell me.

Here is the Image I have been trying to work with: https://i.stack.imgur.com/ONhUA.png

2 Answers

Here is one way to avoid the issue of the properly accessing the contour item in the return values in findContours() in Python/OpenCV.

There are two different possible number of return values from findContours, depending upon which version of OpenCV you are using. So to access the position where contours can be found in the list of return values:

Replace

contours,hierarchy = cv2.findContours(tmp_im, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

With
contours,hierarchy = cv2.findContours(tmp_im, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]

The second line means that if the number of elements in the list of return values is 2, then use the first returned value, contours[0], in the list of returned values, otherwise use the second returned value contours[1] to get to contours

When you were calling cv2.boundingRect(cnts) the value of cnts was wrong. I read around and this is because the version you were using is outdated. It appears more information is now returned from cv2.findContours(...), so you simply need to extract it.

def process_component(): Changes

contours,hierarchy = cv2.findContours(tmp_im, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = contours[0]

Second, when you were drawing the circle using cv2.circle(...) your were passing it a tuple of floats. cv2.circle(...) cannot use floats apparently, so convert each value in the tuple to an int first. You may want to round these floats before converting to an int.

def process_component(): Changes

center = ( int(x+w/2), int(y+h/2) )
# Or use the rounded version below.
# center = ( int(round(x+w/2)), int(round(y+h/2)) )
cv2.circle(tmp_im, center, 3, (255,0,0), 2, 8, 0)

Here is your script with those implementations.

import os
import cv2
import numpy as np
from matplotlib import pyplot as plt


def distance_to_camera(knownWidth, focalLength, perWidth):
    """ 
    Find distance between camera and object with OpenCV.
    Return the distance from the maker (object?) to the camera.
    """ 
    return (knownWidth * focalLength) / perWidth



def imshow_components(labels):
    """ Display labels: For debugging purposes"""
    # Map component labels to hue val
    label_hue = np.uint8(179*labels/np.max(labels))
    blank_ch = 255*np.ones_like(label_hue)
    labeled_img = cv2.merge([label_hue, blank_ch, blank_ch])

    # cvt to BGR for display
    labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_HSV2BGR)

    # set bg label to black
    labeled_img[label_hue==0] = 0
    #labeled_img[labels==0] = 0

    cv2.imshow('labeled.png', labeled_img)
    cv2.waitKey(1000)
    cv2.imwrite('labeled_img.png',labeled_img)
    #cv2.imwrite('label_hue.png',label_hue)



def process_component(labels):
    """ 
    Describe what happens here.
    width of ____ is ___.
    Find contours of ___ for ___.
    etc. Write what happens in this __docstr__
    return the ______.
    """
    
    width = np.zeros(np.max(labels)) 
    
    for i in range(1,np.max(labels)+1):
        
        tmp_im= labels.copy()
        tmp_im[:] = 0
        tmp_im[labels==i] = 255
        
        file="imlabel_%d.png"%(i, )
        cv2.imwrite(file,tmp_im)
        tmp_im = tmp_im.astype(np.uint8)

        ##########################
        # Here was the first issue. https://docs.opencv.org/3.1.0/dd/d49/tutorial_py_contour_features.html
        contours,hierarchy = cv2.findContours(tmp_im, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
        cnts = contours[0]

        x,y,w,h = cv2.boundingRect(cnts)
        ##########################

        width[i-1] = w
        tmp_im=cv2.rectangle(tmp_im,(x,y),(x+w,y+h),(255,0,0),2)
        

        ##########################
        # HERE IS THE NEXT ISSUE. 
        # Fixed it. The center tuple (x, y) cannot be of type floats apparently. Convert each value to an int after division.
        # center = center of the bounding box
        center=(int(x+w/2),int(y+h/2))

        cv2.circle(tmp_im, center, 3, (255,0,0), 2, 8, 0)
        ##########################

        cv2.imshow(file, tmp_im)
        cv2.waitKey(1000)
        
        
    return width




def main():
    """ Call this function to run the program. Put some detail in here. """
    
    # Get the path to the filename. (Im on windows right now)
    filename =  "many_blob.png"
    path = os.path.join(os.getcwd(), filename)
    
    
    # What are these?
    KNOWN_WIDTH = 8
    focalLength = 545
    
    # Read the path as an cv2 image and show it. Wait 1 sec.  
    img = cv2.imread(path)
    cv2.imshow("img", img)
    cv2.waitKey(1000)
    
    # Do something else here. I dont know what, sorry idk about cv2
    image = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)[1]  # ensure binary 127
    cv2.imshow("image", image)
    cv2.waitKey(1000)
    
    # Do something with parameters that will affect the image.
    kernel = np.ones((5,5),np.uint8)
    erosion = cv2.erode(image,kernel,iterations = 5)
    dilate=cv2.dilate(erosion,kernel,iterations = 5)
    edged = cv2.Canny(dilate, 0, 128)
    
    # Show the affected image.
    cv2.imshow("edged", edged)
    cv2.waitKey(1000)
    
    # idk.
    connectivity=8
    num_labels,labels_im = cv2.connectedComponents(edged,connectivity)
    
    # Do something here.
    width_array=process_component(labels_im)
    imshow_components(labels_im)
    cv2.imwrite('labels_img.png',labels_im)
    
    
    
    # do some more things here.
    for i in range(1,np.max(labels_im)+1):
        w=width_array[i-1]
        #marker = find_marker(image)
        dist_cm = distance_to_camera(KNOWN_WIDTH, focalLength, w)
        print("distance en cm = %d",dist_cm)




        
        
        
    
        
if __name__ == '__main__':
    """ If this file is executed, run the main function. """
    
    main()
    
    
    
    
Related