C#预处理和后处理 记录

Mat 

  OpenCV C++ n-dimensional dense array class (cv::Mat) OpenCV c++ n维稠密数组类(cv::Mat)

1.矩阵类型:

例:

MatType 的 MatType.CV_8UC1  等

U 是无符号整数(uint) ,8在前则是8位无符号整数,C是通道(channel), 1是通道数, 

MatType.CV_8UC1 的可能是 0~255数值的,透明通道 或 灰度图通道的

还有 MatType.CV_32FC3 等

F 是浮点数(float)

S 是有符号整数(short integer)

U 是无符号整数(uint)

C是通道(channel)

2.构造函数

15个重载

new Mat( 指针)

new Mat()

new Mat(Mat对象)

new Mat(高,宽,矩阵的类型)

new Mat(地址,读取模式)    

构造二维矩阵并用指定的标量值填充它:

new Mat(高,宽, MatType type, Scalar s)

指向用户分配数据的矩阵头的构造函数:

Mat(高,宽, MatType type, Array data, 开始的位置index) 

。。。。。。。。。

预处理:

按训练好的模型的输入张量需要,要和训练时用的数据一样处理

范围:

有些-1~1 范围的 推理结果比较好,

var frame_size = new OpenCvSharp.Size(frame_w, frame_h); 

Mat new_background = new Mat("models/bg.jpg").Resize(frame_size);

new_background.ConvertTo(new_bg_frame, MatType.CV_32FC3, 1 / 255.0f,0);

转对应维度输入张量......

有些是0~1(归一化),

var frame_size = new OpenCvSharp.Size(frame_w, frame_h); 

Mat new_background = new Mat("models/bg.jpg").Resize(frame_size);

new_background.ConvertTo(new_bg_frame, MatType.CV_32FC3, 1 / 127.5.0f,-1);

转对应维度输入张量......

有些归一化 的同时还 

均值(Mean)中心化: 对于每个通道,你将图像中的每个像素值减去该通道的均值。这一步的目的是将数据的中心移到0点,基本上是在去除数据中的偏置。

标准差(Standard Deviation)缩放: 经过第一步处理后的值将再除以该通道的标准差。这一步的目的是在通道间规范化数据的尺度,即让不同的特征具有相似的尺度。

input_tensor = new DenseTensor<float>(new[] { 1, 3, 512, 512 });

for (int y = 0; y < resize_image.Height; y++)
{
    for (int x = 0; x < resize_image.Width; x++)
    {
        input_tensor[0, 0, y, x] = (resize_image.At<Vec3b>(y, x)[0] / 255f - 0.485f) / 0.229f;
        input_tensor[0, 1, y, x] = (resize_image.At<Vec3b>(y, x)[1] / 255f - 0.456f) / 0.224f;
        input_tensor[0, 2, y, x] = (resize_image.At<Vec3b>(y, x)[2] / 255f - 0.406f) / 0.225f;
    }
}

这样预处理后,图像在训练过程中效果更好;然后这样训练出的模型,这样预处理再去推理就效果更好

有些直接0~255

精度优化:

如果要精度更高, 大于的输入张量的宽高维度,则等比缩放 后 填充,小于则填充

            int height = srcimg.Rows;
            int width = srcimg.Cols;
            Mat temp_image = srcimg.Clone();
            if (height > input_height || width > input_width)
            {
                float scale = Math.Min((float)input_height / height, (float)input_width / width);
                Size new_size = new Size((int)(width * scale), (int)(height * scale));
                Cv2.Resize(srcimg, temp_image, new_size);
                ratio_height =  ratio_width = scale;
                Trace.WriteLine("scale:"+ scale);
            }
            else
                ratio_height = ratio_width = 1;

//填充,参数3到6 分别是上下左右填充的像素,BorderTypes.Constant常量,恒定填充参数8

            Cv2.CopyMakeBorder(temp_image, temp_image, 0, input_height - temp_image.Rows, 0, input_width - temp_image.Cols, BorderTypes.Constant, 0);

处理范围....

/*全局变量ratio_height ,ratio_width  ,记下后,在推理完毕后,计算在原图数据*/

处理好的Mat图像矩阵 转 输入张量: 

//转NCHW维度的张量

var channel_size = 640 * 640;
Mat[] bgrChannels = Cv2.Split(temp_image);
Parallel.For(0, 3, c =>
 {

        // 按实际要的范围计算
        bgrChannels[c].ConvertTo(bgrChannels[c], MatType.CV_32FC1, 1 / 127.5f, -1);
        Marshal.Copy(bgrChannels[c].Data, srcInputArray, ((c + 3) % 3) * channel_size, channel_size);
});
my_input_image = srcInputArray;

