2

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

DeCarabas
  • 23
  • 4
  • I haven't messed around with OpenCV so I cant help you there. However, you should get rid of any non-relevant lines to make it more readable and finally try to break down your code to exactly where the problem is. Try to reorganize it as `how to draw an image in OpenCV` or something similar to fit your problem. Good luck I am sure you'll get past this roadblock. Welcome to StackOverflow its a great resource. – Brandon Nadeau Jan 12 '21 at 19:32
  • quick note your `print` statement towards the bottom should probably be this `print("distance en cm = %d" % dist_cm)`. Use `%` (modulo) to place `dist_cm` inside the string at the `%d` character. – Brandon Nadeau Jan 12 '21 at 19:35
  • 1
    Thanks @Crispy, edited the code so the fluff is no longer there – DeCarabas Jan 12 '21 at 22:19
  • 1
    Is the OpenCV version between Python 2.7 and 3.7 the *same*? The output of `cv2.findContours` depending on whether it's version 3 or 4 will have either two outputs or three outputs. I highly suspect you don't have matching OpenCV versions between the two Python envs. For each env, please do `import cv2; print(cv2.__version__)` and tell us what it prints out for both Python 2.7 and 3.7. – rayryeng Jan 12 '21 at 22:22
  • @rayryeng Good tip. You may have to refactor your code to work with the library version you have for Python3.7, some features / functions may be depreciated. I would recommend rebuilding your program from the smallest piece first and get help on it that way. Before you do that try to place print statements to find out where the program goes wrong. I don't have much time but I will take your code and try to make it work. For now, try your best to recreate your script step by step in a new file. Get it to work piece by piece. – Brandon Nadeau Jan 12 '21 at 23:04

2 Answers2

1

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
fmw42
  • 46,825
  • 10
  • 62
  • 80
0

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()
    
    
    
    
Brandon Nadeau
  • 3,568
  • 13
  • 42
  • 65
  • Thanks a bunch, this solved my issue perfectly. Now I know what to look for if something like this happens again :) – DeCarabas Jan 13 '21 at 23:25
  • No problem. You'll learn a lot from debugging code. Just put `print` statements in your code to find out where everything starts to go wrong, missing or incorrect data, and then google the simplest error code and you'll usually find a stackoverflow thread that will solve your issue, or check the libraries docs. – Brandon Nadeau Jan 14 '21 at 00:15