分类目录归档:opencv

TorchSharp 调用 PyTorch 语义分割(semantic segmentation)神经网络

TorchSharp 是对 Torch c++的封装,基本继承了c++的全部接口。但使用中会有一些小问题,需要特别注意一些。

  1. 语义分割(semantic segmentation)神经网络训练

训练的代码可以参考github里的官方代码 https://github.com/pytorch/vision/tree/main/references/segmentation

2.模型输出

官方代码的模型 默认输出是list 虽然可以强制输出script文件,但TorchSharp 调用后会报错”Expected Tensor but got GenericDict”.因此需要修改网络

export代码如下:

import torch
import torchvision
from torch import nn
import numpy as np

class MyModel(nn.Module):
    def __init__(self):
        super().__init__()
        self._model = torchvision.models.segmentation.lraspp_mobilenet_v3_large(num_classes=2,
                aux_loss=False,
                pretrained=False)
        
        #修改了输入,将输入改为单通道图片
        #如果输入的是3通道 则不需要修改
        for item in self._model.backbone.items():          
            item[1][0] = nn.Conv2d(1, 16, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
            break                                                         
                                                                 
        checkpoint = torch.load('model/model_119.pth', map_location='cpu')
        self._model.load_state_dict(checkpoint['model'], strict=not True)
        self._model.eval()
        
    def forward(self, x):
        # 修改了输出,将List修改为Tensor 并进行了 argmax 和 转float操作
        result = self._model.forward(x)
        return result["out"].argmax(1).flatten().float()


model = MyModel()
x = torch.rand(1,1, 240, 400)
predictions = model(x)

#使用torch.jit.trace 输出 script pt 网络文件
traced_script_module = torch.jit.trace(model, x)
traced_script_module.save('_seg.pt') 

3.使用TorchSharp 进行预测

由于测试程序使用了opencvsharp,所以下面的代码使用了opencv来读取图片和简单的数据预处理,并使用opencv可视化输出

//导入网络
torch.jit.ScriptModule torch_model;
torch_model = torch.jit.load("_seg.pt");

//导入图片,并使用BlobFromImage 进行数据类型 维度 和归一化的转换
Mat temp = Cv2.ImRead("Pic_42633.bmp", ImreadModes.Grayscale);
Mat tensor_mat = OpenCvSharp.Dnn.CvDnn.BlobFromImage(temp, 1 / 255.0);

//初始化输入的tensor
float[] data_bytes = new float[tensor_mat.Total()];
Marshal.Copy(tensor_mat.Data, data_bytes, 0, data_bytes.Length);
torch.Tensor x = torch.tensor(data_bytes, torch.ScalarType.Float32);

//维度转换 和 normalize(进行normalize是因为官方代码里有这一步处理)
x = x.reshape(1, 1, 240, 400);
x = TorchSharp.torchvision.transforms.functional.normalize(x, new double[] { 0.485 }, new double[] { 0.229 });

DateTime date1 = DateTime.Now;
//进行预测
torch.Tensor _out = torch_model.forward(x);
DateTime date2 = DateTime.Now;
TimeSpan ts = date2 - date1;
Console.WriteLine("No. of Seconds (Difference) = {0}", ts.TotalMilliseconds);
Console.WriteLine(_out);

//使用opencv 将预测出的tensor输出为可视化的图片
Mat result_mat = new Mat(240, 400, MatType.CV_32FC1, _out.bytes.ToArray());
result_mat.ConvertTo(result_mat, MatType.CV_8UC1);
Cv2.ImWrite("mask.bmp", result_mat);

Opencv Inpaint + CUDA

opencv只有cpu接口的Inpaint函数,对于需要使用CUDA进行图片处理时,反复内存显存迁移数据会影响计算速度。在不考虑填充效果十分好的情况下,可以使用如下CUDA算法,简单的进行填充。

算法来源

算法主要参考了https://github.com/Po-Ting-lin/HairRemoval.git 中的填充代码。该项目主要是去除皮肤上的毛发。对于其如何寻找需要填充的区域就不讨论了,直接使用其分析出的mask图进行填充。

该填充算法对细线和小面积的填充效果还可以 大面积的 效果就很一般了

继续阅读

OPENCV + CUDA 实现小波滤波

opencv中有cpu 和 gpu版本的DFT函数,及傅里叶变换的函数,可以实现dft滤波。但opencv中没有DWT,及小波变换。下面将介绍一下实现的方法。

傅里叶变换与小波变换都能实现滤波,不好说那个更好。但傅里叶变换有个缺点,对于图像处理来说,其在处理图像锐利边缘时,很容易出现边缘抖动的情况。原因如下图:

傅里叶变换只能使用余弦波,很难拟合出突变。具体的解释和小波变换的特点。可以参考知乎上的文章 https://zhuanlan.zhihu.com/p/44215123

代码部分主要参考了GitHub上实现方法,经过一些简单的修改以适应vc++,和高频滤波的需要。原链接https://github.com/pierrepaleo/PDWT

继续阅读

Jetson nano 获取CSI相机RAW图片并转换为Mat

树莓派可以通过加入 参数 “bayer=True” ,来获取CSI相机的raw图片,raw数据会紧跟在jpg图片的末尾,具体提取的方法不在累述,可以方便的google到。而Nvidia的 Jetson nano要获取raw图片的方法网上比较零散,故整理了一下。

获取raw图片的方法其实比较简单,使用的是jetson nano中自带的Libargus api。其api介绍可以参考https://docs.nvidia.com/jetson/l4t-multimedia/group__LibargusAPI.html,api架构可以参考https://docs.nvidia.com/jetson/archives/l4t-archived/l4t-3231/index.html#page/Tegra%20Linux%20Driver%20Package%20Development%20Guide/jetson_xavier_camera_soft_archi.html

相关代码在 Libargus api demo中的 argus\samples\oneShot 简单修改而来。并增加了转换为opencvmat的代码

继续阅读

Opencv中使用CUDA原函数

opencv中的cuda模块封装了大部分常用的图像处理函数。但一些函数只提供了8bit图片的接口,没有16bit图片的接口。如果需要处理10bit 12bit或更高big的图片就需要调用CUDA的原型函数了。下面就简单举例使用opencv中的GpuMat调用cuda原函数的方法。

opencv中存储GPU图片的类型为GpuMat,不需要考虑显存的分配和释放,使用起来比较方便。而CUDA使用的是Npp8u* Npp16u*等指针,这里就涉及指针的转换。以nppiLUTPalette_16u_C1R 函数举例。

Mat img = ….. //读取一张12bit图片
cuda::GpuMat src_img; //初始化Gpu
src_img.upload(img); //上传图片到Gpu
Mat lut(1, 4096, CV_16UC1, lineardata.data()); // 导入lut数据
cuda::GpuMat gpu_lut = new cv::cuda::GpuMat(1, 4096, CV_16UC1);
gpu_lut.upload(lut); //上传lut数据到Gpu,lut数据也要放入Gpu中
cuda::GpuMat dst_gpu(src_img.size(), CV_16UC1); //初始化转化后的GpuMat
NppiSize oSizeROI = { src_img.cols, src_img.rows }; //设置ROI 这里是整张图
//使用prt获取GpuMat的指针
NppStatus status1 = nppiLUTPalette_16u_C1R(src_img.ptr(), static_cast(src_img.step), dst_gpu.ptr(), static_cast(dst_gpu.step), oSizeROI, gpu_lut.ptr(),16);

得到的dst_gpu就是经过lut后的图片,后续的使用也不需要考虑显存的分配和释放。

Opencv中使用cuda进行 dft 与 idft滤波运算

opencv源代码中包含了dft的demo,但没有使用cuda的demo。下文会简单给出一个cuda例程,并进行简单的高频滤波。

为方便说明,将程序分成了若干部分
1.头文件

#include <iostream>

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/cudaarithm.hpp>

using namespace cv;

2.导入图片加载到GPU,为简单使用了单色图

继续阅读

opencv 中使用 cudnn 预测CNN网络

opencv从3版本开始就已经支持CNN网络模型的预测,到4版本,主流工具tensorflow,pytorch 生成的模型文件大部分都可以支持。但其一直没有使用到CUDNN。但最新发布的4.2版本的opencv已经支持CUDNN了。以下是功能测试

一。安装编译环境

CUDNN的安装方法可以参考tensorflow中关于cudnn的安装方法https://www.tensorflow.org/install/gpu 接下来也会使用tensorflow作为例子。

二。编译opencv

编译方法参考链接https://docs.opencv.org/4.2.0/d3/d52/tutorial_windows_install.html 其中为了加入CUDNN的支持,有以下需要设置一下

1.勾选cuda和cudnn

2.设置CUDA_ARCH_BIN
cudnn不支持3.5下的显卡。需要根据自己使用的显卡来设置,列表参考https://developer.nvidia.com/cuda-gpus

3.代码bug修正
由于我使用的是vs2015,版本低了点。导致编译时会出错。github上已经修复了这个bug,估计会在下一个版本发布。所以低版本vs编译时 需要手动修改一下

文件 modules/dnn/src/cuda/grid_stride_range.hpp

namespace detail {
    template <int>  __device__ auto getGridDim()>decltype(dim3::x);
    template <> inline __device__ auto getGridDim<0>()>decltype(dim3::x) { return gridDim.x; }
    template <> inline __device__ auto getGridDim<1>()>decltype(dim3::x) { return gridDim.y; }
    template <> inline __device__ auto getGridDim<2>()>decltype(dim3::x) { return gridDim.z; }

    template <int> __device__ auto getBlockDim()>decltype(dim3::x);
    template <> inline __device__ auto getBlockDim<0>()>decltype(dim3::x) { return blockDim.x; }
    template <> inline __device__ auto getBlockDim<1>()>decltype(dim3::x) { return blockDim.y; }
    template <> inline __device__ auto getBlockDim<2>()>decltype(dim3::x) { return blockDim.z; }

    template <int> __device__ auto getBlockIdx()>decltype(uint3::x);
    template <> inline __device__ auto getBlockIdx<0>()>decltype(uint3::x) { return blockIdx.x; }
    template <> inline __device__ auto getBlockIdx<1>()>decltype(uint3::x) { return blockIdx.y; }
    template <> inline __device__ auto getBlockIdx<2>()>decltype(uint3::x) { return blockIdx.z; }

    template <int> __device__ auto getThreadIdx()>decltype(uint3::x);
    template <> inline __device__ auto getThreadIdx<0>()>decltype(uint3::x) { return threadIdx.x; }
    template <> inline __device__ auto getThreadIdx<1>()>decltype(uint3::x) { return threadIdx.y; }
    template <> inline __device__ auto getThreadIdx<2>()>decltype(uint3::x) { return threadIdx.z; }
}

替换为

namespace detail {
    using dim3_member_type = decltype(dim3::x);

    template <int>  __device__ dim3_member_type getGridDim();
    template <> inline __device__ dim3_member_type getGridDim<0>() { return gridDim.x; }
    template <> inline __device__ dim3_member_type getGridDim<1>() { return gridDim.y; }
    template <> inline __device__ dim3_member_type getGridDim<2>() { return gridDim.z; }

    template <int> __device__ dim3_member_type getBlockDim();
    template <> inline __device__ dim3_member_type getBlockDim<0>() { return blockDim.x; }
    template <> inline __device__ dim3_member_type getBlockDim<1>() { return blockDim.y; }
    template <> inline __device__ dim3_member_type getBlockDim<2>() { return blockDim.z; }

    using uint3_member_type = decltype(uint3::x);

    template <int> __device__ uint3_member_type getBlockIdx();
    template <> inline __device__ uint3_member_type getBlockIdx<0>() { return blockIdx.x; }
    template <> inline __device__ uint3_member_type getBlockIdx<1>() { return blockIdx.y; }
    template <> inline __device__ uint3_member_type getBlockIdx<2>() { return blockIdx.z; }

    template <int> __device__ uint3_member_type getThreadIdx();
    template <> inline __device__ uint3_member_type getThreadIdx<0>() { return threadIdx.x; }
    template <> inline __device__ uint3_member_type getThreadIdx<1>() { return threadIdx.y; }
    template <> inline __device__ uint3_member_type getThreadIdx<2>() { return threadIdx.z; }
}

文件 modules/dnn/src/cuda4dnn/csl/cudnn/cudnn.hpp

    template <class> auto get_data_type()->decltype(CUDNN_DATA_FLOAT);
    template <> inline auto get_data_type<half>()->decltype(CUDNN_DATA_HALF) { return CUDNN_DATA_HALF; }
    template <> inline auto get_data_type<float>()->decltype(CUDNN_DATA_FLOAT) { return CUDNN_DATA_FLOAT; }

替换为

    using cudnn_data_enum_type = decltype(CUDNN_DATA_FLOAT);
    template <class> cudnn_data_enum_type get_data_type();
    template <> inline cudnn_data_enum_type get_data_type<half>() { return CUDNN_DATA_HALF; }
    template <> inline cudnn_data_enum_type get_data_type<float>() { return CUDNN_DATA_FLOAT; }

修改完成后正常编译就可以了

三。使用

设置为使用cudnn非常简单,只需要在原来的基础上添加两行代码。

net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA); 
net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA); 

