How can I improve my algorithm for multi-region template matching using OpenCV in c#

Viewed 21

Source(compressed from original so that I could post it) enter image description here

Template(I require the template to support alpha masking)

enter image description here

The source image with generated regions and the match location of the template enter image description here

I'll start with the results.

enter image description here

When I perform a standard template match on the source/template pair it takes 475 ms. From there I tried multi-region template matching in parallel using Parallel.Foreach. The best result I got was with a dimension of 4x4 which brought the time down to 184 ms. I was happy with that until I observed the individual region processing times. When not performed in parallel they continually drop albeit on a curve. Even if I take the case of 4x4, which took 184 ms on average, and look at the individual region times, which are 56 ms on average, shouldn't the 4x4 multi region have taken less time? Assuming that all the threads executed concurrently, 184 ms seems high(I know what you're thinking :-) The individual region times DO spike when performed in parallel, but in serial they are usually within 5% of eachother).

Here is the code I wrote for the test. I apologize for the length:

public static class TemplateMatchTest
{
    
    public class RegionJob
    {
        public Point ImageLocation;
        public Image<Bgr, byte> SourceRegion;
        public Image<Bgra, byte> Template;
        public List<Rectangle> Results;
        public double averageMatchTime;
    }

    public static void PerformanceTest()
    {

        Image<Bgra, Byte> template = Emgu.CV.CvInvoke.Imread("Template.png", Emgu.CV.CvEnum.ImreadModes.Unchanged).ToImage<Bgra, byte>();
        Image<Bgr, Byte> source = Emgu.CV.CvInvoke.Imread("Source.png", Emgu.CV.CvEnum.ImreadModes.Unchanged).ToImage<Bgr, byte>();

        int sampleSize = 30;
        List<double> parallelAverages = new List<double>();
        double cumulative = 0;
        
        // -- Calculate the average time to find the template using parallel processing over image sections

        for (int j = 1; j <= 10; j++)
        {
            cumulative = 0;
            for (int i = 0; i < sampleSize; i++)
            {
                DateTime matchStart = DateTime.Now;
                List<Rectangle> results = ParallelTemplateMatchBySection(source, template, 4000, j, 1.2f);
                double elapsed = DateTime.Now.Subtract(matchStart).TotalMilliseconds;
                cumulative += elapsed;
            }
            double parallelMatchAverage = cumulative / sampleSize;
            parallelAverages.Add(parallelMatchAverage);
        }
        
        // -- Calculate the average time to find the template using standard template matching

        cumulative = 0;
        for(int i = 0; i < sampleSize; i++)
        {
            DateTime matchStart = DateTime.Now;
            List<Rectangle> results = FindImageInImage(source, template, 4000);
            double elapsed = DateTime.Now.Subtract(matchStart).TotalMilliseconds;
            cumulative += elapsed;
        }
        double serialAverage = cumulative / sampleSize;

        // -- Calculate the average time per region when calculated in serial

        List<double> serialRegionAverages = new List<double>();
        for (int j = 1; j <= 10; j++)
        {
            cumulative = 0;
            List<RegionJob> serialRegions = CalculateRegionProcessingTime(source, template, 4000, j, 1.2f, sampleSize);
            foreach(RegionJob serialRegion in serialRegions)
            {
                cumulative += serialRegion.averageMatchTime;
            }
            double serialRegionAverage = cumulative / serialRegions.Count;
            serialRegionAverages.Add(serialRegionAverage);
        }

        Console.WriteLine("===================================================================================================================");
        Console.WriteLine("Average times to perform standard template matching over " + sampleSize.ToString() + " samples:");
        Console.WriteLine(serialAverage.ToString("F2"));
        Console.WriteLine("===================================================================================================================");

        Console.WriteLine("Average times to perform template matching by region over " + sampleSize.ToString() + " samples:");
        for(int i = 0; i < parallelAverages.Count; i++)
        {
            double curAverage = parallelAverages[i];
            Console.WriteLine("Dimension : " + (i + 1).ToString() + "X" + (i + 1).ToString() + " | Average: " + curAverage.ToString("F2"));
        }
        Console.WriteLine("===================================================================================================================");
        Console.WriteLine("Average individual region times calculated in serial over " + sampleSize.ToString() + " samples:");
        for (int i = 0; i < serialRegionAverages.Count; i++)
        {
            double curRegionAverage = serialRegionAverages[i];
            Console.WriteLine("Dimension : " + (i + 1).ToString() + "X" + (i + 1).ToString() + " | Region Average: " + curRegionAverage.ToString("F2"));
        }
        Console.WriteLine("===================================================================================================================");
    }

    public static List<RegionJob> CalculateRegionProcessingTime(Image<Bgr, byte> Source, Image<Bgra, byte> Template, int Threshold, int Dimension, float OverlapCoef, int SampleSize)
    {

        List<RegionJob> resultRegions = GenerateRegions(Source, Template, Dimension, OverlapCoef);

        for(int i = 0; i < resultRegions.Count; i++)
        {
            RegionJob curRegion = resultRegions[i];
            double cumulative = 0;
            for(int j = 0; j < SampleSize; j++)
            {
                DateTime matchStart = DateTime.Now;
                curRegion.Results = FindImageInImage(curRegion.SourceRegion, curRegion.Template, Threshold);
                double elapsed = DateTime.Now.Subtract(matchStart).TotalMilliseconds;
                cumulative += elapsed;
            }
            curRegion.averageMatchTime = cumulative / SampleSize;
        }

        return resultRegions;

    }

