Search code examples
pythonopencvscikit-image

How can I draw a vertical axis through needle center


I would like to draw a vertical axis through the center of the needle (black object) in this image. How would I go about doing that.

needle


Solution

  • Here is how I would approach it.

    Threshold the image
    
    Average the image down to 1 row
    
    Stretch the 1 row to full dynamic range so that the darkest x coordinates correspond to the top of the black needle
    
    Get the range of x coordinates and compute the center x coordinate
    
    Draw a vertical line
    


    Sorry, I do not know OpenCV well. So here are my results using ImageMagick.

    (See the ADDITION further down for OpenCV code)

    Input:

    enter image description here

    Threshold Image

    convert img.jpg -threshold 5% img_t5.png
    


    enter image description here

    Scale the image down to 1 row (then scale up to 50 rows for visualization)

    convert img_t5.png -scale x1! -auto-level +write tmp1.png -scale x50! tmp2.png
    


    enter image description here

    Get array of X coordinates for black pixels only and get first and last one and compute average to get the center of needle

    xArr=(`convert tmp1.png txt: | grep "gray(0)" | sed -n 's/^\([^,]*\),.*$/\1/p'`)
    num=${#xArr[*]}
    last=$((num-1))
    firstx=${xArr[0]}
    lastx=${xArr[$last]}
    centx=`convert xc: -format "%[fx:($firstx+$lastx)/2]" info:`
    


    Draw Red Line

    convert img.jpg -fill red -draw "line $centx,0 $centx,2047" -alpha off result.jpg
    


    enter image description here

    ADDITION:

    I am not that proficient in OpenCV. Nevertheless, after spending quite some time, here is one way to do it in OpenCV.

    import cv2
    import numpy as np
    
    # read image
    img = cv2.imread('needle.jpg', cv2.IMREAD_UNCHANGED)
    #print('Original Dimensions : ',img.shape)
    
    # get dimensions
    height = img.shape[0]
    width = img.shape[1]
    
    # threshold image
    threshold = 5*255/100
    ret,thresholded = cv2.threshold(img,threshold,255,cv2.THRESH_BINARY)
    
    # resize image to 1 row
    wd = width
    ht = 1
    dim = (wd, ht)
    resized = cv2.resize(thresholded, dim, interpolation = cv2.INTER_AREA)
    #print('Resized Dimensions : ',resized.shape)
    
    # stretch values to full dynamic range (0 to 255)
    stretched = cv2.normalize(resized,None,0,255,cv2.NORM_MINMAX)
    
    # get x coordinate locations of zeroes (black pixels)
    minLocations = list(np.nonzero(stretched==0))[1]
    numberLocations = len(minLocations)
    print("Zero Locations: ",minLocations)
    
    # get first and last x coordinate values and then the average of first and last values in list
    first = minLocations[0]
    last = minLocations[numberLocations-1]
    average = int(round((first+last)/2))
    print("Centerline X Coordinate: ",average)
    
    # draw red line on image
    new_img = img.copy()
    new_img = cv2.cvtColor(new_img, cv2.COLOR_GRAY2BGR)
    cv2.line(new_img,(average,0),(average,height),(0,0,255),1)
    
    # save result
    cv2.imwrite("needle_centerline.jpg", new_img)
    


    Here are the print results:

    Zero Locations:  [1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287]
    
    Centerline X Coordinate:  1258
    


    enter image description here


    The following suggestion has been added in by Mark Setchell, namely, one can equally find the needle by totalling up the columns of the (thresholded) image and choosing the column that comes to the smallest total, because that has the most black and least white:

    import cv2
    import numpy as np
    
    # read image
    img = cv2.imread('needle.jpg', cv2.IMREAD_UNCHANGED)
    #print('Original Dimensions : ',img.shape)
    
    # get dimensions
    height, width = img.shape
    
    # threshold image
    threshold = 5*255/100
    ret,thresholded = cv2.threshold(img,threshold,255,cv2.THRESH_BINARY)
    
    # Sum up columns to get column totals
    ct = np.sum(thresholded,axis=0)
    
    # Find index of column with lowest total, i.e. least white and most black
    index = np.argmin(ct)
    
    ... continue as Fred 
    
    # draw red line on image
    new_img = img.copy()
    new_img = cv2.cvtColor(new_img, cv2.COLOR_GRAY2BGR)
    cv2.line(new_img,(index,0),(index,height),(0,0,255),1)
    
    # save result
    cv2.imwrite("needle_centerline.jpg", new_img)