This is not a full answer, but it's too extensive to be a comment. Here's a good way to get all of the components using the wonderful MorphologicalComponents
, paired with ChanVeseBinarize
:
img = Import["https://i.stack.imgur.com/rXlxu.jpg"];
binarized =
ChanVeseBinarize@img;
components =
MorphologicalComponents[binarized];
componentNumbers = Rest@Sort[DeleteDuplicates@Flatten@components];
slices =
Table[
ImageCrop@
Image[Map[If[# == i, 0., 1.] &] /@ components,
"Real", ColorSpace -> "Grayscale"],
{i, componentNumbers[[;; 100]]}
];
Then we can see the effect of this:
Partition[slices, 10] // Grid[#, Dividers -> All] &

Note that we could get bounding boxes instead by supplying Method->"BoundingBox"
to MorphologicalComponents
.
Hope this is enough to get you started.
On getting image coordinates
We can slightly modify the code that generated our slices
to also get the coordinate bbox:
slices =
Table[
With[{component = Map[If[# == i, 0., 1.] &] /@ components},
ImageCrop@
Image[component, "Real", ColorSpace -> "Grayscale"] ->
CoordinateBoundingBox@Position[component, 0.]
],
{i, componentNumbers[[;; 100]]}
];
This way each image is paired with its coordinate bounding box. Then we can operate on the images themselves to figure out which ones are the same. This'll take a tiny bit more work, but shouldn't be bad at this point as the component images are of pretty high quality already.
On getting matching images
So ImageDistance
seems to ID the images pretty well if you use the "EarthMoverDistance"
. I'm sure this could be played with more to make it more solid though.
Here's what I found from a quick survey:
Start by importing your test images and prepping them:
chars =
ImageCrop@*
ColorNegate@*ChanVeseBinarize@*Import /@ {
"https://i.stack.imgur.com/xVqDd.jpg",
"https://i.stack.imgur.com/Uci9a.jpg",
"https://i.stack.imgur.com/1LtJm.jpg"
};
Then the general form will be:
KeySort@GroupBy[
Select[slices,
ImageDistance[First@#, chars[[i]],
DistanceFunction -> "EarthMoverDistance"] < <dissimilarity-max> &
],
ImageDistance[First@#, chars[[i]],
DistanceFunction -> "EarthMoverDistance"] &
]
And here .05 seems to work pretty well for <dissimilarity-max>
although you'll want to tune this. By pre-processing the pulled slice images (filling in gaps and whatnot using, e.g. Dilation
) you can improve this matching process yet further.
Using ImageDimensions as a criterion
As each of these characters has a pretty unique size to it we can use the ImageDimensions
as matching criteria (to scale this we could use the ImageDimensions
ratio).
distanceFunctions[im1_, im2_] :=
{
Norm[ImageDimensions@im1 - ImageDimensions@im2],
ImageDistance[im1, chars[[1]],
DistanceFunction -> "EarthMoverDistance"]
};
matches =
Table[
KeySort@GroupBy[
Select[slices,
And @@ MapThread[
# < #2 &, {
distanceFunctions[First@#, chars[[i]]],
{15, 1}
}] &
],
Last@distanceFunctions[First@#, chars[[i]]] &
],
{i, Length@chars}]
This is near perfect (note that we're not really using the ImageDistance
but we could tune the factors for each match so I'm leaving it in there).