Tensor<float> input_tensor = new DenseTensor<float>(my_input_image, new[] { 1, 3, input_height, input_width });

另外

 Cv2.Merge(new Mat[]{bgrChannels[2],bgrChannels[1],bgrChannels[0]}, result);  无法再多线程里运行,速度慢1ms~3ms,所以不用这样的方式排序

后处理:

根据模型输出张量信息处理

只列举遇到过的:

1.  pix2pix.onnx   输入输出都是NHWC ,后处理

float[] pdata = 输出张量.AsTensor<float>().ToArray();

float[] floatArray = new float[512 * 512 * 3];
float[] rArray = pdata.Take(size).ToArray();
float[] gArray = pdata.Skip(size).Take(size).ToArray();
float[] bArray = pdata.Skip(size * 2).Take(size).ToArray();

Mat r = new Mat(height, width, MatType.CV_32F, rArray);
Mat g = new Mat(height, width, MatType.CV_32F, gArray);
Mat b = new Mat(height, width, MatType.CV_32F, bArray);

实践

ppMattingv2 抠图后处理:

实践1:加入精度96.63%、FPS 63,SOTA人像分割方案PP-HumanSeg v2开箱即用!-CSDN博客

最后一步 高斯模糊 不对

实践代码:

// 对于当前的输出张量,无改良效果
           

            // 边缘检测以生成边缘掩码
            Mat edges = new Mat();
            Cv2.Canny(mask_out, edges, 100, 200); // 阈值根据需要调整

            // 生成距离变换图
            Mat distTrans = new Mat();
            Cv2.DistanceTransform(edges, distTrans, DistanceTypes.L2, DistanceTransformMasks.Mask5);
            Cv2.Normalize(distTrans, distTrans, 0, 255, NormTypes.MinMax);

            // 高斯模糊平滑距离变换图
            Cv2.GaussianBlur(distTrans, distTrans, new OpenCvSharp.Size(3, 3), 0);
            Cv2.ImShow("GaussianBlur", distTrans);
            

            // 结合边缘渐变到原始掩码
            Mat blend = new Mat();
            distTrans.ConvertTo(distTrans, MatType.CV_8UC1,255.0f,0);//保持mask_out, distTrans 统一类型才能AddWeighted函数传参
            Cv2.AddWeighted(mask_out, 0.5, distTrans, 0.5, 0, blend);
            Cv2.ImShow("blend", blend);
            Mat eMat = new Mat();
            Cv2.Add(mask_out, blend, eMat);

//这个可以去掉外部部分细线,或小的杂物,内部填充少许但边缘处理不行
            // 阈值操作
            
            Cv2.Threshold(mask_out, mask_out, 0, 255, ThresholdTypes.Binary);

          //  mask_out.ConvertTo(mask_out, MatType.CV_8UC1);
            // 创建一个结构元素,用来腐蚀 膨胀等操作的精度
            Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(15, 15),new OpenCvSharp.Point(-1,-1));
            //Mat element2 = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(25, 5), new OpenCvSharp.Point(-1, -1));

            // 闭运算 获取去除内部细节的图
            Mat neiBiHeMat= new Mat();
           
            Cv2.MorphologyEx(mask_out, neiBiHeMat, MorphTypes.Close, Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(3, 3)) );
           // Cv2.MorphologyEx(neiBiHeMat, neiBiHeMat, MorphTypes.Close, Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(15, 1)));
            //Cv2.ImShow("neiBiHeMat", neiBiHeMat);
            // 执行顶帽运算 获取去除内部细节的图的外部细节
            Mat topHat = new Mat();
            Cv2.MorphologyEx(neiBiHeMat, topHat, MorphTypes.TopHat, element);
            
            Cv2.ImShow("外部细节", topHat);

            // 获取去除内部细节的图 去掉 去除 内部细节的图》的外部细节
            Cv2.Subtract(neiBiHeMat, topHat, mask_out);
            Cv2.GaussianBlur(mask_out, mask_out, new OpenCvSharp.Size(5, 5), 0);
            //mask_out = neiBiHeMat;

            if (mask_out.Type() != MatType.CV_32FC1)
            {
                mask_out.ConvertTo(mask_out, MatType.CV_32FC1,1/255.0f,0);
            }
            
            Cv2.ImShow("mask handler11111111", mask_out);
            int size = mask_out.Rows * mask_out.Cols;
            //float[] mask_float_array = new float[size];
            unsafe
            {
                System.Runtime.InteropServices.Marshal.Copy(new IntPtr(mask_out.DataPointer), phaArray, 0, size);
            }

