-2

I want to split images like this in a way that every symbols gets splits up vertically kind of like this input image:

![input image][1]

to this:

![here][2]

The problem is each symbol might have different width so I can't really fix the splitting points like we do in array splitting. If all objects had same width then I could segment the image base on width. In this scenario, what logic I should use to extract these connected objects?

spyderB
  • 59
  • 5
  • Do you know how many symbols there are? If so you could say the (n-1) lines with the most white pixels are where your cuts should be. If you don't know how many there are an approximation with image-width/average-symbol-width might be possible? – Carl Jan 25 '22 at 17:04
  • just erode and then connected components/contours... I don't know why the other suggestions seem to just flail around. – Christoph Rackwitz Jan 25 '22 at 19:40

1 Answers1

0

First load the img from the url

import numpy as np
import urllib.request
from PIL import Image
from matplotlib import pyplot as plt

urllib.request.urlretrieve(
  'https://i.stack.imgur.com/GRHzg.png',
   "img.png")
img = Image.open("img.png")
img.show()

enter image description here

Then consider the black part as "filled" and convert in numpy array

arr = (np.array(img)[:,:,:-1].sum(axis=-1)==0)

If we sum the rows values for each column we can have a simple sum of how much pixel are filled in each column:

plt.subplot(211)
plt.imshow(arr, aspect="auto")
plt.subplot(212)
plt.plot(arr.sum(axis=0))
plt.xlim(0,arr.shape[1])

enter image description here

finally if we compute the differential of this sum over the columns we can obtain the following result:

plt.subplot(211)
plt.imshow(arr, aspect="auto")
plt.subplot(212)
plt.plot(np.diff(arr.sum(axis=0)))
plt.xlim(0,arr.shape[1])

enter image description here

At this point you can simply chose a threshold and cut the image:

threshold = 25
cut = np.abs(np.diff(arr.sum(axis=0)))>threshold
x_lines = np.arange(len(cut))[cut]

plt.imshow(arr, aspect="auto")
plt.vlines(x_lines, 0, arr.shape[0], color="r")

enter image description here

This is my solution and it works fine, but it is sensitive to the chosen threshold and to the columns gradient. I hope it is useful.

  • 1
    this only works because the picture happens to have vertical edges between these touching objects (from the squares). if that weren't the case (as can be seen on the right end of the triangle), there would be no peaks in the `diff` result – Christoph Rackwitz Jan 25 '22 at 21:04
  • @spyderB I think it doesn't, but you can try – Salvatore Daniele Bianco Jan 26 '22 at 08:30
  • @spyderB In this case it does not work because there are non-vertical edges (how suggested Christoph). I think that a more general solution could be to find local minimum in the sum function: https://i.stack.imgur.com/kMZqE.png – Salvatore Daniele Bianco Jan 26 '22 at 09:32
  • @spyderB I answered in https://stackoverflow.com/questions/70860087/how-to-vertically-segment-an-image/70863364#70863364 – Salvatore Daniele Bianco Jan 26 '22 at 12:24
  • @spyderB use `plt.save("filename.png")` before showing the picture you want to save. Look at thist: https://stackoverflow.com/questions/9622163/save-plot-to-image-file-instead-of-displaying-it-using-matplotlib – Salvatore Daniele Bianco Jan 27 '22 at 15:14
  • @spyderB sure. the iteration cycles on each separator you have (vertical red lines). In the first iteration, when `i==0` you want to keep the picture from the first column (the very left one) to the first separator (first vertical red line `x_lines[0]`); after, you want to keep in the others iterations the picture between two vertical lines `x_lines[i-1]` and `x_lines[i]` (practically between the first and the second, the second and the third, etc...); finally, when the cycle have finished, you have to keep the last part of the figure between the last line and the last column of the figure. – Salvatore Daniele Bianco Jan 28 '22 at 15:46