在C#中调整图像大小并将其发送到OpenCV会导致图像失真

时间:2017-08-18 09:39:12

标签: c# c++ opencv emgucv

这是与this one相关的后续问题。基本上,我有一个使用OpenCV进行图像处理的DLL。有两种方法,一种接受image-Path,另一种接受cv::Mat。使用image-path的人工作正常。接受image的人是有问题的。

这是接受文件名(DLL)的方法:

CDLL2_API void Classify(const char * img_path, char* out_result, int* length_of_out_result, int N)
{
    auto classifier = reinterpret_cast<Classifier*>(GetHandle());

    cv::Mat img = cv::imread(img_path);
    cv::imshow("img recieved from c#", img);
    std::vector<PredictionResults> result = classifier->Classify(std::string(img_path), N);
    std::string str_info = "";
    //...
    *length_of_out_result = ss.str().length();
}

这是接受图像(DLL)的方法:

CDLL2_API void Classify_Image(unsigned char* img_pointer, unsigned int height, unsigned int width, 
                              int step, char* out_result, int* length_of_out_result, int top_n_results)
    {
        auto classifier = reinterpret_cast<Classifier*>(GetHandle());
        cv::Mat img = cv::Mat(height, width, CV_8UC3, (void*)img_pointer, step);
        std::vector<Prediction> result = classifier->Classify(img, top_n_results);

        //...
        *length_of_out_result = ss.str().length();
    }

以下是C#应用程序中的代码:DllImport:

