Search code examples
pythonnumpyopencvcanny-operatorimage-thresholding

Approximating edge with rough outline - OpenCV


I've been researching and trying a couple functions to get what I want and I feel like I might be overthinking it. One version of my code is below. The sample image is here.

My end goal is to find the angle (yellow) of the approximated line with respect to the frame (green line) Final I haven't even got to the angle portion of the program yet.

The results I was obtaining from the below code were as follows. Canny Closed Small Removed

Anybody have a better way of creating the difference and establishing the estimated line? Any help is appreciated.

import cv2
import numpy as np

pX = int(512)
pY = int(768)

img = cv2.imread('IMAGE LOCATION', cv2.IMREAD_COLOR)
imgS = cv2.resize(img, (pX, pY))
aimg = cv2.imread('IMAGE LOCATION', cv2.IMREAD_GRAYSCALE)

# Blur image to reduce noise and resize for viewing
blur = cv2.medianBlur(aimg, 5)
rblur = cv2.resize(blur, (384, 512))

canny = cv2.Canny(rblur, 120, 255, 1)
cv2.imshow('canny', canny)
kernel = np.ones((2, 2), np.uint8)
#fringeMesh = cv2.dilate(canny, kernel, iterations=2)
#fringeMesh2 = cv2.dilate(fringeMesh, None, iterations=1)
#cv2.imshow('fringeMesh', fringeMesh2)
closing = cv2.morphologyEx(canny, cv2.MORPH_CLOSE, kernel)
cv2.imshow('Closed', closing)

nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(closing, connectivity=8)
#connectedComponentswithStats yields every separated component with information on each of them, such as size
sizes = stats[1:, -1]; nb_components = nb_components - 1

min_size = 200  #num_pixels

fringeMesh3 = np.zeros((output.shape))
for i in range(0, nb_components):
    if sizes[i] >= min_size:
        fringeMesh3[output == i + 1] = 255


#contours, _ = cv2.findContours(fringeMesh3, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
#cv2.drawContours(fringeMesh3, contours, -1, (0, 255, 0), 1)


cv2.imshow('final', fringeMesh3)

#cv2.imshow("Natural", imgS)
#cv2.imshow("img", img)
cv2.imshow("aimg", aimg)
cv2.imshow("Blur", rblur)
cv2.waitKey()
cv2.destroyAllWindows()

Solution

  • You can fit a straight line to the first white pixel you encounter in each column, starting from the bottom.

    I had to trim your image because you shared a screen grab of it with a window decoration, title and frame rather than your actual image:

    enter image description here

    import cv2
    import math
    import numpy as np
    
    # Load image as greyscale
    im = cv2.imread('trimmed.jpg', cv2.IMREAD_GRAYSCALE)
    
    # Get index of first white pixel in each column, starting at the bottom
    yvals = (im[::-1,:]>200).argmax(axis=0)
    
    # Make the x values 0, 1, 2, 3...
    xvals = np.arange(0,im.shape[1])
    
    # Fit a line of the form y = mx + c
    z = np.polyfit(xvals, yvals, 1)
    
    # Convert the slope to an angle
    angle = np.arctan(z[0]) * 180/math.pi
    

    Note 1: The value of z (the result of fitting) is:

    array([ -0.74002694, 428.01463745])
    

    which means the equation of the line you are looking for is:

    y = -0.74002694 * x + 428.01463745
    

    i.e. the y-intercept is at row 428 from the bottom of the image.

    Note 2: Try to avoid JPEG format as an intermediate format in image processing - it is lossy and changes your pixel values - so where you have thresholded and done your morphology you are expecting values of 255 and 0, JPEG will lossily alter those values and you end up testing for a range or thresholding again.