How to count the objects in the image with PHP?

39

The original photo of the beans is this:

Decreasetheresolutionofthephoto,Ialreadyappliedafiltertobeingrayscaleandincreasedthecontrasttothemaximumtobecomeblackandwhite.ThenIexaminedthecolorofeachpixelandmountedanarrayarray.IfIloopoverthisarrayandwritea0forthewhitepixelsandanxfortheblackones,Igetthefollowing:

From there I'm trying to find a way to count how many portions of x have in that array. Can anyone help me with this?

    
asked by anonymous 11.02.2017 / 01:53

2 answers

43

It is worth emphasizing that this response was more due to the simplicity of the solution than the performance of the same.

Logic

The most natural thing to think about is: I will go through all the values in my matrix checking if it is 1, if it is, increase the amount of beans. A very obvious problem is that in this way it will be considered, for every 1 of the beans, as a different bean. In the example given below, instead of resulting in 3 beans, it would result in 88 (total of 1 in the matrix). A simple way to get around this is when you find a value 1 in the matrix, replace it with 2 and check the peripheral values, ie the four side values and the four diagonal values, searching for other values 1. If you find, repeat the process, replacing them with 2 and checking their peripheral values. Thus, for each value 1 found in the loop, the area of the beans referring to this value will have its values updated to 2, so, when continuing with the loop through the matrix, you will no longer find values 1 for the same beans, but values 2 , ignoring them.

Solution

Photo considered in solution

A 50x50 photo containing 3 beans was considered.

$photo = [
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
];

Defined the variable referring to the amount of beans:

$beans = 0;

We traced the entire array, looking for values 1:

// Percorre as linhas da foto
for ($i = 0; $i < count($photo); $i++)
{

  // Percorre as colunas da foto
  for ($j = 0; $j < count($photo[$i]); $j++)
  {

    // Encontrou um feijão
    if ($photo[$i][$j] == 1)
    {
      // Incrementa a quantidade de feijões
      $beans++;

      // Atualiza a área do feijão por 2
      expandBeanArea($photo, $i, $j);
    }

  }
}

We will define the function expandBeanArea so that it is recursive and can reach the entire area in the array referring to a bean, replacing values 1 through 2. The following function:

function expandBeanArea (&$photo, $i, $j)
{
  if ($i < 0 || $i >= count($photo)) return;
  if ($j < 0 || $j >= count($photo[0])) return;

  if ($photo[$i][$j] == 1)
    {
        $photo[$i][$j] = 2;

        expandBeanArea($photo, $i-1, $j-1);
        expandBeanArea($photo, $i-1, $j);
        expandBeanArea($photo, $i-1, $j+1);
        expandBeanArea($photo, $i, $j-1);
        expandBeanArea($photo, $i, $j+1);
        expandBeanArea($photo, $i+1, $j-1);
        expandBeanArea($photo, $i+1, $j);
        expandBeanArea($photo, $i+1, $j+1);
    }
}

If we define an output:

echo "Encontrados {$beans} feijões na foto.";

We'll get the message: Encontrados 3 feijões na foto.

See working at PhpFiddle .

  

Note 1 : For this workaround, it is important that the $photo parameter of the expandBeanArea function is passed by reference. This is due to the fact that the changes made to this variable should influence the next iterations of the executed loop.

  It is also essential that the loop that runs through the array is done with the for statement, since the foreach statement stores some of the initial value of the array and it uses it in all iterations and even if its value changes (including passing by reference, see Note 1 ), all iterations are based on the initial value, generating a wrong result.

  

Note 3 : Following this logic, it will be considered as a bean each value or region of 1 that is surrounded with values 0. This practice is not advisable for the system in production since any present noise could generate false results. One way around this would be, in the process of substituting values by 2, also count the amount of 1's that are being substituted for the same bean and thus only consider that it is, in fact, a bean, when the amount pass a predefined threshold value.

Eliminating Noises

As quoted in Note 3 , the program is very sensitive to any noise present in the photo. So, to implement the solution suggested in it, to count the amount of 1 in each bean, in order to define a minimum area in the photo, we can do:

