How to detect black Bullets in Image?

2.7k views Asked by At

Given the following image, How can I detect black bullets (90 bullet) in this image using C#, EmguCV or AForge?

enter image description here

I tried to use GetPixel(x,y) method but it checks only pixel by pixel, it is very slow and I need to detect the bullets not pixels.

3

There are 3 answers

1
Benj On

I created a full solution for the problem (relying only on Bitmap.GetPixel(Int32,Int32)).

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;

namespace StackOverflow
{
    public static class Program
    {
        static void Main(string[] args)
        {
            const String PATH = @"C:\sim\sbro6.png";

            Stopwatch watch = new Stopwatch(); watch.Start();
            List<Bitmap> l_new, l_old;

            {

                Bitmap bmp = Image.FromFile(PATH) as Bitmap;

                // Initialization
                l_old = new List<Bitmap>();
                l_new = new List<Bitmap>(); l_new.Add(bmp);

                // Splitting
                while (l_new.Count > l_old.Count)
                {
                    l_old = l_new; l_new = new List<Bitmap>();

                    l_new.AddRange(SplitBitmapsVertically(SplitBitmapsHorizontally(l_old)));
                }

                // for (Int32 i = 0; i < l_new.Count; i++)
                // {
                //     l_new[i].Save(@"C:\sim\bitmap_" + i + ".bmp");
                // }

            }

            watch.Stop();

            Console.WriteLine("Picture analyzed in ".PadRight(59,'.') + " " + watch.Elapsed.TotalSeconds.ToString("#,##0.0000"));
            Console.WriteLine("Dots found ".PadRight(59, '.') + " " + l_new.Count);
            Console.WriteLine();
            Console.WriteLine("[ENTER] terminates ...");
            Console.ReadLine();

        }

        static List<Bitmap> SplitBitmapsVertically(List<Bitmap> l_old)
        {
            Int32 x_start = -1; Bitmap bmp; Boolean colContainsData = false;
            List<Bitmap> l = new List<Bitmap>();

            foreach(Bitmap b in l_old)
            {
                for (Int32 x = 0; x < b.Width; x++)
                {
                    colContainsData = false;

                    for (Int32 y = 0; y < b.Height; y++)
                    {
                        if (b.GetPixel(x, y).ToArgb() != Color.White.ToArgb())
                        {
                            colContainsData = true;
                        }
                    }

                    if (colContainsData) if (x_start < 0) { x_start = x; }
                    if (!colContainsData || (x == (b.Width - 1))) if (x_start >= 0)
                        {
                            bmp = new Bitmap(x - x_start, b.Height);

                            for (Int32 x_tmp = x_start; x_tmp < x; x_tmp++)
                                for (Int32 y_tmp = 0; y_tmp < b.Height; y_tmp++)
                                {
                                    bmp.SetPixel(x_tmp - x_start, y_tmp, b.GetPixel(x_tmp, y_tmp));
                                }

                            l.Add(bmp); x_start = -1;
                        }
                }
            }

            return l;
        }
        static List<Bitmap> SplitBitmapsHorizontally(List<Bitmap> l_old)
        {
            Int32 y_start = -1; Bitmap bmp; Boolean rowContainsData = false;
            List<Bitmap> l = new List<Bitmap>();

            foreach (Bitmap b in l_old)
            {
                for (Int32 y = 0; y < b.Height; y++)
                {
                    rowContainsData = false;

                    for (Int32 x = 0; x < b.Width; x++)
                    {
                        if (b.GetPixel(x, y).ToArgb() != Color.White.ToArgb())
                        {
                            rowContainsData = true;
                        }
                    }

                    if (rowContainsData) if (y_start < 0) { y_start = y; }
                    if (!rowContainsData || (y == (b.Height - 1))) if (y_start >= 0)
                        {
                            bmp = new Bitmap(b.Width, y - y_start);

                            for (Int32 x_tmp = 0; x_tmp < b.Width; x_tmp++)
                                for (Int32 y_tmp = y_start; y_tmp < y; y_tmp++)
                                {
                                    bmp.SetPixel(x_tmp, y_tmp - y_start, b.GetPixel(x_tmp, y_tmp));
                                }

                            l.Add(bmp); y_start = -1;
                        }
                }
            }

            return l;
        }
    }
}