    public static List<Rectangle> ParallelTemplateMatchBySection(Image<Bgr, byte> Source, Image<Bgra, byte> Template, int Threshold, int Dimension, float OverlapCoef)
    {

        List<Rectangle> results = new List<Rectangle>();

        List<RegionJob> regions = GenerateRegions(Source, Template, Dimension, OverlapCoef);

        Parallel.ForEach(regions, curRegion =>
        {
            curRegion.Results = FindImageInImage(curRegion.SourceRegion, curRegion.Template, Threshold);                
        });

        foreach(RegionJob curRegion in regions)
        {
            foreach (Rectangle curRectangle in curRegion.Results)
            {
                results.Add(new Rectangle(curRectangle.X + curRegion.ImageLocation.X,
                                          curRectangle.Y + curRegion.ImageLocation.Y,
                                          curRegion.Template.Width,
                                          curRegion.Template.Height));
            }
        }

        return results;

    }

    public static List<Rectangle> FindImageInImage(Image<Bgr, Byte> Source, Image<Bgra, Byte> Template, int Threshold)
    {

        List<Rectangle> foundLocations = new List<Rectangle>();

        // -- Generate the mask based on the template's alpha channel

        Image<Bgr, Byte> mask = new Image<Bgr, Byte>(Template.Width, Template.Height);
        for (int y = 0; y < Template.Height; y++)
        {
            for (int x = 0; x < Template.Width; x++)
            {
                if (Template.Data[y, x, 3] <= 128)
                {
                    mask.Data[y, x, 0] = 0;
                    mask.Data[y, x, 1] = 0;
                    mask.Data[y, x, 2] = 0;
                }
                else
                {
                    mask.Data[y, x, 0] = 255;
                    mask.Data[y, x, 1] = 255;
                    mask.Data[y, x, 2] = 255;
                }
            }
        }                   

        // -- Perform the template matching

        Image<Gray, float> result = new Image<Gray, float>(Source.Width, Source.Height);
        Image<Bgr, Byte> templateConverted = Template.Convert<Bgr, byte>();
        Emgu.CV.CvInvoke.MatchTemplate(Source, templateConverted, result, TemplateMatchingType.Sqdiff, mask);
        float[,] data = (float[,])result.Mat.GetData();

        // -- Locate matches based on threshold

        int width = Source.Width - Template.Width + 1;
        int height = Source.Height - Template.Height + 1;
        int templateWidth = Template.Width;
        int templateHeight = Template.Height;
        for (int x = 0; x < width; x++)
        {
            for (int y = 0; y < height; y++)
            {
                float curValue = data[y, x];
                if (curValue <= Threshold)
                {
                    Rectangle foundPoint = new Rectangle(x, y, templateWidth, templateHeight);
                    foundLocations.Add(foundPoint);
                }
            }
        }

        // -- Filter overlapping results

        int curCheckIndex = 0;
        while (curCheckIndex < foundLocations.Count - 1)
        {
            Rectangle curCheckRect = foundLocations[curCheckIndex];
            for (var i = foundLocations.Count - 1; i > curCheckIndex; i--)
            {
                Rectangle curEndRect = foundLocations[i];
                if (curCheckRect.IntersectsWith(curEndRect))
                {
                    foundLocations.Remove(curEndRect);
                }
            }
            curCheckIndex++;
        }

        return foundLocations;

    }

    public static List<RegionJob> GenerateRegions(Image<Bgr, Byte> Source, Image<Bgra, Byte> Template, int Dimension, float OverlapCoef)
    {

        List<RegionJob> regions = new List<RegionJob>();

        float regionWidth = Source.Width / Dimension;
        float regionHeight = Source.Height / Dimension;
        float overlapX = Template.Width * OverlapCoef;
        float overlapY = Template.Height * OverlapCoef;

        for(int x = 0; x < Dimension; x++)
        {
            for(int y = 0; y < Dimension; y++)
            {
                float curX, curY, curWidth, curHeight;

                if(x == 0)
                {
                    curX = 0;
                    curWidth = regionWidth + overlapX / 2;
                }
                else if(x == Dimension - 1)
                {
                    curX = (x * regionWidth) - overlapX / 2;
                    curWidth = Source.Width - curX;
                }
                else
                {
                    curX = (x * regionWidth) - overlapX / 2;
                    curWidth = regionWidth + overlapX;
                }

                if (y == 0)
                {
                    curY = 0;
                    curHeight = regionHeight + overlapY / 2;
                }
                else if(y == Dimension - 1)
                {
                    curY = (y * regionHeight) - overlapY / 2;
                    curHeight = Source.Height - curY;
                }
                else
                {
                    curY = (y * regionHeight) - overlapY / 2;
                    curHeight = regionHeight + overlapY;
                }

                Rectangle curRect = new Rectangle((int)curX, (int)curY, (int)curWidth, (int)curHeight);
                RegionJob newRegion = new RegionJob();                    
                newRegion.Template = Template.Clone();
                Source.ROI = curRect;
                newRegion.SourceRegion = Source.Copy();
                newRegion.ImageLocation = new Point((int)curX, (int)curY);
                regions.Add(newRegion);
                Source.ROI = Rectangle.Empty;
            }
        }

        return regions;

    }

}

My goal is to reduce the generalized multi-region template matching function to perform near real-time on HD graphics. I need the matching to consider color so I can't desaturate it. I also require alpha masking in the template which rules out Cuda based template matching(As far as I know..)

I'm hoping the community has some tips on some approaches I can use to improve performance. Am I doing multithreading badly? I tried using individual threads that I created myself but the results were very similar to those above. Is the Open.CV library sharing resources for Emgu.CV.CVInvoke.MatchTemplate that is slowing down parallel processing? I read that the library should scale well in parallel.

My system specs:

enter image description here

Thanks for checking out this post. Sorry again for the length!

0 Answers
Related