Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does OpenCV take much longer to load an image than .NET?

Tags:

c++

c#

opencv

I was looking into using OpenCV in a performance critical application, so I decided to start with the basics and test image loading speed. To my surprise, image loading (something we do a lot of) takes ~1.5 times longer with OpenCV when compared to .NET.

Here is my code:

CvDll.cpp

#include "stdafx.h"
#include <opencv2\opencv.hpp>

#define CVDLL_API __declspec(dllexport)

extern "C"
{
    CVDLL_API void CvLoadImage(const char* imagePath);
    CVDLL_API void CvCreateMat(int width, int height, int stride, int channels, void* pBuffer);
}

CVDLL_API void CvLoadImage(const char* imagePath)
{
    cv::Mat image = cv::imread(imagePath, CV_LOAD_IMAGE_UNCHANGED);
}

CVDLL_API void CvCreateMat(int width, int height, int stride, int channels, void* pBuffer)
{
    int type = CV_MAKETYPE(CV_8U, channels);
    cv::Mat image(cv::Size(width, height), type, pBuffer, stride);
}

Program.cs

   static class Cv
    {
        [DllImport("CvDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "CvLoadImage")]
        public static extern void LoadImage(string imagePath);

        [DllImport("CvDll.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, EntryPoint = "CvCreateMat")]
        public static extern void CreateMat(int width, int height, int stride, int channels, IntPtr pBuffer);
    }

    static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: {0} (path to image)", Path.GetFileName(System.Reflection.Assembly.GetCallingAssembly().Location));
            Console.Write("Press any key to continue...");
            Console.ReadKey();
            return;
        }

        string imagePath = args[0];

        try
        {
            if (!File.Exists(imagePath)) throw new ApplicationException("Image file does not exist.");

            // Time .NET
            Console.Write(".NET Loading {0} Bitmaps: ", ITERATIONS);
            TimeSpan timeDotNet = TimeIt(
                () =>
                {
                    using (Bitmap img = new Bitmap(imagePath))
                    {
                        int width = img.Width;
                        int height = img.Height;
                        int channels = Image.GetPixelFormatSize(img.PixelFormat) / 8; // Assumes 1 byte per channel

                        BitmapData bd = img.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, img.PixelFormat);

                        // Create a Mat from the bitmap data to make the operation equivalent
                        Cv.CreateMat(width, height, Math.Abs(bd.Stride), channels, bd.Scan0);

                        img.UnlockBits(bd);
                    }
                }
                , ITERATIONS
            );
            Console.WriteLine("{0}", timeDotNet);

            // Time OpenCV
            Console.Write("OpenCV Loading {0} Mats: ", ITERATIONS);
            TimeSpan timeCv = TimeIt(
                () => Cv.LoadImage(imagePath)
                , ITERATIONS
            );
            Console.WriteLine("{0}", timeCv);

            // Show ratio
            Console.WriteLine("CV / .NET: {0:0.000}", timeCv.TotalMilliseconds / timeDotNet.TotalMilliseconds);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception caught: {0}{1}", ex.Message, Environment.NewLine);
        }

        // End
        Console.Write("Press any key to continue...");
        Console.ReadKey();
    }

    static TimeSpan TimeIt(Action action, int iterations)
    {
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < iterations; ++i)
        {
            action();
        }
        return sw.Elapsed;
    }
}

Here is a link to the flower image I've been using to test.

My results (CV time / .NET time):

  • 1 Channel PNG: 1.764
  • 3 Channel PNG: 1.290
  • 4 Channel PNG: 1.336

  • 1 Channel BMP: 1.384

  • 3 Channel BMP: 1.099
  • 4 Channel BMP: 1.809

  • 3 Channel JPG: 2.816 (Example image)

These tests were done compiled in Release mode without the debugger attached using the official OpenCV Windows libs.

My initial thought was the speed of memory allocation, but looking at the difference between different channel images seems to imply this is not the case.

Other things I've tried:

  • Moving loop into C++ function: No change
  • Turn on all the optimization options I could find in VS: No change
  • Second machine: Tried on an AMD Phenom II machine and the color images started giving around 1.25 and grayscale around 1.5. So while different, still in .NET's favor.

Other details:

  • Windows 7 x64
  • Visual Studio 2013
  • OpenCV 2.4.9
  • .NET 4.5

The results seem kind of counter intuitive, but it sure seems like in this particular scenario OpenCV is simply slower.

EDIT:

Thanks to @πάντα ῥεῖ for pointing out that the operations weren't equivalent, edited to create a Mat in both scenarios to isolate the loading method. I hope this makes it a valid test.

EDIT2:

Fixed issue pointed out by @B, revised the numbers when loading with CV_LOAD_IMAGE_UNCHANGED.

like image 921
Mills Avatar asked Oct 01 '22 11:10

Mills


1 Answers

Unless you specify otherwise, which you haven't, OpenCv returns a color image. Therefore for OpenCV you are paying for a color conversion which probably isn't occurring for .NET. For the one color image you need to specify CV_LOAD_IMAGE_GRAYSCALE, or set the flags to -1 to get whatever is in the file.

Looking at the source code, it looks like 3 channel images come out of the actual decoder (PNG and Jpeg, at least) in RGB channel order and these are swapped to the BGR order that OpenCV expects everywhere. If your .NET library is returning the images in RGB order, then you might need to do a conversion to BGR if you are going to pass the images to other OpenCV functions. ANd then you would probably lose the speed advantage.

To be fair, you need to add a RGB2BGR conversion to your .NET load code - see Converting a BGR bitmap to RGB

Also, for the 4 channel PNG, OpenCV will discard the alpha channel and return a 3 channel image unless you specify flags = -1.

like image 136
Bull Avatar answered Oct 04 '22 21:10

Bull