i have the following png with me:
[![enter image description here][1]][1]
in the above image i want to find the length of every dash and also the length between every 2 dashes ie the gaps.
Parallely, i have this code which gives me length of a line:
Another idea that i have in my mind about finding length of every dashes could be that, if i go on finding the medial axes of the dashes, then would traversing in the same direction of the medial axis(a line which passes through the centre of the concerned object) could some how give me the length in any way?? medial axis output: the green line inside the black is the medial axis i get, i have made use of open cv to fine the medial axis, but again i am not sure if that would work on another complex images such as the circle above and also, how do i find the length of gaps... Any help would be appreciated!
This is a total brute force code. Is not optimized at all, is super slow. I hope somebody vectorize it totally or in part in another answer. I invite to patch it.
This code detects each dash as a "blob", and that's the slowest part of the code. But there are many libraries for blob detection, which should be much faster. I just don't know how to use them.
The code scans line by line, and when it finds a black pixel, it adds it to a blob, but it results in some blobs being detected multiple times. This image shows the centers of the blobs:
The second stage, which is the slowest, compares all blobs to join and discard the adjacent ones.
After the duplicated blobs are deleted, the distance between blob center is calculated by finding the nearest neighbor of each blob, and taking the median distance, to avoid the influence of outsiders.
Here the distance between centers is drawn with yellow arrows
The blobs get stored as instances of the class blob, which stores the pixels of each blob, calculates the center, and the axis of symmetry, to be able to tell the orientation of each dash.
The class blob has a function named extremeAxisOfInertia(), which returns 2 vectors: the first vector points in the direction of the longest side of the dash, and the second vector point to the shorter side
principalAxis, secondaryAxis = blobInstance.extremeAxisOfInertia()
Also, the maximum dimension of each blob (measured between centers of pixels) is given by the function dimensions()
blobLength, blobWidth = blobInstance.dimensions()
It is calculated as the length between the most distant pixels on the direction of the principal axis of inertia. Here the dots show the center of each pixel of one blob:
The distance of separation between dashes is calculated as the difference between the median separation of the blobs center, minus the median length of the blobs.
As the title on the plot below says, the image posted by the OP results in a median separation of 9.17 pixels, and a median dash length of 7.60 pixels. But hat calculation only uses the closest neighbor. There is a lot of space for improvement.
print("Imports...")
import cmath
import numpy as np
import cv2
import matplotlib.pyplot as plt
URL = "https://i.sstatic.net/eNV5m.png"
def downloadImage(URL):
'''Downloads the image on the URL, and convers to cv2 BGR format'''
from io import BytesIO
from PIL import Image as PIL_Image
import requests
response = requests.get(URL)
image = PIL_Image.open(BytesIO(response.content))
return cv2.cvtColor(np.array(image), cv2.COLOR_BGR2RGB)
# index of row and column on each pixel=[pixel[_rw],pixel[_cl]]=[row,column]
_rw, _cl = 0, 1
class blob(object):
# array of xy coordinates
pixels: list
blobNumber: int
def __init__(self, blobNumber, row=None, col=None):
if row is not None and col is not None:
self.pixels = [[row, col]]
self.blobNumber = blobNumber
def addPixel(self, row, col):
if [row, col] not in self.pixels:
self.pixels.append([row, col])
else:
raise ValueError("Pixel already in blob")
# baricenter of the blob
def centerRC(self):
'''returns row and column of the baricenter of the blob
returns a pair of floats which may not match a specific pixel
returns rowY,columnX
'''
center = np.mean(self.pixels, axis=0)
return center[_rw], center[_cl]
def Ixx(self):
''' central moment of the blob respect to x axis'''
Cy, Cx = self.centerRC()
return sum((p[_rw]-Cy)**2 for p in self.pixels)
def Iyy(self):
''' central moment of the blob respect to y axis'''
Cy, Cx = self.centerRC()
return sum((p[_cl]-Cx)**2 for p in self.pixels)
def Ixy(self):
''' central moment of the blob respect to x and y axis'''
Cy, Cx = self.centerRC()
return sum((p[_rw]-Cy)*(p[_cl]-Cx) for p in self.pixels)
def extremeAxisOfInertia(self):
'''Calculates the principal axis of inertia of the blob
returns unitary vectors pointing on the direction normal to the
max [principal] axis of inertia
and the minimum [principal] axis of inertia
Also returns the maximum and minimum momentum along the principal axis of inertia
returns maxAxis, minAxis, maxI, minI
^minAxis
|max axis of inertia
┌──────|──────┐
---│------|------│---minor axis of inertia ──>maxAxis
└──────|──────┘
|
'''
Ixx = self.Ixx()
Iyy = self.Iyy()
Ixy = self.Ixy()
I = np.array([[Ixx, Ixy], [Ixy, Iyy]])
# print(f"I = {I}")
eigenvalues, eigenvectors = np.linalg.eig(I)
eigMatrix = np.array(eigenvectors)
Imax = np.matmul(eigMatrix, np.matmul(I, eigMatrix.T))
# print(f"eigenvalues = {eigenvalues}")
# print(f"eigenvectors = {eigenvectors}")
# print(f"Imax = {Imax}")
if Imax[0, 0] >= Imax[1, 1]:
maxAxis = eigenvectors[0]
minAxis = eigenvectors[1]
else:
maxAxis = eigenvectors[1]
minAxis = eigenvectors[0]
return maxAxis, minAxis, max(Imax[0, 0], Imax[1, 1]), min(Imax[0, 0], Imax[1, 1])
def dimensions(self):
'''
returns the dimensions of the blob, measured between pixel centers
assuming that the blob is roughly a rectangle with small side b and large side h
returns h,b
┌─────────────h─────────────┐(measured between pixel centers)
┌─────────────────────────────┐
| |┐
│pixel center non represented │|b (measured between pixel centers)
| |┘
└─────────────────────────────┘
'''
maxAxis, minAxis, maxI, minI = self.extremeAxisOfInertia()
# rotate all pixel coordinates to convert max axis into horizontal
# and extract the maximum and minimum x coordinates
rotor = complex(maxAxis[_cl], maxAxis[_rw])
pixelsHorizontalized = np.array(
[complex(p[_cl], p[_rw])/rotor for p in self.pixels])
x, y = pixelsHorizontalized.real, pixelsHorizontalized.imag
h = max(x)-min(x)
b = max(y)-min(y)
return h, b
def plotPixels(self):
import matplotlib.pyplot as plt
plt.scatter([p[_cl] for p in self.pixels], [p[_rw]
for p in self.pixels], label=f" blob {self.blobNumber}")
centerR, centerC = self.centerRC()
maxAxis, minAxis, _, __ = self.extremeAxisOfInertia()
length, width = self.dimensions()
plt.plot([centerC, centerC+maxAxis[_cl]*length/2], [centerR,
centerR+maxAxis[_rw]*length/2], 'r', label="Max axis")
plt.plot([centerC, centerC+minAxis[_cl]*width/2], [centerR,
centerR+minAxis[_rw]*width/2], 'b', label="Min axis")
ax = plt.gca()
ax.invert_yaxis() # on images y axis goes down
ax.legend()
plt.title(
f"Blob {self.blobNumber}; max dimension = {length:.2f}, min dimension = {width:.2f}")
plt.show()
print("Fetching image from URL...")
image = downloadImage(URL)
cv2.imshow('Original image', image)
cv2.waitKey(10)
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
threshold = 128
ret, thresh = cv2.threshold(
img_gray, type=cv2.THRESH_BINARY_INV, thresh=threshold, maxval=255)
print("Classification of points in blobs...")
# all pixels classified as 0 blob
# add extra border rows and columns to avoid problems with the blob classifier
padThresh = np.pad(thresh, (1, 1), constant_values=0)
classif = padThresh*0
blobs = {} # key=blobCount, value=blob object
blobCount = 0
neighborPixelsToCheck = [(-1, -1), (-1, 0), (-1, 1), (0, -1)]
for row in range(1, padThresh.shape[0]-1): # avoided first and last row added
# avoided first and last column added
for col in range(1, padThresh.shape[1]-1):
# if pixel is black...
if padThresh[row, col] > threshold:
# if up and left pixels are also black...
if any(padThresh[row+y, col+x] > threshold for y, x in neighborPixelsToCheck):
numBlob = max(classif[row+y, col+x]
for y, x in neighborPixelsToCheck)
classif[row, col] = numBlob
blobs[numBlob].addPixel(row, col)
else:
blobCount += 1
classif[row, col] = blobCount
blobs[blobCount] = blob(blobCount, row=row, col=col)
plt.imshow(classif/max(classif.flatten()))
# Collect centers of all blobs
centers = [value.centerRC() for key, value in blobs.items()]
plt.scatter([c[_cl] for c in centers], [c[_rw]
for c in centers], label="blobs \ncenters", color="red", marker="+")
#legend on upper right corner
plt.legend(loc="center")
plt.title("Blobs and Centers of blobs detected")
plt.show()
print("Unifying blobs...")
# unify adjacent blobs
keys = list(blobs.keys())
for idx, this in enumerate(keys[:-1]):
if this in blobs.keys(): # It may had been deleted by a previous loop
print(f" Comparing blob {this} of {len(keys)}...")
thisPixels = blobs[this].pixels[::-1] # reverse to speed up comparison
for other in keys[1+idx:]:
if other in blobs.keys(): # It may had been deleted by a previous loop
otherPixels = blobs[other].pixels
# if squared euclidean distance between centers of blobs < 2
if any((p[0]-q[0])**2+(p[1]-q[1])**2 < 2.1 for p in thisPixels for q in otherPixels):
# merge blobs
blobs[this].pixels.extend(otherPixels)
# reverse to speed up comparison
thisPixels.extend(otherPixels[::-1])
# remove other blob
del blobs[other]
plt.imshow(classif/max(classif.flatten()))
# Calculating median distance between blobs
# Collect centers of all blobs
centers = np.asarray([value.centerRC() for key, value in blobs.items()])
plt.scatter([c[_cl] for c in centers], [c[_rw]
for c in centers], label="centers", color="red", marker="+")
def closest_node(node, nodes):
# nodes = np.asarray(nodes)
deltas = nodes - node
dist_2 = np.einsum('ij,ij->i', deltas, deltas)
return np.argmin(dist_2)
nearest = []
for idx, c in enumerate(centers):
Cent_withoutC = np.delete(centers, idx, axis=0)
nearest.append(Cent_withoutC[closest_node(c, Cent_withoutC)])
# plt.scatter([c[_cl] for c in nearest],[c[_rw] for c in nearest],label="nearest",color="red",marker="+")
distances = [((n-c)[0]**2+(n-c)[1]**2)**0.5 for c, n in zip(centers, nearest)]
for c, n in zip(centers, nearest):
x, y, dx, dy = c[_cl], c[_rw], (n-c)[_cl], (n-c)[_rw]
plt.arrow(x, y, dx, dy, length_includes_head=True, head_width=1 /
4*(abs(dx*dy))**.5, color="yellow", edgecolor="black")
plt.title("Nearest neighbor of each blob")
plt.show()
plt.scatter(x=range(len(distances)), y=np.sort(distances),
label="Distances between blobs", color="red")
# the median value is better than an average,
# because is less sensible to outsiders
medianDistance = np.median(distances)
plt.plot([1, len(distances)], [medianDistance, medianDistance],
label="Median distance", color="red")
title=f"Median distance between blob centers = {medianDistance:.2f} pixels"
# Median value of the largest dimension of the blobs
blobsLengths=[]
for key,_blob in blobs.items():
length,width=_blob.dimensions()
blobsLengths.append(length)
medianBlobLength = np.median(blobsLengths)
plt.scatter(x=range(len(blobsLengths)), y=np.sort(
blobsLengths), label="blobs Lengths", color="blue")
plt.plot([1, len(blobsLengths)], [medianBlobLength, medianBlobLength],
label="Median blob length", color="blue")
# add to title the median value of the largest dimension of the blobs
title=f"{title}\nMedian blob length = {medianBlobLength:.2f} pixels"
medianBlobSeparation=medianDistance-medianBlobLength
title=f"{title}\nMedian blob separation = {medianBlobSeparation:.2f} pixels"
plt.title(title)
plt.legend()
plt.show()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With