1

I'm loading C++ library from my C# code dynamically. I want to find small image inside large one, converting large image to byte[] and small image read from physical path.
When I call imdecode then large_img always returns 0 cols and rows.

C#

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate ImageParams GetImageParams(IntPtr dataPtr, int size, string path);

// ...

byte[] largeImgByteArr = this.BitmapToByteArray(bmp);
IntPtr dataPtr = Marshal.AllocHGlobal(largeImgByteArr.Length);
Marshal.Copy(dataPtr, largeImgByteArr, 0, largeImgByteArr.Length);

C++

ImageParams GetImageParams(BYTE* largeImgBuf, int bufLength, const char* smallImgPath)
{
    Mat large_img_data(bufLength, 1, CV_32FC1, largeImgBuf);

    Mat large_img = imdecode(large_img_data, IMREAD_COLOR);
    Mat small_img = imread(smallImgPath, IMREAD_COLOR);

    int result_cols = large_img.cols - small_img.cols + 1;
    int result_rows = large_img.rows - small_img.rows + 1;

    Mat result;
    result.create(result_cols, result_rows, CV_32FC1);

    matchTemplate(large_img, small_img, result, CV_TM_SQDIFF_NORMED);
    normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
}

What I'm doing wrong here?
Note: I have checked that image path is correct and byte array not empty.

Edit 1

I changed my code a bit by providing the large image width and height, also got rid of imdecode and changed something like in this post.

ImageParams GetImageParams(BYTE* largeImgBuf, int height, int width, int bufLength, const char* smallImgPath)
{  
    // Mat large_img = imdecode(large_img_data, IMREAD_COLOR);
    Mat large_img = Mat(height, width, CV_8UC3, largeImgBuf);
    Mat small_img = imread(templPath, 1);

    /// ...
}

Now it returns rows and columns but when call matchTemplate method it throws an exception:

enter image description here

Community
  • 1
  • 1
Mr. Blond
  • 1,113
  • 2
  • 18
  • 41
  • Why do you need `imdecode` here? `largeImgBuf` already seems to contain the correct data. Also `CV_32FC1` doesn't seem right. Pleas try something like `Mat large_img(bufLength, 1, CV_8UC3, largeImgBuf);` – Miki Aug 21 '16 at 15:05
  • @Miki Thanks for reply, although the same happens with `CV_8UC3`. I will soon update my question with more info. – Mr. Blond Aug 21 '16 at 15:27

2 Answers2

2

Remember that Bitmap structure in C# uses data padding (stride value as multiplicity of 4) whrereas OpenCV may not. Try creating Mat object directly from byte array but adjust step (stride) value as it just assigns data without any ownership or reallocation.

EDIT

Here's an example how create OpenCV Mat object from Bitmap. Data from bitmap is not copied, but only assigned. PixelFormat and OpenCV mat type must have corresponding single element size in bytes.

cv::Mat ImageBridge::cvimage(System::Drawing::Bitmap^ bitmap){
    if(bitmap != nullptr){
        switch(bitmap->PixelFormat){
        case System::Drawing::Imaging::PixelFormat::Format24bppRgb:
            return bmp2mat(bitmap, CV_8UC3);
        case System::Drawing::Imaging::PixelFormat::Format8bppIndexed:
            return bmp2mat(bitmap, CV_8U);
        default: return cv::Mat();
        }
    }
    else
        return cv::Mat();
}


cv::Mat ImageBridge::bmp2mat(System::Drawing::Bitmap^ bitmap, int image_type){
    auto bitmap_data = bitmap->LockBits(
        System::Drawing::Rectangle(0, 0, bitmap->Width, bitmap->Height),
        System::Drawing::Imaging::ImageLockMode::ReadWrite,
        bitmap->PixelFormat);

    char* bmpptr = (char*)bitmap_data->Scan0.ToPointer();

    cv::Mat image(
        cv::Size(bitmap->Width, bitmap->Height),
        image_type,
        bmpptr,
        bitmap_data->Stride);

    bitmap->UnlockBits(bitmap_data);
    return image;
}

