Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Check for areas that are too thin in an image

I am trying to validate black and white images (more of a clipart images - not photos) for an engraving machine.
One of the major things I need to take into consideration is the size of areas (or width of lines) since the machine can't handle lines that are too thin - so I need to find areas that are thinner than a given threshold.

Take this image for example:

enter image description here

The strings of the harp might be too thin to engrave.

I am reading about Matlab and OpenCV but image processing is an area I am learning about for the first time.

I am a Java / C# developer so implementation done with one of those languages will be best for me but any direction will be greatly appreciated.

like image 560
James Black Avatar asked Feb 02 '15 10:02

James Black


2 Answers

A solution using matlab utilizing image morphological operations:

Define the minimal thickness of allowed area, for example, minThick=4

BW = imread('http://i.stack.imgur.com/oXKep.jpg'); 
BW = BW(:,:,1) < 128; %// convert image to binary mask
se = strel('disk', minTick/2, 0); %// define a disk element   
eBW = imerode( BW, se ); %// "chop" half thickness from mask
deBW = imdilate( eBW, se ); %// dilate the eroded mask

Eroding and dilating should leave regions that are thicker than minThick unchanged, but it will remove the thin areas

invalidArea = BW & ~deBW; %// pixels that are in BW but not in deBW

Resulting with:
enter image description here

You can read more about imdilate and imerode in the linked documentation.

like image 137
Shai Avatar answered Sep 22 '22 08:09

Shai


This is primarily for self-containment, but this is the equivalent code to what @Shai has performed in Python. I used the numpy and OpenCV packages from Python. The equivalent code to doing it in Python would simply be this:

import numpy as np # Import numpy package
import cv2 # Import OpenCV package

orig = cv2.imread('oXKep.jpg') # Read in image from disk
BW = orig[:,:,2] < 128 # Threshold below 128 to invert image
minThick = 5 # Define minimum thickness
se = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (minThick,minThick)) # define a disk element
finalBW = 255*cv2.morphologyEx(BW.astype('uint8'), cv2.MORPH_OPEN, se) # "chop" half thickness from mask and dilate the eroded mask

# Find invalid area
invalidArea = 255*np.logical_and(BW, np.logical_not(finalBW)).astype('uint8') 

# Show original image
cv2.imshow('Original', orig)

# Show opened result
cv2.imshow('Final', finalBW)

# Show invalid lines
cv2.imshow('Invalid Area', invalidArea)

# Wait for user input then close windows
cv2.waitKey(0)
cv2.destroyAllWindows()

A few intricacies that I need to point out:

  1. OpenCV's imread function reads in colour channels in reverse order with respect to MATLAB. Specifically, the channels are read in with a blue-green-red order. This means that the first channel is blue, the second channel green and third channel red. In MATLAB, these are read in proper RGB order. Because this is a grayscale image, the RGB components are the same so it really doesn't matter which channel you use. However, in order to be consistent with Shai's method, the red channel is being accessed and so we need to access the last channel of the image through OpenCV.
  2. The disk structuring element in MATLAB with a structure number of 0 is essentially a diamond shape. However, because OpenCV does not have this structuring element built-in, and I wanted to produce the minimum amount of code possible to get something going, the closest thing I could use was the elliptical shaped structuring element.
  3. In order for the structuring element to be symmetric, you need to make sure that the size is odd, so I changed the size from 4 to 5 from Shai's example.
  4. In order to show an image using OpenCV Python, the image must be at least an unsigned 8-bit integer type. Binary images for display using OpenCV are not supported, and so I artificially made the binary images uint8 and multiplied the values by 255 before displaying them.
  5. You can combine the erosion and dilation operations into one operation using morphological opening. Opening seeks to remove thin lines or disconnect objects that are thinly connected but maintain the shape of the original more larger objects. This is the the point of eroding first so that you can remove these lines but you will shrink the objects a bit in terms of the area, then dilating after so that you can restore the shapes back to their original size (mostly). I exploited that by performing a morphological opening via cv2.morphologyEx.

This is what I get:

enter image description here

like image 40
rayryeng Avatar answered Sep 25 '22 08:09

rayryeng