function expandBeanArea (&$photo, $i, $j)
{
  if ($i < 0 || $i >= count($photo)) return;
  if ($j < 0 || $j >= count($photo[0])) return;

  $area = 0;

  if ($photo[$i][$j] == 1)
    {
        $photo[$i][$j] = 2;

        $area++;

        $area += expandBeanArea($photo, $i-1, $j-1);
        $area += expandBeanArea($photo, $i-1, $j);
        $area += expandBeanArea($photo, $i-1, $j+1);
        $area += expandBeanArea($photo, $i, $j-1);
        $area += expandBeanArea($photo, $i, $j+1);
        $area += expandBeanArea($photo, $i+1, $j-1);
        $area += expandBeanArea($photo, $i+1, $j);
        $area += expandBeanArea($photo, $i+1, $j+1);
    }

  return $area;
}

Notice the insertion of the variable $area and is responsible for storing the amount of values 1 of each bean. Now, while traversing the array, we make the condition:

// Percorre as linhas da foto
for ($i = 0; $i < count($photo); $i++)
{

  // Percorre as colunas da foto
  for ($j = 0; $j < count($photo[$i]); $j++)
  {

    // Encontrou um feijão
    if ($photo[$i][$j] == 1)
    {
      // Verifica a área do feijão na foto
      $area = expandBeanArea($photo, $i, $j);

      // Verifica se a área é maior que o limiar pré-definido
      if ($area >= 20)
      {
        // Incrementa a quantidade de feijões
        $beans++;
      }
    }

  }
}

In this way, if at least 20 values 1 are not found in the same area of the photo, it is considered noise and not counted in the count of beans.

  

Note 4 : The value 20 is hypothetical in this example and should be set as needed for your project. Estimate the average area occupied by the beans to get this value.

See the expansion process, replacing the 1 by 2 values in the animation below. Note that the bean count occurs only when the entire area is expanded, this will therefore be considered the number of 1 found, counting as beans only the area is equal to or greater than 20. I took advantage of and included in the example some small noises , characterized by areas smaller than 1 scattered around the photo.

    
11.02.2017 / 03:13
11

If you can install an extension you can do php-opencv .

First, with the image with name feijao.jpg saved, we can read it in shades of gray.

$image  = imread('feijao.jpg', IMREAD_GRAYSCALE);

As the beans are black, it is very easy to isolate them from the rest of the image, simply apply a threshold with a very small value. In the example, I used 80, so that every pixel smaller than 80 would turn foreground and greater than 80 would turn background ( THRESH_BINARY_INV turns black into white and vice versa).

$binary = null;
threshold($image, $binary, 80, 255, THRESH_BINARY_INV);

We have as a result of this processing the following image:

Nowwe'llfindallbeansusingthefindContoursfunctionofopencv.InthelibrarythefunctioniscalledfindContoursWithoutHierarchybecauseitdoesnotreturnthethirdparameterthattheoriginalfunctionreturns,thehierarchiesbetweenthecontours.

$contours=null;findContoursWithoutHierarchy($binary,$contours,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);

Withthiswehavealistofcontours.Buteachoutlinecanhavedozensofpoints,inwhichcase,tomakeiteasiertowork,let'scalculatetherectanglesthatencompasseachsetofcontoursthatwefind.ForthisweusethefunctionboundingRect.

$boxes=array_map(function($contour){returnboundingRect($contour);},$contours);

Drawingtherectanglesfound,wegetthefollowingresult.

Butifwecountthenumberofboxeswewillseethattheresultis9(not8asweexpected).Lookingattheboxes,thereis1extremelysmall(withheightandwidthof1px).Wewillthenfiltertheresultswithverysmallpixelsbykickingaminimumvalue.

$boxes=array_filter($boxes,function($box){return$box->width>20&&$box->height>20;});

Hereweremoveeveryonewhoislessthan20pxtallorwide.Theheightandtheaveragewidthofthecorrectrectanglesis120px,soit'sareasonablebet.

Nowjustprinttheresult:

echo'Encontrados'.count($boxes).'feijõesnafoto.';

Andwe'llgetthecorrectresult:

  

Found8beansinthephoto.

PS:I'veomittedsomepartsofthecodetofocusonjusttheessentials,Iputthefullcodeonthis gist

    
13.07.2018 / 04:25