Home > Software engineering >  How to vectorize an image using opencv?
How to vectorize an image using opencv?

Time:11-23

I'm using the term "vectorize" because that is what has been used to describe the process I'm writing about. I don't know what it's actually called but what I'm trying to do is take the elements of an image and separate them into different images.

Here is an example picture I'm trying to "vectorize"

What I would like to do is (using opencv) separate the corncob from the green stock it's attached to, and separate each corncob chunk into their own images.

What I have tried is the following:

def kmeansSegmentation(path_to_images, image_name, path_to_save_segments):
    img = cv2.imread(path_to_images image_name)

    img_blur = cv2.GaussianBlur(img, (3,3), 0)
    img_gray = cv2.cvtColor(img_blur, cv2.COLOR_BGR2GRAY)

    img_reshaped = img_gray.reshape((-1, 3))
    img_reshaped = np.float32(img_reshaped)
    criteria = (cv2.TERM_CRITERIA_EPS   cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)


    K = 5
    attempts = 10
    ret,label,center=cv2.kmeans(img_reshaped,K,None,criteria,attempts,cv2.KMEANS_PP_CENTERS)
    center = np.uint8(center)
    res = center[label.flatten()]

    v = np.median(res)
    sigma=0.33
    lower = int(max(0, (1.0 - sigma) * v))
    upper = int(min(255, (1.0   sigma) * v))
    edges = cv2.Canny(img_gray, lower, upper)

    contours, hierarchy = cv2.findContours(edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    sorted_contours= sorted(contours, key=cv2.contourArea, reverse= True)
    mask = np.zeros(img.shape[:2], dtype=img.dtype)



    array_of_contour_areas = [cv2.contourArea(contour) for contour in contours]
    contour_avg = sum(array_of_contour_areas)/len(array_of_contour_areas)
    contour_var = sum(pow(x-contour_avg,2) for x in array_of_contour_areas) / len(array_of_contour_areas)
    contour_std = math.sqrt(contour_var)


    print("Saving segments", len(sorted_contours))
    for (i,c) in tqdm(enumerate(sorted_contours)):
        if (cv2.contourArea(c) > contour_avg-contour_std*2):
            x,y,w,h= cv2.boundingRect(c)
            cropped_contour= img[y:y h, x:x w]
            cv2.drawContours(mask, [c], 0, (255), -1)
            #tmp_image_name= image_name   "-kmeans-"   str(K)   str(random.random())   ".jpg"
            #cv2.imwrite(path_to_save_segments tmp_image_name, cropped_contour)


    result = cv2.bitwise_and(img, img, mask=mask)
    
    """
    scale_percent = 30 # percent of original size
    width = int(edges.shape[1] * scale_percent / 100)
    height = int(edges.shape[0] * scale_percent / 100)
    dim = (width, height)


    resized = cv2.resize(result, dim, interpolation = cv2.INTER_AREA)

    cv2.imshow("edges", resized)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    """
    #tmp_image_name= image_name   "-kmeans-"   str(K)   str(random.random())   ".png"
    #cv2.imwrite(path_to_save_segments tmp_image_name, result)
    return result

Excuse the commented out code, that's me just observing the changes I make to the image as I modify the algorithm.

CodePudding user response:

I believe what you are trying to do is something called "Instance Segmentation". This process is best done with deep learning techniques which might not suit you unless you can find a pre-trained model. Here is an article on how you can do that: https://www.analyticsvidhya.com/blog/2019/04/introduction-image-segmentation-techniques-python/

A more simple (but way less accurate) solution might be to just find use RGB threshold values to create an "outline" of the picture and then use a flood fill algorithm to discern specific pixels in the resulting image. To make a rough outline of the image, you'll have to experiment with different threshold values. First, convert the whole image to grayscale w. OpenCV (cv2.imread('image-name', 0)). To test different threshold values, simply check if each pixel value is above or below a certain value (that you pick):

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

def apply_threshold(img_array, threshold):
    threshold_applied = []
    for row in img_array:
        threshold_applied.append([])
        for pixel in row:
            if pixel>threshold:
                threshold_applied[len(threshold_applied)-1].append([255, 255, 255])
            else:
                threshold_applied[len(threshold_applied)-1].append([0, 0, 0])
    new_img = np.array(threshold_applied, np.uint8)
    cv2.imwrite(str(threshold) ".jpg", new_img)

img = cv2.imread("Ek1Dx.jpg", 0)
apply_threshold(img, 210)

If you run the code, you can see that the output is extremely inaccurate and that the cob and the kernels of corn are barely visible. Instance segmentation and dividing images has actually been a very big computer science problem for the past decade or so. There may be other filters you can use to get more accurate results, but I think identifying a threshold is a baseline image segmentation technique. If you want, you can try tweaking around with the threshold value in the program to see if it'll get you a more divided image.

  • Related