It takes roughly half a second to process the image (see attached picture) Benchmark for Image-Processing

The idea is to iteratively split the provided image into rows and colums that do only contain a subset of dots until there is exactly one dot contained.

The dots may be arbitrarily spread over the image. Hope this helps

0
berkyl On

Algorithm/Idea 1 You can divide your image in squares as shown in this sample: enter image description here

With this logic you only have to check every 20th pixel. As soon as you know where the first dot is, you will know that every other dot have to be in the same horizontal line (in your provided sample).

A sample algorithm would look similar to this (please note that it need further improvement):

Bitmap myBitmap = new Bitmap ("input.png");

int skipX = 12;
int skipY = 12;

int detectedDots = 0;

for (int x = 0; x < myBitmap.Width; x += skipX) {
    for (int y = 0; y < myBitmap.Height; y += skipY) {
        Color pixelColor = myBitmap.GetPixel (x, y);

        if (pixelColor.R + pixelColor.G + pixelColor.B == 0) {
            myBitmap.SetPixel (x, y, Color.Red);
            detectedDots++;
        }
    }
}

myBitmap.Save ("output.png");

Console.WriteLine (detectedDots + " dots detected");

I added an output so you can check which dots got detected (all containing red pixels).

Further improvement would be to find the center of a dot. After that you should know the width (and height) and can start at first upper left dot with an offset of the dots width.

Algorithm 2 The second algorithm analyses each pixel and is a lot easier to implement. As soon as there's a black pixel it checks if there was a black pixel in the same vertical or horizontal line before and skips in this case until there is no black pixel in line.

Further improvement would be to store the height of the first dot and to make the condition in the middle of the snippet more beautiful.

Stopwatch watch = new Stopwatch(); watch.Start();

Bitmap myBitmap = new Bitmap ("input.png");

int dotsDetected = 0;


List<int> xFound = new List<int>();


for (int x = 0; x < myBitmap.Width; x++) {
    bool yFound = false;
    bool dotFound = false;
    for (int y = 0; y < myBitmap.Height; y++) {
        Color pixelColor = myBitmap.GetPixel (x, y);
        if (pixelColor.R + pixelColor.G + pixelColor.B == 0) {
            dotFound = true;

            if (yFound)
                continue;

            if (xFound.Contains (y) 
                || xFound.Contains (y + 1) 
                || xFound.Contains (y + 2) 
                || xFound.Contains (y + 3)
                || xFound.Contains (y + 4)
                || xFound.Contains (y - 1) 
                || xFound.Contains (y - 2) 
                || xFound.Contains (y - 3)
                || xFound.Contains (y - 4)) {
                yFound = true;
                continue;
            }

            xFound.Add (y);
            //myBitmap.SetPixel (x, y, Color.Red);

            dotsDetected++;
            yFound = true;
        } else
            yFound = false;
    }

    if(!dotFound) //no dot found in this line
        xFound.Clear();
}

//myBitmap.Save ("output.png");

watch.Stop();

Console.WriteLine("Picture analyzed in " + watch.Elapsed.TotalSeconds.ToString("#,##0.0000"));

Console.WriteLine (dotsDetected + " dots detected");

Performance

1
Paul-Jan On

Unless you are doing this to learn more about image processing, do not re-invent the wheel. Just use emgucv (or a similar library). The emgucv syntax is rather unfriendly (mostly because of its underlying Win32 OpenCV implementation), but basically it comes down to

Contour<Point> contour = img.FindContours(CV_CHAIN_APPROX_TC89_L1, RETR_TYPE.CV_RETR_LIST);

for (; contour != null; contour = contour.HNext)
{
    // You now have the contours. These have characteristics like a boundingRect, which is an easy way to approach the center of a circle.
}