去掉了部分非主体细节,但是方块化了,不平滑了

=========================================

 3.  函数

1.  转换函数 

(1)mat转其它数据/通道的mat

 mat对象.ConvertTo()

///通过可选缩放将数组转换为另一种数据类型, 转换时第三参数还可以乘(也可以乘分数1/255.0f), 第四参数可以 偏移(减)

mat对象.ConvertTo(OutputArray m, MatType rtype, double alpha = 1, double beta = 0)

应用:

var frame_size = new OpenCvSharp.Size(frame_w, frame_h); 

Mat new_background = new Mat("models/bg.jpg").Resize(frame_size);

 new_background.ConvertTo(new_bg_frame, MatType.CV_32FC3, 1 / 255.0f,0);

其中可以

0~1   第三/四参数   1/255.0f  0

-1~1   第三/四参数   1/127.5f  -1

(2)张量 转 数组

float[] pdata = 张量.AsTensor<float>().ToArray();

(3)Mat 转 数组

Marshal.Copy(mat.Data,arr, 开始复制的起点索引, 复制长度);

(4) 数组 转 Mat

var mat= new Mat(高,宽, MatType type, Array data, 开始的位置index) 

① rbg数组  转 Mat

var mat= new Mat(高,宽, MatType.8UC3,rbg数组) 

② Point2f 数组 转 Mat  及  反转

var mat= new Mat(Point2f 数组长度, 1, MatType.CV_32FC2, Point2f 数组 );

Point2f[] face68landmarks;
mat.GetArray<Point2f>(out face68landmarks);

需要注意的是,输出张量和mat 的排列方式,

rrrrrrrrrrrrrrrrrrrrrrrrgggggggggggggggggbbbbbbbbbbbbbbbb 是输出张量 转数组的排列方式

而bgrbgrbgrbgrbgrbgrbgrbgrbgrbgrbgrbgrbgr 是mat图像的排列方式

组合 输出张量 转 数组 ------ 数组 转 Mat

(1)

//pdata是输出张量转的数组, 
float[] temp_r = new float[channel_step];
float[] temp_g = new float[channel_step];
float[] temp_b = new float[channel_step];

Array.Copy(pdata, temp_b, channel_step);
Array.Copy(pdata, channel_step, temp_g, 0, channel_step);
Array.Copy(pdata, channel_step * 2, temp_r, 0, channel_step);

Mat rmat = new Mat(128, 128, MatType.CV_32FC1, temp_r);
Mat gmat = new Mat(128, 128, MatType.CV_32FC1, temp_g);
Mat bmat = new Mat(128, 128, MatType.CV_32FC1, temp_b);

Mat result = new Mat();

Cv2.Merge(new Mat[] { bmat * 255.0f, gmat * 255.0f, rmat * 255.0f }, result);

(5) 转换Mat到NDArray

// 转换Mat到NDArray
byte[] imageData = new byte[image.Rows* image.Cols* 3];
Marshal.Copy(mat.Data, imageData,0,imageData.Length);
var ndArray = (np.array(imageData).astype(np.float32)/ p_number).reshape(image.Height, image.Width, 3);

// NDArray ndArray = image.ToNdArray(); //0.20.5 版本可以0.30.0报错不全

 封装,利用NumSharp库转换顺序(速度非最快):

public DenseTensor<float> ProcessImage(Mat image, float p_number=255.0f)
{
    // 确保图像是8位无符号整数类型
    if (image.Type() != MatType.CV_8UC3)
    {
        throw new InvalidOperationException("图像类型必须是8位无符号整数(CV_8UC3)");
    }
    Mat mat=new Mat();
    // 将图像从BGR转换为RGB
    Cv2.CvtColor(image, mat, ColorConversionCodes.BGR2RGB);

    // 转换Mat到NDArray
    byte[] imageData = new byte[image.Rows* image.Cols* 3];
    Marshal.Copy(mat.Data, imageData,0,imageData.Length);
    var ndArray = (np.array(imageData).astype(np.float32)/ p_number).reshape(image.Height, image.Width, 3); ;
    // NDArray ndArray = image.ToNdArray(); //0.20.5 版本可以0.30.0报错不全

    // 调整维度顺序从[H, W, C]到[C, H, W](256x456是示例尺寸)
    int[] dims = { 2, 0, 1 };
    ndArray = ndArray.transpose(dims);

    // 展平NDArray到1D
    var flattenedArray = ndArray.flatten().ToArray<float>();
    //float[] flattenedArray = ndArray.ToArray<float>();
    // 创建DenseTensor
    var inputTensor = new DenseTensor<float>(flattenedArray, new[] { 1, 3, image.Height, image.Width });
            
    return inputTensor;
}

