# How to calculate the position of a figure in an image?

My question is about recognising soccer players in the pitch. Thanks to Bill S who wrote an excellent script to recognise parts of the players. I found this issue after testing his solution. I will describe his solution and then I will try to explain my 'new' problem.

img = Import["http://i.stack.imgur.com/K60Nl.gif"]


This shows you the following image: I want to select all 'blue' soccer players

blue = PixelValuePositions[img, {0.580, 0.705, 0.796}];
{h, v} = ImageDimensions[img];
imgD = ConstantArray[0, {h, v}];
Length[Table[imgD[[Sequence @@ blue[[i]]]] = 1, {i, 1, Length[blue]}]];
bluePeople = ImageRotate[Dilation[Erosion[Image[imgD], 4], 8], Pi/2]
ComponentMeasurements[redPeople, "Centroid"]; pixels = ComponentMeasurements[bluePeople, "Centroid"][[All, 2]];


This gives the values: {{95.8636, 1120.96}, {1227.08, 1058.48}, {1598.58, 1049.69}, {1501.61, 919.903}, {1481.09, 854.259}, {83.0436, 740.669}, {61.3402, 662.205}, {786.63, 325.184}, {735.35, 250.843}, {790.906, 207.406}}

Show[Image[img, ImageSize -> 600],
Graphics[{Black,
Table[Inset[ToString[i], pixels[[i]]], {i, 1, Length[pixels]}]}]] As you can see, the players below wear blue socks so the script recognises three 'centroids'.

Why do I want this? I want to calculate the position of the 'blue' soccer players in the pitch. The 'best way' to do this is based on a central pixel position. So calculating the centroid is logical. But in this situation described, one soccer player has three centroids.

How can I combine these three?

• Perhaps you can dilate the final binary image to coalesce nearby blobs?
– rm -rf
Feb 27, 2014 at 16:11

In cases like the one you've at hand, where there are no superposition of "blue" players it's enough to be able to isolate the morphological component for each player in the field and then ask if your centroids are members of the same morphological component.

I'll show a way to isolate the players heavily based on nikie's outstanding Martian chronicles answer:

img = Import["http://i.stack.imgur.com/K60Nl.gif"];

getDist[img_, pixels_] := Module[{dist, edist, logPdf, rgb},
dist = MultinormalDistribution[{mR, mG, mB}, {{sRR, sRG, sRB}, {sRG, sGG, SGB}, {sRB, sGB, sBB}}];
edist = EstimatedDistribution[pixels, dist];
logPdf = PowerExpand@Log@PDF[edist, {r, g, b}];
rgb = ImageData /@ ColorSeparate[GaussianFilter[img, 3]];
logPdf /. {r -> rgb[], g -> rgb[], b -> rgb[]}]

