MartinO
MartinO

Reputation: 13

OpenCV Smoother contour

I am trying to solve the issue of creating paths around logos with OpenCV. I have attached two images, tekst.png and tekst2.png. I have also attached an image comparison.png that shows the wanted result (created manually) and the result I currently am getting with my program.

If anyone has any tips for me, I'd appreciate it a lot!

Short description of wanted solution:

The code I currently have:


def current_milli_time():
    return round(time.time() * 1000)

def time_calculation_start():
    timing.append(current_milli_time())

def time_calculation_end(string):
    timing.append(current_milli_time())
    print(str(string) + ": ", timing[1] - timing[0], "ms")
    timing.clear()


def render_png(filename):
    print(filename)
    time_calculation_start()
    original_image = cv2.imread(str(filename), cv2.IMREAD_UNCHANGED)
    copy = original_image.copy() # Saved for imagecreation
    time_calculation_end("Setup")

    time_calculation_start()
    if(original_image.shape[2] == 4):
        b,g,r,mask = cv2.split(original_image)
    time_calculation_end("Mask")
    

    # Reduce outer turdss
    time_calculation_start()
    kernel = np.ones((3,3), np.uint8)
    dilation = cv2.dilate(mask,kernel,iterations = 2)
    dilation = cv2.erode(dilation,kernel,iterations = 1)
    time_calculation_end("Dialtion")
    time_calculation_start()
    gaublur = cv2.GaussianBlur(dilation,(16,16),0)
    time_calculation_end("Gaussian blur")


    #Find contours
    time_calculation_start()
    contours, hierarchy = cv2.findContours(gaublur, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    time_calculation_end("Find contours")
    
    print("\tContour layers: ", len(contours))


    # Draw contours
    time_calculation_start()
    cv2.drawContours(copy, contours, -1, (0, 255, 0, 255),1) 
    time_calculation_end("Draw contours")
    print("\n")

    cv2.imwrite(str(render_path) + str(filename), copy)

Upvotes: 1

Views: 5909

Answers (2)

fmw42
fmw42

Reputation: 53164

Here is the alternate method to deal with your other image in Python/OpenCV. Basically we substitute a Gaussian blur and threshold for the dilation. Use the first Gaussian blur sigmas to change the thickness of the white and the second Gaussian blur sigmas to change the thickness of the shadow. Typically, about 3*sigma=thickness in pixels. Or you can use the size arguments for a direct change of thickness in pixels and set the sigmas to 0.

Input:

enter image description here


import cv2
import numpy as np

# read image
img = cv2.imread('10years.png', cv2.IMREAD_UNCHANGED)

# extract bgr image
bgr = img[:,:,0:3]

# extract alpha channel
alpha = img[:,:,3]

# blur alpha channel to smooth features (in place of dilation)
blurred = cv2.GaussianBlur(alpha, (0,0), sigmaX=5, sigmaY=5, borderType = cv2.BORDER_DEFAULT)

# threshold near 0 (in place of dilation)
thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY)[1]

# make edge outline
edge = cv2.Canny(thresh, 0, 200)

# thicken edge
edge = cv2.GaussianBlur(edge, (0,0), sigmaX=0.3, sigmaY=0.3)

# make background
result = np.full_like(bgr, (255,255,255))

# invert threshold image and blur to thresh
thresh_inv = 255 - thresh
thresh_inv = cv2.GaussianBlur(thresh_inv, (0,0), sigmaX=21, sigmaY=21)
thresh_inv = cv2.merge([thresh_inv,thresh_inv,thresh_inv])

# overlay blurred area on background
result[thresh_inv>0] = thresh_inv[thresh_inv>0]

# overlay threshold white region
result[thresh==255] = (255,255,255)

# overlay bgr image
result[thresh==255] = bgr[thresh==255]

# overlay edge
result[edge!=0] = (96,96,96)

# save resulting images
cv2.imwrite('10years_alpha.jpg',alpha)
cv2.imwrite('10years_alpha_thresh.jpg',thresh)
cv2.imwrite('10years_alpha_thresh_inv.jpg',thresh_inv)
cv2.imwrite('10years_alpha_threshd_edge.jpg',edge)
cv2.imwrite('10years_result.jpg',result)

