# Size of a square object in an image

I recorded the following image (28$\times$28 pixels) showing a bright square object (I used a square object of 35 micrometers side length and imaged it with a microscope):

Scaled to pixel size (for better visualization) it looks the following:

How can I determine the side length?

I started with:

data = ImageData[image, "Byte"];
ListLinePlot[data[[All, 14]], ImageSize -> Medium]


which gives:

and in 3d:

ListPlot3D[ImageData[image,"Byte"]]


Now I could determine the mean position of the strongest slope at both edges and determine from that the side length.

How would you proceed? Are there image analysis functions existing which can be used to detect the edges to subpixel resolution?

• What do you already know about the shape? Can we assume that it is a rectangle? Can we assume that it is in fact a special rectangle: a square? Can we assume that the sides are aligned with the sides of the image? A recognition/measurement algorithm could potentially make use of all this information. – Szabolcs Mar 7 '16 at 11:48
• Maybe I don't understand the question properly, but as I understand it I would have written {First[#], Last[#], First[#] - Last[#]} &@ Ordering@Differences[data[[All, 13]]] to compute the required side lengths from the points of strongest slope. – C. E. Mar 7 '16 at 11:51
• @Szabolcs: Thank you for this important questions. Yes the object is a square (same height and width of 35 micrometers). The lines of the square are experimentally aligned with the sides of the image (as good as possible, but there might be a small invisible rotation angle). The square has in reality a uniform constant white color. – mrz Mar 7 '16 at 12:33
• @C.E. Thank you. This is very elegant solution to find the positions of highest negative and positive slope and from that the side length. The accuracy of your proposal is 1 pixel. Since the object is a square I could use your proposal and then calculate the mean of all possible (smoothed) curves to get subpixel resolution. – mrz Mar 7 '16 at 13:12

data = ImageData[i1];
s = Select[Split@MaxDetect[GradientFilter[#, 2], 1/100] & /@
Join[data, Transpose@data], Length@# == 5 &];
Length[#[[2]]/2] + Length@#[[3]] + Length[#[[4]]/2] & /@ s // Mean // N

(* 19.878 *)

• Excellent ... is it possible for this code to give information about the measurement error (e.g. standard deviation)? – mrz Mar 7 '16 at 14:59
• @mrz Length[#[[2]]/2] + Length@#[[3]] + Length[#[[4]]/2] & /@ s // StandardDeviation // N – Dr. belisarius Mar 7 '16 at 15:08
• @mrz But perhaps you could make some better outlier treatment given the measurements distribution Length[#[[2]]/2] + Length@#[[3]] + Length[#[[4]]/2] & /@ s // Histogram – Dr. belisarius Mar 7 '16 at 15:10
• @mrz I believe the point length = 25 is a clear outlier – Dr. belisarius Mar 7 '16 at 15:15
• @mrz I believe the following is more precise {#[[2]] + 14 - #[[1]]} & /@ Map[Ordering[-#, 1] &, Partition[#, 14] & /@ Select[(GradientFilter[#, 1]) & /@ Join[data, Transpose@data], Max@# > .05 &], {2}] // Mean // N gives 18.125 – Dr. belisarius Mar 7 '16 at 15:35

Another approach: Use UnitBox to model a rectangle or square and fit it to the data:

model = a UnitBox[(x - x0)/b, (y - y0)/c] + d;    (* b x c rectangle *)
model = a UnitBox[(x - x0)/b, (y - y0)/b] + d;    (* b x b square *)


The results look something like this:

Show[
ListPlot3D[data],
Plot3D[{Indeterminate, model /. fit}, {x, 1, 2 xm}, {y, 1, 2 ym}],
PlotRange -> {0, Max[{data[[All, 3]], model /. {x -> x0, y -> y0} /. fit}]}
]


The rectangular model produces a nearly square estimate of average side length just over 18 pixels.

data = Flatten[
MapIndexed[Flatten[{#2, #1}] &,
1];
{xm, ym} = ImageDimensions[image]/2;
zm = Min[data];
a0 = -Subtract @@ MinMax[data];

model = a UnitBox[(x - x0)/b, (y - y0)/c] + d;
fit = FindFit[data, model,
{{a, a0/2}, {b, xm + 0.01}, {c, xm - 0.01}, {d, zm - 1.1},
{x0, xm + 0.01}, {y0, xm - 0.1}},
{x, y},
Method -> "PrincipalAxis"]
(*  {a -> 162.374, b -> 17.9927, c -> 18.3605, d -> 31.1348,
x0 -> 13.8496, y0 -> 15.3473}  *)

Sqrt[b*c] /. fit
(*  18.1757  *)


The square model produces an estimate b of almost 18.

model = a UnitBox[(x - x0)/b, (y - y0)/b] + d;
fit = FindFit[data, model,
{{a, a0}, {b, xm + 0.01}, {d, zm - 0.1},
{x0, xm + 0.01}, {y0, xm - 0.1}},
{x, y},
Method -> "PrincipalAxis"]
(*  {a -> 162.374, b -> 17.9961, d -> 31.1348, x0 -> 13.8987, y0 -> 15.0648}  *)


Note that since the data is discrete and UnitBox is piecewise constant, small changes in the parameters related to the domain, b, c, x0 and y0, do not change the goodness of the fit.

• Thank you for this solution ... I will try to compare it to the other codes in the next days. – mrz Mar 8 '16 at 20:58

This estimates the side length at nearly 21:

edges = EdgeDetect[image];
ComponentMeasurements[
WatershedComponents[Closing[edges, 0.5], CornerNeighbors -> False],
"Area"]
Sqrt[1 /. %]    (* using area to estimate a square *)
(*
{1 -> 437.375, 2 -> 283.375}
20.9135
*)


This is what edges looks like:

• This is a very interesting solution, thank you ... Could you estimate the error as Dr. belisarius did? – mrz Mar 7 '16 at 22:10
• @mrz The component measurement of the area of the edge, which can be found with ComponentMeasurements[1 + WatershedComponents[Closing[edges, 0.5], CornerNeighbors -> False], "Area"], shows the uncertainty in the area to be on the order of 66.75, which from $dA = 2s \, ds$ translates to an uncertainty in side length of a little over 1.5 pixels. I'm not sure I understand Dr. bel's analysis, since it seems that in the image the corners are naturally cut off. This means the average cross-section would underestimate the side. But you can apply his code to the image Closing[edges, 0.5]. – Michael E2 Mar 8 '16 at 3:55
pic = Import["http://i.stack.imgur.com/2uAtN.png"]
Mean @@ Abs[
Subtract @@@
Values[ComponentMeasurements[MorphologicalBinarize[pic],
"BoundingBox"]]]


19.5