grassSample = ImageTake[img, {340, 900}, ImageDimensions@img];
grassPixels = Flatten[ImageData[grassSample], 1];
p = getDist[img, grassPixels];
i1 = DeleteSmallComponents[Image[-p/20], 100] Now you can go with your method for finding blue players (though I don't like it too much) and find out what centroids belong to the same player:

mci1 = MorphologicalComponents@i1;
blue = PixelValuePositions[img, {0.580, 0.705, 0.796}];
{h, v} = ImageDimensions[img];
imgD = ConstantArray[0, {h, v}];
Table[imgD[[Sequence @@ blue[[i]]]] = 1, {i, 1, Length[blue]}];
bluePeople = ImageRotate[Dilation[Image@imgD, 8], Pi/2];
centroids = Round@ComponentMeasurements[bluePeople, "Centroid"][[All, 2]];
TableForm[Transpose@{ss = {Last@ImageDimensions@i1 - #[], #[]} & /@  centroids,
mci1[[Sequence @@ ##]] & /@ ss},
TableHeadings -> {None, {"Centroid", "Cluster"}}] The method you have works quite well, as long as you're careful about the parameters:

img = Import["http://i.stack.imgur.com/K60Nl.gif"];
blue = PixelValuePositions[img, {0.580, 0.705, 0.796}];
{h, v} = ImageDimensions[img];
imgD = ConstantArray[0, {h, v}];
Length[Table[imgD[[Sequence @@ blue[[i]]]] = 1, {i, 1, Length[blue]}]];
bluePeople = ImageRotate[Dilation[Image[imgD], 40], Pi/2]
ComponentMeasurements[bluePeople, "Centroid"]; Except for some typos, I just changed the amount of the Dilation to be enough to join together the various parts of the nearer player. Fortunately, the other players are far enough apart that they remain separated.

Here is my try. But it seems to work. First you choose a color: In you case the blue you are looking for in RGB is

teamcolor={156, 199, 220};


Now we simply look at a distance measure of every color in the image from this one

distcolor=Map[Function[{x}, NormalizedSquaredEuclideanDistance[x, teamcolor]],
ImageData@img, {2}];


We then apply as the comment mentioned a dilation with a Disk of the size of about half the player size.

Dilation[ColorNegate@Binarize[Image@tt, 0.499], DiskMatrix] I'm going to assume we aren't really concerned about how fast this runs, or about using it to detect players in several frames of a video -- just this single still image. In this case, the quickest way I can come up with is to convert the white "blobs" in the image into a graph:

First, make a matrix from your data:

L = {{95.8636, 1120.96}, {1227.08, 1058.48}, {1598.58,
1049.69}, {1501.61, 919.903}, {1481.09, 854.259}, {83.0436,
740.669}, {61.3402, 662.205}, {786.63, 325.184}, {735.35,
250.843}, {790.906, 207.406}};
A[\[Alpha]_] :=
Table[If[Norm[L[[i]] - L[[j]]] < \[Alpha], 1, 0], {i, 1,
Length[L]}, {j, 1, Length[L]}];
Manipulate[MatrixForm[A[\[Alpha]]], {\[Alpha], 0, 500}]


This A[\[Alpha]] command gives you the adjacency matrix of a graph. Then you should make a corresponding graph:

<< Combinatorica


You can get an idea of what the abstract graph looks like by varying \[Alpha]:

Manipulate[ShowGraph[G[\[Alpha]]], {\[Alpha], 0, 500}]


The parameter alpha is the distance between (centroids of) white blobs that we are willing to allow before we consider them to be pieces of the same person. (Note that this idea, like many others, will not work if two players are standing right next to each other, e.g. to block a free kick.)

So the question is which alpha to pick? Well, first you can overlay with your image and see how it looks:

img = Import["http://i.stack.imgur.com/K60Nl.gif"];
Manipulate[
Show[
img,
GraphPlot[G[\[Alpha]],
VertexCoordinateRules -> Table[i -> L[[i]], {i, 1, Length[L]}]],
ImageSize -> 300
],
{\[Alpha], 0, 500}]


This uses GraphPlot instead of GraphShow since we're now plotting the graph in the x-y plane with the image as background. We use VertexCoordinateRules because we want the vertices to actually show up where the players arms, legs, torsos, etc are.

You can play with the parameter alpha and when it gets to be about 127, the very last piece of a guy is connected (the guy in the front). Even at alpha=95 that guy is connected, just not every part to every other part.

If you take alpha to be too large, like 160, you start connecting pieces of two different guys (up in the top right).

In order to make this automatic, you can simply run through all the alphas until you get the right number of guys:

\[Epsilon] = 0.1; \[Alpha] = 0; n = 6;
While[Length[ConnectedComponents[G[\[Alpha]]]] > n, \[Alpha] += \[Epsilon]]
Show[img,
GraphPlot[G[\[Alpha]],
VertexCoordinateRules -> Table[i -> L[[i]], {i, 1, Length[L]}]],
ImageSize -> 300
]


Here epsilon is the step-size for alpha (0.1 is very cautious, you could use 1 or even 5) and n is the number of players you expect to show up. Once there are n connected components of the graph of body parts, the thing assumes it is done.

One thing this algorithm could, but does not, accommodate is the slope of the field. Players who are closer to the camera will have body parts that are further apart. I see no need to really hash out those kinds of tweaks in a test-case like this, but for high-scale implementation this might be important too.

Anyway, once you know the correct value of alpha, you can just lump them together according to those connected components:

PLAYERS = ConnectedComponents[G[\[Alpha]]]
Table[
Mean[L[[PLAYERS[[i]]]]],
{i, 1, Length[PLAYERS]}]


Assuming you didn't change the value of alpha since running that loop, there will be six coordinates in the list PLAYERS representing their coordinates. You could use a different method besides just the Mean[] here, but I'm doing this the quick and lazy way. And if you want to wrap it up nicely to see how it all goes, maybe this:

TEAM[\[Alpha]_] :=
Show[
img,
GraphPlot[G[\[Alpha]],
VertexCoordinateRules -> Table[i -> L[[i]], {i, 1, Length[L]}]],
ListPlot[Table[Mean[L[[ConnectedComponents[G[\[Alpha]]][[i]]]]],
{i, 1, Length[ConnectedComponents[G[\[Alpha]]]]}],
PlotStyle -> Green],
Graphics[
Text["# Players: " <>
ToString[Length[ConnectedComponents[G[\[Alpha]]]]], {100, 100}]],
ImageSize -> 300
]
Manipulate[TEAM[\[Alpha]], {\[Alpha], 0, 500}]


Notice that when you make alpha too big, the centroids of certain guys start combining at some point between the two (or more) of them.

cvb = ComponentMeasurements[
SelectComponents[ DeleteSmallComponents[
ChanVeseBinarize[ImageResize[img, 300], "TargetColor" -> LightBlue,
"LevelPenalty" -> {3, 2}]],
"Rectangularity", # > .2 &], "Centroid"];
ImageMultiply[ImageResize[img, 300],
Graphics[{Red, Disk[#, 3] & /@ cvb[[All, 2]]}, PlotRange -> {{0, 300}, Automatic},
ImagePadding -> 0, Axes -> True, AxesOrigin -> {0, 0}]]
` 