EDIT 2

Conversion in reverse - this time data from Mat image is copied as Bitmap allocates it's own memory.

System::Drawing::Bitmap^ ImageBridge::bitmap(cv::Mat& image){
    if(!image.empty() && image.type() == CV_8UC3)
        return mat2bmp(image, System::Drawing::Imaging::PixelFormat::Format24bppRgb);
    else if(!image.empty() && image.type() == CV_8U)
        return mat2bmp(image, System::Drawing::Imaging::PixelFormat::Format8bppIndexed);
    else
        return nullptr;
}

System::Drawing::Bitmap^ ImageBridge::mat2bmp(cv::Mat& image, System::Drawing::Imaging::PixelFormat pixel_format){
    if(image.empty())
        return nullptr;

    System::Drawing::Bitmap^ bitmap = gcnew System::Drawing::Bitmap(image.cols, image.rows, pixel_format);

    auto bitmap_data = bitmap->LockBits(
        System::Drawing::Rectangle(0, 0, bitmap->Width, bitmap->Height),
        System::Drawing::Imaging::ImageLockMode::ReadWrite,
        pixel_format);

    char* bmpptr = (char*)bitmap_data->Scan0.ToPointer();
    int line_length = (int)image.step;
    int bmp_stride = bitmap_data->Stride;

    assert(!image.isSubmatrix());
    assert(bmp_stride >= 0);
    for(int l = 0; l < image.rows; l++){
        char* cvptr = (char*)image.ptr(l);
        int bmp_line_index = l * bmp_stride;

        for(int i = 0; i < line_length; ++i)
            bmpptr[bmp_line_index + i] = cvptr[i];
    }

    bitmap->UnlockBits(bitmap_data);

    return bitmap;
}

Or if you have Mat image with step as multiplicity of 4 you can use non-copy version.

System::Drawing::Bitmap^ ImageBridge::bitmap2(cv::Mat& image){   
    System::Drawing::Bitmap^ bitmap;
    assert(!image.isSubmatrix());
    if(!image.empty() && image.type() == CV_8UC3){
        bitmap = gcnew System::Drawing::Bitmap(
            image.cols, image.rows,
            3 * image.cols,
            System::Drawing::Imaging::PixelFormat::Format24bppRgb,
            System::IntPtr(image.data));
    }
    else if(!image.empty() && image.type() == CV_8U){
        bitmap = gcnew System::Drawing::Bitmap(
            image.cols, image.rows,
            image.cols,
            System::Drawing::Imaging::PixelFormat::Format8bppIndexed,
            System::IntPtr(image.data));
    }
    return bitmap;
}
willy
  • 199
  • 3
  • I will check this as soon as I can. Maybe you could provide a sample code or some useful link how to do it? That would help me alot. – Mr. Blond Aug 25 '16 at 08:08
  • Thanks. Last thing, how do I convert `BYTE*` to `System::Drawing::Bitmap`? – Mr. Blond Aug 25 '16 at 20:02
  • Again I've edited my answer. For your case the BYTE* array is in fact image.data (whole data) or image.ptr(index) (just one line). – willy Aug 26 '16 at 07:16
  • It still throws the same exception. I changed my logic a bit so that I'm saving image on hard drive, do necessary processing and just delete it. Know that this is not as nice as doing all in the memory buffer, but it works. Thanks for your effort, this will definitely help someone else, cheers. – Mr. Blond Aug 26 '16 at 17:45
0

According to the documentation

The function reads an image from the specified buffer in the memory. If the buffer is too short or contains invalid data, the empty matrix/image is returned.

Try checking the error code after the imdecode part

#include <errno.h>

cout << errno;
mic4ael
  • 7,974
  • 3
  • 29
  • 42