# show thresh and result    
cv2.imshow("bgr", bgr)
cv2.imshow("alpha", alpha)
cv2.imshow("thresh", thresh)
cv2.imshow("thresh_inv", thresh_inv)
cv2.imshow("edge", edge)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Alpha channel image:

enter image description here

Alpha blurred and thresholded:

enter image description here

Above inverted and blurred:

enter image description here

Edge Image:

enter image description here

Result:

enter image description here

Is this what you wanted?

Upvotes: 0

fmw42
fmw42

Reputation: 53164

Here is one way to do that in Python/OpenCV. Note that I reduced the size of your input.

  • Read the input
  • Extract the BGR channels
  • Extract the alpha channel
  • Get the largest contour from the alpha channel to remove small regions
  • Reduce the number of vertices to make it smoother
  • Draw a white filled contour on black background
  • Dilate the contour image
  • Make an edge image and thicken it
  • Make a white background image
  • Invert the dilated contour and blur it for the shadow
  • Overlay the blurred dilated area on the background
  • Overlay the dilated white region
  • Overlay the bgr image
  • Overlay the edge
  • Save the result

Input:

enter image description here

import cv2
import numpy as np

# read image
img = cv2.imread('hjemsokt_small.png', cv2.IMREAD_UNCHANGED)

# extract bgr image
bgr = img[:,:,0:3]

# extract alpha channel
alpha = img[:,:,3]

# get largest contours
contours = cv2.findContours(alpha, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)

# smooth contour
peri = cv2.arcLength(big_contour, True)
big_contour = cv2.approxPolyDP(big_contour, 0.001 * peri, True)

# draw white filled contour on black background
contour_img = np.zeros_like(alpha)
cv2.drawContours(contour_img, [big_contour], 0, 255, -1)

# apply dilate to connect the white areas in the alpha channel
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (40,40))
dilate = cv2.morphologyEx(contour_img, cv2.MORPH_DILATE, kernel)

# make edge outline
edge = cv2.Canny(dilate, 0, 200)

# thicken edge
edge = cv2.GaussianBlur(edge, (0,0), sigmaX=0.3, sigmaY=0.3)

# make background
result = np.full_like(bgr, (255,255,255))

# invert dilated image and blur
dilate_inv = 255 - dilate
dilate_inv = cv2.GaussianBlur(dilate_inv, (0,0), sigmaX=21, sigmaY=21)
dilate_inv = cv2.merge([dilate_inv,dilate_inv,dilate_inv])

# overlay blurred dilated area on background
result[dilate_inv>0] = dilate_inv[dilate_inv>0]

# overlay dilated white region
result[dilate==255] = (255,255,255)

# overlay bgr image
result[contour_img==255] = bgr[contour_img==255]

# overlay edge
result[edge!=0] = (96,96,96)

# save resulting images
cv2.imwrite('hjemsokt_small_alpha.jpg',alpha)
cv2.imwrite('hjemsokt_small_contour.jpg',contour_img)
cv2.imwrite('hjemsokt_small_alpha_dilated.jpg',dilate)
cv2.imwrite('hjemsokt_small_alpha_dilated_inv.jpg',dilate_inv)
cv2.imwrite('hjemsokt_small_alpha_dilated_edge.jpg',edge)
cv2.imwrite('hjemsokt_small_result.jpg',result)

# show thresh and result    
cv2.imshow("bgr", bgr)
cv2.imshow("alpha", alpha)
cv2.imshow("contour_img", contour_img)
cv2.imshow("dilate", dilate)
cv2.imshow("dilate_inv", dilate_inv)
cv2.imshow("edge", edge)
cv2.imshow("result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()

Alpha channel:

enter image description here

Contour Image:

enter image description here

Smoothed Dilated contour image:

enter image description here

Inverted contour blurred:

enter image description here

Edge image:

enter image description here

Result:

enter image description here

Upvotes: 7

Related Questions