# Object detection in a low contrast image

I have a small dark object in the upper left corner of an image.

How can I separate it from the noisy rest and determine its IntensityCentroid?

The problem is that the objects intensity is not significantly lower than the intensity in the lower part of the image:

ListPlot3D[ImageData[image] // N]


UPDATE:

Following the question Finding objects in images with inhomogeneous background I tried:

imageN = ColorNegate[image];
background = ImageConvolve[imageN, GaussianMatrix[7]];
subImage = ImageSubtract[imageN, background];
t = FindThreshold[subImage, Method -> "Entropy"];
binImg = DeleteSmallComponents[Binarize[subImage, t], 5];
pts = ComponentMeasurements[ImageMultiply[image, binImg],
"IntensityCentroid"]
Show[image, Graphics[{Red, Point[pts[[All, 2]]]}]]


which gives:

{1 -> {77.2279, 397.997}}


Unfortunately the other solutions for the mentioned question cannot detect the object here.

As Rahul mentioned also background = MedianFilter[imagen, 10] can be used to detect the object. But this is much slower:

background = ImageConvolve[imageN, GaussianMatrix[7]]; // RepeatedTiming

{0.005, Null}

background = MedianFilter[imageN, 20]; // RepeatedTiming

{22.8, Null}

• Isolate the background, e.g. using MedianFilter[i, 10], and then ImageSubtract it from the original image.
– user484
Aug 16 '16 at 15:43
• Possible duplicate of Finding objects in images with inhomogeneous background
– user484
Aug 16 '16 at 18:06
• LocalAdaptiveBinarize[ColorNegate@img, 20, {.6, 0, .2}] gives me a good result. Aug 16 '16 at 22:12

There is a way to simplify all these process:

img=--your image here--;

ComponentMeasurements[
DeleteSmallComponents@
ColorNegate@
LocalAdaptiveBinarize[img, 50, {1, -2, -.01}], {"Centroid",


{1 -> {{77.0441, 397.853}, 4.65243}}

The first is the Centroid of your region, and the second is equivalent disk radius.

The basic idea is to use LocalAdaptiveBinarize. Firstly, I checked the image's size and have a approximate point size, about 10 pixels, so we set the local adaptive range to 50. Then, we want to eliminate the average, so the first argument in {1,-2,-.01} should be 1, representing the elimination of 1*average. then we want to neglect those noises, and from your ListPlot3D, we can easily see that there's almost no noise over 2*sigma, so a proper value for the second part is 2, then the third represents a slight shift in mean, so -0.01 should be proper.

Check the result, it's beautiful!

Then use ComponentMeasurements and everything will be fine.

# Edit

Note that you need to know the "IntensityCentroid", I made some slight modification to let it show you exactly the "IntensityCentroid" as in previous answer, Binarize will mop out any intensity information.

TakeLargestBy[
ComponentMeasurements[
ImageMultiply[
Dilation[
DeleteSmallComponents@
DiskMatrix[10]],
ImageSubtract[Blur[img, 50], img]], {"IntensityCentroid",


The basic idea is to use Blur and ImageSubtract to get out the background information then subtract it. Then we can use the method in the previous part to narrow down the selection. Finally determin the IntensityCentroid in this way.

{1 -> {{76.4188, 397.894}, 10.66}}

A sight difference, but this time the first part: {76.4188, 397.894} tells you the intensity centriod instead of morphological centroid. also, the radius is more accurate~~~ :)

• How to choose coefficients {1, -2, -.01} and how to automatize it? The simplest way is to use Binarize[], where the threshold value is found by Otsu's algorithm. Aug 17 '16 at 18:25
• @SvyatoslavKorneev there's no need to set it accuratly. plot the intensity graph out, check approximately how much time the signal's intensity greater than the noise'. Set an approximately smaller number.
– Wjx
Aug 18 '16 at 0:11
• @SvyatoslavKorneev as this is only used as a mask in the second solution, so there's no need to be such accurate. :)
– Wjx
Aug 18 '16 at 0:13

Let me give you my general solution for the scientific image processing. The most important is to try to use less subjective parameters, like a threshold value. First, let's assume that the image is taken from charge-coupled device (CCD) or CMOS matrix. These detectors are widely used in most of the modern digital electronics: photo camera, scanner etc.

Second, plot the image histogram.

im = Import["image.png"]
imdata = ImageData[im];
data = Flatten[ImageData[im]];
SmoothHistogram[data, PlotRange -> All]


You can see that the histogram looks like combination of two Gaussian PDF. It has Gaussian shape due to the photon-shot noise. Because the number of photon counts is finite. In general, the noise has Poisson distribution, but for the large number of photon counts Poisson PDF can be approached by Gaussian. Also from the image histogram, you can see that Gaussian curves are very close to each others, and the image has low contrast. To increase the contrast, you have to separate distributions. Let's find distributions property. It can be done by various ways, but I use Expectation-Maximization algorithm for Gaussian Mixture Models. I gave the example of the algorithm for other question. The results of the algorithm you can find at the image below. Please note, it is important to choose the the starting point as better as you can, otherwise it may converge to single Gaussian only.

The result of the algorithm, where the red curve shows the recovered distribution, where two Gaussian functions where used.

And the left Gaussian has mean value

μ1 = 0.588906


and the right

μ2 = 0.623881


Let's define linear mapping to increase the contrast.

df[gl_] := If[gl < μ2, (gl - μ2)/(μ1 - μ2), 0];


For the left maximum it gives 1 and for the right 0. Now let's map the initial image to the high-contrast image.

{nRow, nCol} = Dimensions[imdata]
imdf = Table[df[imdata[[i]][[j]]], {i, 1, nRow}, {j, 1, nCol}];


And plot.

Image[imdf]


As you can see, the spot and other features have higher contrast now.

Now, you perform the simple threshold-based segmentation, where the threshold value is between pikes 0.5.

segment =
Table[If[imdf[[i]][[j]] > 0.5, 1, 0], {i, 1, nRow}, {j, 1, nCol}];

binary = Image[segment]


Delete small and border components

binnew = DeleteSmallComponents[DeleteBorderComponents[binary], 100]


ComponentMeasurements[binnew, "IntensityCentroid"]
{1 -> {77.2571, 397.8}}


UPDATE:

Actually, Otsu's algorithm gives similar result directly for the low-contrast image. As far as I see, the idea behind the Otsu's algorithm is similar to what I write here. But now it is clear how to extend the segmentation to arbitrary number of Gaussian.

Segmentation with Otsu's algorithm of the low-contrast image.

ColorNegate[Binarize[im]]


• "First, let's assume that the image is taken from charge-coupled device (CCD) matrix." - shouldn't then we also assume that the image is sRGB-encoded and we have to convert it to linear RGB before processing? Aug 17 '16 at 5:37
• Thank you for pointing me out. It could be, but the intensity histogram has Gaussian like shape, so I assume the gray color was linearly transformed from the intensity of CCD. This image could be also compressed. For scientific purpose, it is better to work with TIFF images. Correct me plz if I am wrong. Thank you! Aug 17 '16 at 18:42
• For this image MinMax[imdata] returns {0.521569, 0.662745}, hence we can safely assume approximately linear relation between linear RGB and sRGB. But in some situations working in a physically meaningful colorspace can be important, for example as in this case. Aug 18 '16 at 4:02
• The cam has a CMOS sensor ...
– mrz
Aug 18 '16 at 21:16
• I will add CMOS to the answer. In term of shot noise CCD and CMOS are the same. Aug 18 '16 at 22:41