在 C# 中调整图像大小并将其发送到 OpenCV 会导致图像失真
Resizing the image in C# and sending it to OpenCV results in distorted image
这是与 相关的后续问题。基本上,我有一个使用 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 ";
}
问题二:
除了调整大小后的失真,我面临着在 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# 调整大小有不同的结果。
所以总结一下到目前为止提出的问题和要点:
- 所有情况都完好无损,如果我们在 C# 应用程序中调整图像大小,
并像我们之前一样正常传递信息,图像将是
扭曲(上面给出的例子)
- 如果我们调整图像大小,将它保存到磁盘,并给出它的文件名
到 OpenCV 创建一个新的
cv::Mat
,它完美地工作,没有任何问题。
如果我使用 EmugCV 而不是使用 Bitmap
,请使用
Emug.CV.Mat
并使用 mat 对象从
C#,没有失真。
但是,我从 C# 调整大小的图像中获得的精度(参见 #2)与我使用 OpenCV 从调整大小的图像中获得的精度不同。
如果我在手动使用之前调整图像大小,这没有任何区别
CvInvoke.Resize()
来自 C#,并将生成的图像发送到 DLL,
或者发送原始图像(使用 EmguCV)并使用 cv::resize()
在 C++ 代码中调整它的大小。这就是阻止我使用 EmguCV 或传递图像原始图像并使用 OpenCV 在 DLL 中调整其大小的原因。
以下是具有不同结果的图像,显示了问题:
--------------------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 resize 以及 DLL 中使用的 OpenCV resize 函数,行为相似但未按预期工作(即像#2)!,应用于图像的 C# resize 方法是有问题的,而如果它是调整大小,保存并传递文件名,结果会很好。
您可以在此处查看描述不同场景的屏幕截图:http://imgur.com/a/xbgIQ
我做的是用imdecode
。
这是 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 部分的调整大小无法按预期工作!
这是与 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 ";
}
问题二:
除了调整大小后的失真,我面临着在 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# 调整大小有不同的结果。
所以总结一下到目前为止提出的问题和要点:
- 所有情况都完好无损,如果我们在 C# 应用程序中调整图像大小, 并像我们之前一样正常传递信息,图像将是 扭曲(上面给出的例子)
- 如果我们调整图像大小,将它保存到磁盘,并给出它的文件名
到 OpenCV 创建一个新的
cv::Mat
,它完美地工作,没有任何问题。 如果我使用 EmugCV 而不是使用
Bitmap
,请使用Emug.CV.Mat
并使用 mat 对象从 C#,没有失真。但是,我从 C# 调整大小的图像中获得的精度(参见 #2)与我使用 OpenCV 从调整大小的图像中获得的精度不同。 如果我在手动使用之前调整图像大小,这没有任何区别
CvInvoke.Resize()
来自 C#,并将生成的图像发送到 DLL, 或者发送原始图像(使用 EmguCV)并使用cv::resize()
在 C++ 代码中调整它的大小。这就是阻止我使用 EmguCV 或传递图像原始图像并使用 OpenCV 在 DLL 中调整其大小的原因。
以下是具有不同结果的图像,显示了问题:
--------------------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 resize 以及 DLL 中使用的 OpenCV resize 函数,行为相似但未按预期工作(即像#2)!,应用于图像的 C# resize 方法是有问题的,而如果它是调整大小,保存并传递文件名,结果会很好。
您可以在此处查看描述不同场景的屏幕截图:http://imgur.com/a/xbgIQ
我做的是用imdecode
#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 部分的调整大小无法按预期工作!