[DllImport(@"CDll2.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        static extern void Classify_Image(IntPtr img, uint height, uint width, int step, byte[] out_result, out int out_result_length, int top_n_results = 2);

将图像发送到DLL的方法:

private string Classify_UsingImage(Bitmap img, int top_n_results)
{
    byte[] res = new byte[200];
    int len;
    BitmapData bmpData;
    bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, img.PixelFormat);
    Classify_Image(bmpData.Scan0, (uint)bmpData.Height, (uint)bmpData.Width, bmpData.Stride, res, out len, top_n_results);
    //Remember to unlock!!!
    img.UnlockBits(bmpData); 
    string s = ASCIIEncoding.ASCII.GetString(res);
    return s;
}

现在,当我将图像发送到DLL时,这很有效。如果我使用imshow()来显示收到的图像,则图像显示得很好。

实际问题:

然而,当我调整大小相同的图像并使用上述相同的方法发送时,图像会失真。

我需要补充一点,如果我使用下面给定的C#方法调整图像大小,然后保存它,然后将文件名传递给DLL以使用Classify(std::string(img_path), N);打开它完美无缺。

以下是截图,显示了这种情况的一个例子:
从C`发送的图像没有调整大小:

首先调整相同图像的大小,然后将其发送到DLL:

此处首先调整图像大小(在C#中),保存到磁盘然后将其filepath发送到DLL:

这是负责调整大小的片段(C#):

/// <summary>
/// Resize the image to the specified width and height.
/// </summary>
/// <param name="image">The image to resize.</param>
/// <param name="width">The width to resize to.</param>
/// <param name="height">The height to resize to.</param>
/// <returns>The resized image.</returns>
public static Bitmap ResizeImage(Image image, int width, int height)
{
    var destRect = new Rectangle(0, 0, width, height);
    var destImage = new Bitmap(width, height);

    destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);

    using (var graphics = Graphics.FromImage(destImage))
    {
        graphics.CompositingMode = CompositingMode.SourceCopy;
        graphics.CompositingQuality = CompositingQuality.HighQuality;
        graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        graphics.SmoothingMode = SmoothingMode.HighQuality;
        graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

        using (var wrapMode = new ImageAttributes())
        {
            wrapMode.SetWrapMode(WrapMode.TileFlipXY);
            graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
        }
    }

    return destImage;
}

这是original image

这是Classify方法,它使用文件路径来读取图像:

std::vector<PredictionResults> Classifier::Classify(const std::string & img_path, int N)
{
    cv::Mat img = cv::imread(img_path);
    cv::Mat resizedImage;
    std::vector<PredictionResults> results;
    std::vector<float> output;

   // cv::imshow((std::string("img classify by path") + type2str(img.type())), img);
    if (IsResizedEnabled())
    {
        ResizeImage(img, resizedImage);
        output = Predict(resizedImage);
    }
    else
    {
        output = Predict(img);
        img.release();
    }

    N = std::min<int>(labels_.size(), N);
    std::vector<int> maxN = Argmax(output, N);
    for (int i = 0; i < N; ++i)
    {
        int idx = maxN[i];
        PredictionResults r;
        r.label = labels_[idx];
        r.accuracy = output[idx];
        results.push_back(r);
    }

    return results;
}

这是上面方法中使用的ResizeImage

void Classifier::ResizeImage(const cv::Mat & source_image, cv::Mat& resizedImage)
{
    Size size(GetResizeHeight(), GetResizeHeight());
    cv::resize(source_image, resizedImage, size);//resize image

    CHECK(!resizedImage.empty()) << "Unable to decode image ";
}

问题2:
调整大小后的失真,我面临着在C#中调整大小和使用OpenCV本身调整大小之间的差异。
我已经创建了另一种使用EmguCV的方法(也在下面给出)并传递了所需的信息,并且没有遇到任何这样的扭曲,当我们在C#中调整图像并将其发送到DLL时会发生这种扭曲。 然而,这种差异让我想了解造成这些问题的原因 以下是使用EmguCV.Mat的方法,无论调整大小如何都可以使用的代码:

private string Classify_UsingMat(string imgpath, int top_n_results)
{
    byte[] res = new byte[200];
    int len;

    Emgu.CV.Mat img = new Emgu.CV.Mat(imgpath, ImreadModes.Color);

    if (chkResizeImageCShap.Checked)
    {
        CvInvoke.Resize(img, img, new Size(256, 256));
    }

    Classify_Image(img.DataPointer, (uint)img.Height, (uint)img.Width, img.Step, res, out len, top_n_results);

    string s = ASCIIEncoding.ASCII.GetString(res);
    return s;
}

我为什么关心?
因为,当我使用OpenCV调整大小(我使用EMguCV的CvInvoke.resize()cv::resize()时)时,我得到的精度与我在C#中调整图像大小时的精度不同,将其保存到磁盘并发送图像路径到openCV。
所以当我在C#中处理图像时,我需要修复发生的失真,或者我需要理解为什么OpenCV中的大小调整与C#调整大小有不同的结果。

总结到目前为止所提出的问题和要点:

  1. 所有情况都完好无损,如果我们在C#应用程序中调整图像大小, 正如我们之前所做的那样正常传递信息,图像将是 扭曲(上面给出的例子)
  2. 如果我们调整图像大小,请将其保存到磁盘,并提供其文件名 要在OpenCV上创建一个新的cv::Mat,它可以完美地运行而不会出现任何问题。
  3. 如果我使用EmugCV而不是使用Bitmap,请使用 Emug.CV.Mat并使用mat对象发送所需的参数 C#,没有失真。

    然而,我从C#调整大小的图像(参见#2)获得的精度与使用OpenCV从调整大小的图像获得的精度不同。 如果我在手动使用之前调整图像大小,这没有任何区别 来自C#的CvInvoke.Resize(),并将生成的图像发送到DLL, 或发送原始图像(使用EmguCV)并使用cv::resize()在C ++代码中调整大小。这是阻止我使用EmguCV或传递图像原始图像并使用OpenCV在DLL内调整大小的原因。

  4. 以下是具有不同结果的图像,显​​示了问题:

    --------------------No Resizing------------------------------
    1.Using Bitmap-No Resize, =>safe, acc=580295
    2.Using Emgu.Mat-No Resize =>safe, acc=0.580262
    3.Using FilePath-No Resize, =>safe, acc=0.580262
    --------------------Resize in C#------------------------------
    4.Using Bitmap-CSharp Resize, =>unsafe, acc=0.770425
    5.Using Emgu.Mat-EmguResize, =>unsafe, acc=758335
    6.**Using FilePath-CSharp Resize, =>unsafe, acc=0.977649**
    --------------------Resize in DLL------------------------------
    7.Using Bitmap-DLL Resize, =>unsafe, acc=0.757484
    8.Using Emgu.DLL Resize, =>unsafe, acc=0.758335
    9.Using FilePath-DLL Resize, =>unsafe, acc=0.758335
    

    我需要获得我在#6获得的准确度。正如你可以看到EmguCV调整大小以及DLL中使用的OpenCV调整大小函数,行为相似并且不能按预期工作(例如#2)!,应用于图像的C#调整大小方法是有问题的,而如果它是调整大小,保存并传递文件名,结果会很好。

    您可以在此处看到描绘不同场景的屏幕截图:http://imgur.com/a/xbgIQ

1 个答案:

答案 0 :(得分:1)

我所做的就是使用imdecode as EdChum suggested。 这就是DLL和C#中的函数现在的样子:

#ifdef CDLL2_EXPORTS
#define CDLL2_API __declspec(dllexport)
#else
#define CDLL2_API __declspec(dllimport)
#endif

#include "classification.h" 
extern "C"
{
    CDLL2_API void Classify_Image(unsigned char* img_pointer, long data_len, char* out_result, int* length_of_out_result, int top_n_results = 2);
//...
}

实际方法:

CDLL2_API void Classify_Image(unsigned char* img_pointer, long data_len,
                              char* out_result, int* length_of_out_result, int top_n_results)
{
    auto classifier = reinterpret_cast<Classifier*>(GetHandle());
    vector<unsigned char> inputImageBytes(img_pointer, img_pointer + data_len);
    cv::Mat img = imdecode(inputImageBytes, CV_LOAD_IMAGE_COLOR);

    cv::imshow("img just recieved from c#", img);

    std::vector<Prediction> result = classifier->Classify(img, top_n_results);
    //...
    *length_of_out_result = ss.str().length();
}

这是C#DllImport:

[DllImport(@"CDll2.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
static extern void Classify_Image(byte[] img, long data_len, byte[] out_result, out int out_result_length, int top_n_results = 2);

这是将图像发送回DLL的实际方法:

private string Classify_UsingImage(Bitmap image, int top_n_results)
{
    byte[] result = new byte[200];
    int len;
    Bitmap img;

    if (chkResizeImageCShap.Checked)
        img = ResizeImage(image, int.Parse(txtWidth.Text), (int.Parse(txtHeight.Text)));
    else
        img = image;

    //this is for situations, where the image is not read from disk, and is stored in the memort(e.g. image comes from a camera or snapshot)
    ImageFormat fmt = new ImageFormat(image.RawFormat.Guid);
    var imageCodecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(codec => codec.FormatID == image.RawFormat.Guid);
    if (imageCodecInfo == null)
    {
        fmt = ImageFormat.Jpeg;
    }

    using (MemoryStream ms = new MemoryStream())
    {
        img.Save(ms, fmt);
        byte[] image_byte_array = ms.ToArray();
        Classify_Image(image_byte_array, ms.Length, result, out len, top_n_results);
    }

    return ASCIIEncoding.ASCII.GetString(result);
}

通过在调整C#图像大小后执行此操作,我们根本不会遇到任何扭曲。

但是,我无法弄清楚为什么OpenCV部分的调整不会按预期工作!

相关问题