Search code examples
phpocrtesseractimagick

PHP: Count object groups in image


I need to automate the counting of dots in this image. We can assume that the dots are always red (or black if it helps) and always this size on a white background. The positions and counts change often

At first I thought I could use Tesseract OCR and may count periods or bullets but the results are empty no matter how I manipulate the image or whitelist characters.

Another thing I tried is using Imagick, there's a connected components function but in my tests I used the same code and got no results...

I looked into OpenCV but the only port for PHP uses documentation written primarily in Chinese and doesn't support the full library so that throws Hough Transform out the window.

The code below: Lastly I've ventured into reading the image with PHP and looping through the pixels and finding any outside of a certain threshold but I'm stuck at how I actually group the individual pixels to an object. -- One note is that I couldn't get this to work with the full sized images (even though they are already scaled down) despite having no memory limit and execution time limit.

I've spent a day and a half on this and it seems so simple with the shapes being consistant but no...

Here's the image: Image to count

// creating the initial image
  $starting_img = imagecreatefrompng("converted.png");
  //Dimentions
  $imgDims = getimagesize($source_image);
  $scanWidth = $imgDims[0];
  $scanHeight = $imgDims[1];
  //New image
  $final = imagecreatetruecolor($scanWidth,$scanHeight);
  $white = imagecolorallocate($final, 255, 255, 255);
  imagefill($final,0,0,$white);

  $imageData = array();

  for ($y=1; $y < $scanHeight - 1; $y++) {
    // Get First Row, the scan each row col
    $currentRow = array();
    for ($x=1; $x < $scanWidth - 1; $x++) {
      // Get first col in row 1 and check its data
      $rgb = imagecolorat($starting_img,$x,$y);
      $color = array(
        'r' => ($rgb >> 16) & 255,
        'g' => ($rgb >> 8) & 255,
        'b' => $rgb & 255
      );
      $currentCol = array(
        'x' => $x,
        'y' => $y,
        'r' => $color['r'],
        'g' => $color['g'],
        'b' => $color['b'],
      );
      //Is the pixel red?
      // if ($color['r'] > 200 && $color['g'] < 100 && $color['b'] < 100) {
        array_push($currentRow, $currentCol);
      // }
    }
    //Does this row have any red pixels?
    // if (count($currentRow) > 1) {
      array_push($imageData, $currentRow);
    // }
  }

  foreach ($imageData as $currentRow => $row) {
    foreach ($row as $currentCol => $col) {
      $newColoredPixel  = imagecolorallocate($final,$col['r'],$col['g'],$col['b']);
      // adding the new pixel to the new image
      imagesetpixel($final,$col['x'],$col['y'],$newColoredPixel);
    }
  }

  file_put_contents('imageData.txt', print_r($imageData, true));

  imagepng($final,"final.png");

  imagedestroy($final);
  imagedestroy($starting_img);

Solution

  • Nice question !

    If blobs are not overlapping - you can count them by formula :

    blob_count = total_blob_area / blob_unit_area

    in your case blob is a circle, so formula becomes :

    blob_count = total_blob_area / (π * r^2)

    total_blob_area is simply a reddish pixel count, but you must find blob unit diameter empirically.

    BTW, function imagecolorat() returns RGB value only if image is of truecolor type, however your given PNG have indexed color space, so to be able to extract real RGB values from it - you must pass output of imagecolorat() to imagecolorsforindex().

    The code for blob counting method described above is this :

    function countBlobs($imfile, $blob_diameter) {
    
        $blob_area = pi()*pow($blob_diameter/2, 2);
    
        $im = imagecreatefrompng($imfile);
        list($width, $height, $type, $attr) = getimagesize($imfile);
    
        $total_blob_area = 0;
        for ($x=0; $x < $width; $x++) {
            for ($y=0; $y < $height; $y++) {
                $rgb = imagecolorat($im, $x, $y);
                $colors = imagecolorsforindex($im, $rgb);
                $avg = ($colors['red']+$colors['green']+$colors['blue'])/3;
                if ($avg < 150) 
                {
                    $total_blob_area++;
                }
            }
        }
    
        $blobs = $total_blob_area / $blob_area;
    
        return round($blobs);
    }
    
    echo ('blobs : ' . countBlobs('countblobs.png', 16));
    

    Blob unit diameter you can find with distance measurement tools in GIMP. However, if your blobs can be of different shapes / sizes - then you will need some sort of averaging for a blob unit area.