2.重新设置宽高函数  Cv2.Resize()

 Cv2.Resize(image_rgb, resize_image, new OpenCvSharp.Size(宽, 高));

2. 过滤 0~255 或 其它范围

public Mat ClampMatValues(Mat mat,float sV=0,float eV=255)
{
    Mat result = mat.Clone();
    // 使用Cv2.Min和Cv2.Max限制矩阵内的值
    // 首先,确保所有值都大于等于sV
    Cv2.Max(result, new Scalar(sV), result);
    // 然后,确保所有值都小于等于eV
    Cv2.Min(result, new Scalar(eV), result);
    return result;
}

3.映射  Cv2.Normalize()

//把原来的值 缩放到对应范围,比如0,10,510 映射到 0 ,5,255

Cv2.Normalize(mat, out_mat, 0, 255, NormTypes.MinMax); 

4.   mat[] 合成 mat  Cv2.Merge()

//mat[] 组成mat   ,多个单通道 组合成 多通道
Cv2.Merge(bgrChannels, crop_img);

5.  变换矩阵 相关

例如facefusion 的过程用到:

Mat affine_matrix = new Mat(2, 3, MatType.CV_32FC1,

new float[] { scale, 0.0f, translation[0], 0.0f, scale, translation[1]  } );

[a b tx]
[c d ty]
/*
[scale 0.0f translation[0]]
[0.0f scale translation[1]]*/
a 和 d 是水平和竖直方向的缩放因子,分别控制图像的水平和垂直缩放。
b 和 c 是错切因子,控制图像的变形。
tx 和 ty 是位移因子,控制图像的平移。tx 控制水平平移,ty 控制垂直平移。

(1)  Cv2.WarpAffine()

a 和 d 是水平和竖直方向的缩放因子,分别控制图像的水平和垂直缩放。

b 和 c 是错切因子,控制图像的变形。

tx 和 ty 是位移因子,控制图像的平移。tx 控制水平平移,ty 控制垂直平移。

6. 运算 

参考图解:

C# OpenCvSharp 形态学处理效果

Mat src = Cv2.ImRead("test.jpg");
Mat element = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(3, 3));

Mat dst = new Mat();

// 腐蚀
Mat erodedImage = new Mat();
Cv2.Erode(src, erodedImage, element);

// 膨胀
Mat dilatedImage = new Mat();
Cv2.Dilate(src, dilatedImage, element);

// 开运算
Cv2.MorphologyEx(src, dst, MorphTypes.Open, element);

// 闭运算
Cv2.MorphologyEx(src, dst, MorphTypes.Close, element);

// 顶帽运算
Cv2.MorphologyEx(src, dst, MorphTypes.TopHat, element);

// 黑帽运算
Cv2.MorphologyEx(src, dst, MorphTypes.BlackHat, element);

// 梯度运算
Cv2.MorphologyEx(src, dst, MorphTypes.Gradient, element); 

1.   Mat 对象通道排序转换 

Cv2.CvtColor(mat_image, mat_image, ColorConversionCodes.BGR2RGB);

mat_image输入, 改变默认的BGR 排序 到 BGR, 输出mat_image

也可以  ColorConversionCodes.BGR2其它

2.   OpenCVSharp4 的加减乘除  

除:

Cv2.Divide(resize_image, div, buf, 1.0f, MatType.CV_32FC3);

Cv2.Divide(被除数, 除数, 结果, 缩放因子, 输出矩阵类型);

3.   预处理的 Mat图像数据归一化

NCHW 的输入张量 数据归一化(0~1)

(1)  OpenCV函数来计算

Mat buf = new Mat(inpHeight, inpWidth, MatType.CV_32FC3, new Scalar(0.0f));
Mat div = new Mat(inpHeight, inpWidth, resize_image.Type(), new Scalar(255, 255, 255));
float[] rgbArray = new float[inpWidth* inpHeight * 3];