具体opencv怎么使用模型文件可以参考博客之前的文章https://www.yangyouji.info/archives/349

C# 中使用 OpencvSharp 与 TensorflowSharp 进行Mask-RCNN模型的预测

网上可以找到许多TensorFlow的Mask-RCNN版本,现以github上一个Star较多的版本为例,介绍如何在C#中部署Mask-RCNN模型

一.前期准备

1.同步Mask-RCNN的代码,地址为 https://github.com/matterport/Mask_RCNN.git

2.训练模型:
可以使用jupyter打开samples文件夹下的例子进行训练,也可以参考其例子训练自己的模型。目标是生存保存好权重的模型文件。模型每训练一个epochs 会在 model_dir=MODEL_DIR 设置好的文件夹下留下模型文件。具体训练方法就不累述了~~~

3.在VS里使用Nuget,给项目添加OpencvSharp 与 TensorflowSharp

继续阅读

OpencvSharp 中使用 cuda

opencvsharp 是 opencv的c#版本,近期有项目使用了opencvsharp来进行图像处理。这个github上星级很高的项目果然是不错的,运行起来比较稳定,没有出现大的问题。但opencvsharp中没有cuda的完整支持,只有最基本的类型支持,无任何算法支持,想用就只能靠自己添加了。作者的解释如下:

大概就是说cuda需要用户自己编译opencv ,没有一个统一版本的dll提供使用,所以就删除了cuda的支持
其实也对,cuda的使用涉及cuda版本,使用的显卡算力等。使用c++版本的opencv时,也是要自己编译的。
但c#上使用就没有办法了吗?还好作者已经打好了基础,提供了GpuMat的支持,并有大量cup版本的函数进行参考。添加起来还是比较容易的。
项目目录 https://github.com/shimat/opencvsharp

继续阅读

opencv + tensorflow + C++ 对RSNet模型进行预测

本文介绍了一种使用opencv + c++ 可以在生产环境下调用tensorflow pb文件进行预测的方法。
该方法不需要在生产环境下搭建python运行环境。 流程上相对简单清晰。如果要将神经网络/机器学习添加到几年前项目中,也比较简单可行。

一.pb文件的生成

本人使用的RsNet模型完全参考github上官方给出的demo训练生成。网址https://github.com/tensorflow/models
训练入口文件official/resnet/imagenet_main.py (主线版本 v1.8.1)
只定义了2个分类,具体的训练方法就不再累

训练完成后会在根目录生成一个imagenet_model文件夹,里面存储了模型文件(ckpt文件)。接下来需要将模型文件固化成pb文件。为了让opencv可以使用这个pb文件,需要定义好神经网络input入口与output出口

继续阅读