扩张图像以去除间隙并获得原始轮廓尺寸的扩张轮廓

问题描述

我正在尝试去除图像中任何给定形状的间隙或孔洞。

我遵循的替代方法是:我对图像进行阈值处理以获得二值图像并应用膨胀来消除间隙,但这会导致形状变大。如何将这些扩张的轮廓缩放到与原始图像的轮廓相同的大小?或者我可以以某种方式放大图像并仍然具有与原始形状相同的大小吗?有没有更好的选择?

我曾尝试应用扩张,然后在扩张图像的轮廓中进行缩放,但一直无法以一种总是导致扩张轮廓与原始轮廓具有相同大小的方式来做到这一点(有时扩张的轮廓并且缩放后的轮廓与原始轮廓的大小相同,有时不同)。

此外,我如何确保以始终消除原始图像间隙的方式应用扩张,无论其形状有多大?我意识到有时扩张的方式是间隙仍然可见但最小化而不是消除,这不是目标。

这就是我目前所拥有的:

import sys
from pathlib import Path

from PIL import Image
from PIL import ImageCms
from PIL import ImageFile
ImageFile.LOAD_TruncATED_IMAGES = True
Image.MAX_IMAGE_PIXELS = 99999999999999

import numpy as np

import cv2
import numpy
img_path = 'input.png'

def cmyk_to_rgb(cmyk_img):
    img = Image.open(cmyk_img)
    if img.mode == "CMYK":
        img = ImageCms.profiletoProfile(img,"Color Profiles\\USWebCoatedSWOP.icc","Color Profiles\\sRGB_Color_Space_Profile.icm",outputMode="RGB")
    return cv2.cvtColor(numpy.array(img),cv2.COLOR_RGB2BGR)

def cv_threshold(img,thresh=254,maxval=255,type=cv2.THRESH_BINARY_INV):
    if len(img.shape) == 3:
        img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    threshed = cv2.threshold(img,thresh,maxval,type)[1]
    return threshed

def find_contours(img,to_gray=None):
    kernel   = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))
    morphed  = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)
    contours = cv2.findContours(morphed,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    return contours[-2]

def mask_from_contours(ref_img,contours):
    mask = numpy.zeros(ref_img.shape,numpy.uint8)
    mask = cv2.drawContours(mask,contours,-1,(255,255,255),24)
    return cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY)

def dilate_mask(mask,kernel_size=10):
    kernel  = numpy.ones((kernel_size,kernel_size),numpy.uint8)
    dilated = cv2.dilate(mask,kernel,iterations=1)
    return dilated

def draw_contours(src_img,contours):
    canvas = cv2.drawContours(src_img.copy(),(0,0),24)
    x,y,w,h = cv2.boundingRect(contours[-1])
    cv2.rectangle(canvas,(x,y),(x+w,y+h),2)
    return canvas

def scale_contour(cnt,scale):
    M = cv2.moments(cnt)
    cx = int(M['m10']/M['m00'])
    cy = int(M['m01']/M['m00'])

    cnt_norm = cnt - [cx,cy]
    cnt_scaled = cnt_norm * scale
    cnt_scaled = cnt_scaled + [cx,cy]
    cnt_scaled = cnt_scaled.astype(np.int32)

    return cnt_scaled

#GET ORIGINAL CONTOURS
orig_img      = cmyk_to_rgb(str(img_path))
orig_threshed = cv_threshold(orig_img,254,type=cv2.THRESH_BINARY_INV)
orig_contours= find_contours(orig_threshed)
orig_mask     = mask_from_contours(orig_img,orig_contours)
orig_output   = draw_contours(orig_img,orig_contours)

#GET DILATED CONTOURS
dilated_mask     = dilate_mask(orig_mask,80)
dilated_contours= find_contours(dilated_mask)
dilated_output   = draw_contours(orig_img,dilated_contours)

#Just to observe dilation effect
RGBimage = cv2.cvtColor(dilated_output,cv2.COLOR_BGR2RGB)
pilImage = Image.fromarray(RGBimage)
pilImage.save('dilation.png',dpi=(300,300))


# SCALE DILATED CONTOURS
cnt_scaled = scale_contour(dilated_contours[0],0.95)
im_copy = orig_img.copy()
cv2.drawContours(im_copy,[cnt_scaled],(50,50,50),24)


RGBimage = cv2.cvtColor(im_copy,cv2.COLOR_BGR2RGB)
pilImage = Image.fromarray(RGBimage)
#Output
pilImage.save('output.png',300))

下面的图像是有时会发生的错误示例:对于某些形状或图像(输入),扩张的轮廓(第二张图片上的粗线)与原始形状的轮廓不匹配。扩张的轮廓应该与任何给定输入的原始形状相匹配。

Input: Original Shape

Bad Output: Original shape + dilated contours scaled in

解决方法

您可以简单地使用 ConvexHull 概念来填补空白。 在 Hull Image 中,您可以看到一条红线轮廓,它是您想要的蓝线轮廓的 convex hull

import cv2

image = cv2.imread("image.png")

gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
_,threshold = cv2.threshold(gray,100,255,cv2.THRESH_BINARY_INV)
cv2.imshow("Threshold",threshold)

contours,hierarchy = cv2.findContours(threshold,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
contour = max(contours,key=cv2.contourArea)

cnt_hull = cv2.convexHull(contour)

cv2.drawContours(image,[contour],-1,(255,0),2)
cv2.drawContours(image,[cnt_hull],(0,255),2)
cv2.imshow("Hull Image",image)

cv2.waitKey(0)
cv2.destroyAllWindows()

enter image description here