sting
sting

Reputation: 33

How to get rid of skeleton line except contour?

I want to get rid of the skeletonized lines except the contours using python.

And, want to extract only the largest contour.

(Actually, I tried to make skeletonized line from the segmented mask. And, I got the main stem with contour like above picture. Among the contours, I want to extract only the contour with largest area.)

I don't know how to do it. Please help me if you have any idea.

Thanks in advance.

import os
import numpy as np
import cv2
from plantcv.plantcv import find_objects
from plantcv.plantcv import image_subtract
from plantcv.plantcv.morphology import segment_sort
from plantcv.plantcv.morphology import segment_skeleton
from plantcv.plantcv.morphology import _iterative_prune
from plantcv.plantcv import print_image
from plantcv.plantcv import plot_image
from plantcv.plantcv import params
from cv2.ximgproc import thinning


def find_large_contour(img, mask):
    params.device += 1
    mask1 = np.copy(mask)
    ori_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ori_img)) == 2:
        ori_img = cv2.cvtColor(ori_img, cv2.COLOR_GRAY2BGR)
    objects, hierarchy = cv2.findContours(mask1, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]
    for i, cnt in enumerate(objects):
        cv2.drawContours(ori_img, objects, i, (255, 102, 255), -1, lineType=8, hierarchy=hierarchy)
    if params.debug == 'print':
        print_image(ori_img, os.path.join(params.debug_outdir, str(params.device) + '_id_objects.png'))
    elif params.debug == 'plot':
        plot_image(ori_img)

    return objects, hierarchy, ori_img

def prune(skel_img, size=2, mask=None):

    # Store debug
    debug = params.debug
    params.debug = None

    pruned_img = skel_img.copy()

    # Check to see if the skeleton has multiple objects
    skel_objects, _ = find_objects(skel_img, skel_img)

    _, objects = segment_skeleton(skel_img)
    kept_segments = []
    removed_segments = []

    if size > 0:
        # If size>0 then check for segments that are smaller than size pixels long

        # Sort through segments since we don't want to remove primary segments
        secondary_objects, primary_objects = segment_sort(skel_img, objects)

        # Keep segments longer than specified size
        for i in range(0, len(secondary_objects)):
            if len(secondary_objects[i]) > size:
                kept_segments.append(secondary_objects[i])
            else:
                removed_segments.append(secondary_objects[i])

        # Draw the contours that got removed
        removed_barbs = np.zeros(skel_img.shape[:2], np.uint8)
        cv2.drawContours(removed_barbs, removed_segments, -1, 255, 1,
                         lineType=8)

        # Subtract all short segments from the skeleton image
        pruned_img = image_subtract(pruned_img, removed_barbs)
        pruned_contour_img = image_subtract(pruned_img, removed_barbs)
        pruned_img = _iterative_prune(pruned_img, 1)

    # Reset debug mode
    params.debug = debug

    # Make debugging image
    if mask is None:
        pruned_plot = np.zeros(skel_img.shape[:2], np.uint8)
    else:
        pruned_plot = mask.copy()
    pruned_plot = cv2.cvtColor(pruned_plot, cv2.COLOR_GRAY2RGB)
    pruned_obj, pruned_hierarchy, large_contour = find_large_contour(pruned_img, pruned_img)
    cv2.drawContours(pruned_plot, removed_segments, -1, (0, 0, 255), params.line_thickness, lineType=8)
    cv2.drawContours(pruned_plot, pruned_obj, -1, (150, 150, 150), params.line_thickness, lineType=8)

    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(pruned_img, os.path.join(params.debug_outdir, str(params.device) + '_pruned.png'))
        print_image(pruned_plot, os.path.join(params.debug_outdir, str(params.device) + '_pruned_debug.png'))

    elif params.debug == 'plot':
        plot_image(pruned_img, cmap='gray')
        plot_image(pruned_plot)

    # Segment the pruned skeleton
    segmented_img, segment_objects = segment_skeleton(pruned_img, mask)

    return pruned_img, segmented_img, segment_objects, large_contour


vseg = cv2.imread("vseg.png", cv2.IMREAD_GRAYSCALE)
gray = thinning(vseg, thinningType=cv2.ximgproc.THINNING_GUOHALL)
pruned, seg_img, edge_objects, large_contour = prune(skel_img=gray, size=3, mask=vseg)

img_cont_gray = cv2.cvtColor(large_contour, cv2.COLOR_BGR2GRAY)

ret_cont, thresh_cont = cv2.threshold(img_cont_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)

cv2.imwrite("first_cont111.png", thresh_cont)

## then I want to extract the only contour with largest area

Upvotes: 3

Views: 341

Answers (2)

Leox
Leox

Reputation: 370

Use morphology as the first step

ret,thresh = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
rect=cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, rect)

enter image description here

Then find the conneсted component of maximal area

max_component=np.full(opening.shape, 0, np.uint8) 
nb_components,labels,stats,centroids= cv2.connectedComponentsWithStats(opening,8)
max_component[labels == np.argmax(stats[1:, -1])+1]=255

enter image description here

Upvotes: 3

warped
warped

Reputation: 9481

using skimage (if you don't have it: conda install scikit-image)

import scipy.ndimage as ndi
from skimage.morphology import binary_erosion, binary_dilation
from skimage.measure import regionprops
from skimage import io
# 
img = io.imread("first_cont111.png") > 0 # open image and ensure 0,1 data

# get rid of 1-pixel lines
img = binary_erosion(img)
img = binary_dilation(img)

# find individual objects and give them unique labels
label_img, _ = ndi.label(img)

props = regionprops(label_img)

# find the label that corresponds to the object with maximum area:
objects = sorted([(p.label, p.area) for p in props], key=lambda x: x[1], reverse=True)
obj = objects[0][0]

# make an image of the same size as the input image:
output_img = np.zeros_like(img)

# and use fancy indexing to copy the largest object
output_img[label_img==obj] = 1

# now make the contour by subtracting the eroded shape
output_img = output_img - binary_erosion(output_image)

Upvotes: 1

Related Questions