# How to perform noisy circle detection

I want to detect circles that are embedded in noise. For this I created a few circles with some defined signal-to-noise ratio:

SNR = 0.5 (* Signal to noise ratio *)
(* Create circles with random radius and coordinates *)
graph = Graphics[
Table[{AbsoluteThickness[RandomReal[{1, 5}]],
Circle[{RandomInteger[{1, 100}], RandomInteger[{1, 100}]},
RandomInteger[{5, 10}]]}, {x, 1, 8}]]
(* replace some values with noise *)
graphDat = ImageData@ColorConvert[#, "Grayscale"] &@graph;
graphDat2 = graphDat;
onePos1 = Position[graphDat, 0.];
onePos2 = Position[graphDat, 1.];
circNum = Length@onePos1;
pixChange = Round[circNum*SNR];
randPosSample = RandomSample[onePos1, pixChange];
Table[graphDat2[[randPosSample[[x, 1]], randPosSample[[x, 2]]]] =
RandomInteger[], {x, 1, Length@randPosSample}];
Table[graphDat2[[onePos2[[x, 1]], onePos2[[x, 2]]]] =
RandomInteger[], {x, 1, Length@onePos2}];
im=Image@graphDat2


For example, the circles are

I'm looking for methods to detect and mark the circles in the image. The method should be fairly robust to noise and can be implemented in Mathematica. Of course, the method should not know where the circles are beforehand! Ideally, I also want to have a measure of the uncertainty in the detection. Therefore, my first idea was to use a neural network, but I'm not sure how to approach this since I'm fairly inexperienced in using neural networks in Mathematica.

Can somebody help me with this?

Of course, you are welcome to propose a method that is more suited for this problem other than neural networks. The more robust to noise the better.

Thanks!

EDIT:

Thanks to the comments, I found the ImageCorrelate function and added the following code. It seems not to make it easier to detect the circles when there is much noise. Moreover, scanning through different radii and thickness values is quite cumbersome since I do not know these values beforehand. I may be using it wrong though.

Manipulate[{#1, #2, ImageCorrelate[#1, #2] // ImageAdjust} &[im,
kernel[r, t]], {{r, 5, "radius"}, 5, 50}, {{t, 5, "thickness"}, 1,
10}, Initialization -> (im = Image@graphDat2; imdim = ImageDimensions[im];
kernel[r_, t_] :=
Image[Graphics[{{Black, Rectangle[{1, 1}, imdim]}, {White,
AbsoluteThickness[t], Circle[imdim/2, r]}},
Background -> Black, ImageSize -> imdim],
ColorSpace -> "Grayscale"])]

• Convolve the signal with a template circle and look for peaks. – David G. Stork Oct 18 '18 at 19:50
• ImageCorrelate will get you quite far here, I think, as well as some of the noise reduction functions. – Carl Lange Oct 18 '18 at 21:08
• I have tested my circular Hough transform , but I doubt it will work, as some of your circles are really really thin, and the noise level is that high. I also tried some TotalVariationFilter[image, .75, Method -> "Laplacian"], but nose is really heavy. Forget median filtering here. – UDB Oct 18 '18 at 21:34
• Thanks for the comments. I tried ImageCorrelate and edited my post with some code. Got to take a deeper look at the Hough transform as well, thanks UDB – holistic Oct 18 '18 at 21:54
• @holistic There's a whole documentation page of filters that might be useful. – Carl Lange Oct 19 '18 at 10:46

Here is a rudimentary, but very fast, neural network approach.

What we'll be doing is generating masks within which are our circles.

First, we'll come up with a slightly faster way to make the images. RandomImage will already generate noise quite simply, and then we can Blend your circles into that with varying weights for an estimate of signal-to-noise. What we're doing here is generating image->mask pairs. This will be our input to the neural network. Notice I'm only creating them at a size of {101,101} - I only have a teeny laptop CPU to train on, and I want this to go fast because I get bored easily. That's why I'm using that resolution. Use a higher one if you have more resources and not as much coffee.

generateImage2[] :=
Module[{graph, i},
graph = Graphics[
Table[{Thickness[RandomReal[{0.005, 0.015}]],
Circle[{RandomInteger[{1, 100}], RandomInteger[{1, 100}]},
RandomInteger[{3, 10}]]}, 8]];
i = Binarize@ImageResize[Image@graph, {101, 101}];
Blend[{i, RandomImage[1, {101, 101}]}, {RandomReal[{0.4, 0.6}],
1}] -> ColorNegate@i]


Now, we'll generate a bigger dataset.

d = ParallelTable[generateImage2[], 4000]


I used a few thousand images - it's probably worth using more.

Now, let's define our neural network. I'm not sure if this specific type of network has a name. We'll use most of a pre-trained Squeezenet (squeezenet is fast af) as our encoder, and our decoder simply takes a bunch of the squeezenet features, does some deconvolutions, catenates them all together and finally does a convolution. I came up with this architecture more or less by myself, so there are definitely far, far better architectures for doing this. Again, I want this to go fast, that's why this network is the way it is.

ng = NetGraph[
Join[Normal@
NetFlatten[
NetTake[NetReplacePart[squeeze,
"Input" ->
NetEncoder[{"Image", {101, 101}, "ColorSpace" -> "RGB"}]],
"fire5"]],
<|

"f2u" -> {BatchNormalizationLayer[],
DeconvolutionLayer[1, 2, "Stride" -> 2],
ElementwiseLayer["ELU"]},
"f3u" -> {BatchNormalizationLayer[],
DeconvolutionLayer[1, 2, "Stride" -> 2],
ElementwiseLayer["ELU"]},
"f4u" -> {BatchNormalizationLayer[],
DeconvolutionLayer[1, 6, "Stride" -> 4],
ElementwiseLayer["ELU"]},
"f5u" -> {BatchNormalizationLayer[],
DeconvolutionLayer[1, 6, "Stride" -> 4],
ElementwiseLayer["ELU"]},
"cat" -> CatenateLayer[],
"resize" -> ResizeLayer[{101, 101}],
"sig" -> ElementwiseLayer["ReLU"],
"con" -> {ConvolutionLayer[1, 1], LogisticSigmoid}
|>
],
{Fold[#2 -> #1 &,
Reverse@Keys@Normal@NetFlatten[NetTake[squeeze, "fire5"]]],
"fire2" -> "f2u",
"fire3" -> "f3u",
"fire4" -> "f4u",
"fire5" -> "f5u",
{"f2u", "f3u", "f4u", "f5u"} ->
"cat" -> "resize" -> "sig" -> "con"
},
"Input" ->
NetEncoder[{"Image", {101, 101}, "ColorSpace" -> "RGB"}],
"Output" -> NetDecoder[{"Image", "ColorSpace" -> "Grayscale"}]
]


Now we simply train the network.

net = NetTrain[ng, d, ValidationSet -> Scaled[0.1]]


This goes at about 45 inputs/s for me. If you have a GPU I assume it's at least ten times faster.

Now we can evaluate the net. We can see the input images on the left and the output mask on the right.

test = Table[generateImage2[], 20];

Thread[First /@ test -> net[First /@ test]]


It may not be exactly what you asked for, and it's certainly not that good, but it's something! Increasing the resolution, training time, and dataset size would help a fair bit. A better architecture, like a UNET, would also significantly improve accuracy at a cost of training and evaluation time.

There are other ways to do this. For instance, you could train a YOLO network (this is probably closer to what you actually want). You could look at ImageCorrelate and various noise reduction filters. Feel free to experiment with different network architectures - if you have a GPU, you'll be far less limited than I am!

• That looks quite promising and gives me a lot of inspiration, thanks :)! – holistic Oct 19 '18 at 13:56
• I just realized, that NetFlatten and NetTake were first implemented in Mathematica 11.3 and are not available in earlier versions. – holistic Oct 19 '18 at 20:59
• That's unfortunate. However, you can use the original construction of the network instead if you take it from NetModel["Squeezenet...","ConstructionNotebook"]. – Carl Lange Oct 19 '18 at 22:24