//Console.WriteLine( $"{ div.Channels()}    {resize_image.Channels()}");
获取并输出resize_image的宽度和高度
//Console.WriteLine("resize_image的宽度: " + resize_image.Width);
//Console.WriteLine("resize_image的高度: " + resize_image.Height);
获取并输出div的宽度和高度
//Console.WriteLine("div的宽度: " + div.Width);
//Console.WriteLine("div的高度: " + div.Height);

// 上面输出信息保证一致后,可以执行除, 参数3
Cv2.Divide(resize_image, div, buf, 1.0f, MatType.CV_32FC3);
Mat[] channels = Cv2.Split(buf); //B,G,R
Marshal.Copy(channels[2].Data, rgbArray, 0, channel_size);//R
Marshal.Copy(channels[1].Data, rgbArray, channel_size, channel_size);//G
Marshal.Copy(channels[0].Data, rgbArray, channel_size * 2, channel_size);//B
Marshal.Copy(resize_image.Data, rgbArray, 0,512*512*3);

/*resize_image: 被除数,通常是一个Mat对象,此处看起来像是一个已经缩放的图像。
div: 除数,可以是一个标量、向量或与resize_image相同尺寸的Mat对象。
buf: 输出结果,通常也是一个Mat对象。在这里,div操作的结果将会存放在这个变量中。
1.0f: 缩放因子,除法的结果会乘以这个因子。在这里设置为1.0,意味着不改变计算的结果。
MatType.CV_32FC3: 输出矩阵的类型。这里指定了三通道浮点数Mat,每个通道32位浮点数。*/

(2) 多线程+指针

unsafe  //NCHW
{
        byte* resizeImagePtr = (byte*)resize_image.Data;
         Parallel.For(0, 256, y =>
        {
                //逐行获取
                for (int x = 0; x < 456; x++)
                {
                        int pixelOffset = (x + y * 456) * 3;  

//x + y * 512 即第y行横向第x个(每多一列就对1个宽度数量的像素),再存RBG就乘3

                         // 假设模型在训练时使用了[0, 1]范围的归一化
                        rgbArray[x + y * 456 + 456 * 256 * 2] = *(resizeImagePtr + pixelOffset) / 255.0f;  // B
                        rgbArray[x + y * 456 + 456 * 256] = *(resizeImagePtr + pixelOffset + 1) / 255.0f;   // G
                        rgbArray[x + y * 456 + 0] = *(resizeImagePtr + pixelOffset + 2) / 255.0f;           // R
                }
        });
}

(3) 易懂易看的写法(无加速,操作张量)

            input_tensor = new DenseTensor<float>(new[] { inpHeight, inpWidth, 3 });
            for (int i = 0; y< inpHeight; y++)
            {
                for (int j = 0; x < inpWidth; x++)
                {
                    // 获取像素值
                    Vec3b pixel = resize_image.Get<Vec3b>(x, y);
                    // 分别提取 R、G、B 值,在 OpenCV 中,像素值的存储顺序是 BGR,而不是 RGB
                    byte r = pixel.Item2;
                    byte g = pixel.Item1;
                    byte b = pixel.Item0;
                    input_tensor[j, i, 0] = r / 255.0f;
                    input_tensor[j, i, 1] = g / 255.0f;
                    input_tensor[j, i, 2] = b / 255.0f;

                }
            }

4. 后处理

(1)张量转Bitmap图片

        static Bitmap TensorToImage_katong(DenseTensor<float> tensor)
         {

            int height = tensor.Dimensions[2];
            int width = tensor.Dimensions[3];
            创建图像对象
            Bitmap image = new Bitmap(width, height);
            将张量的值转换为像素值
            unsafe
            {
                /*在C#中,使用unsafe关键字可以打开不安全代码块。在不安全代码块中,可以使用指针来直接访问内存,并且不受到C#的内存安全限制。
                   * 上述示例中,我们使用了unsafe关键字来访问位图的像素数据。当处理大型图像或需要高性能的图像处理操作时,
                使用指针操作可以提高性能,因为它绕过了C#中的边界检查和安全性检查。*/
                for (int y = 0; y < height; y++)
                {

                    for (int x = 0; x < width; x++)
                    {

                        // 将像素值作为灰度值应用于R、G和B通道
                        System.Drawing.Color pixel = System.Drawing.Color.FromArgb(r, g, b);
                        image.SetPixel(x, y, pixel);
                    }
                }
            }

            image.Save("models/output_katong_linshi.png", ImageFormat.Png);
            return image;
        }

(2) 张量转 Mat图片

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值