用opencv的dnn模块做yolov5目标检测

深度学习推理部署,真好玩😄😄😄

最近在微信公众号里看到多篇讲解yolov5在openvino部署做目标检测文章,但是没看到过用opencv的dnn模块做yolov5目标检测的。于是,我就想着编写一套用opencv的dnn模块做yolov5目标检测的程序。在编写这套程序时,遇到的bug和解决办法,在这篇文章里讲述一下。

在yolov5之前的yolov3和yolov4的官方代码都是基于darknet框架的实现的,因此opencv的dnn模块做目标检测时,读取的是.cfg和.weight文件,那时候编写程序很顺畅,没有遇到bug。但是yolov5的官方代码(https://github.com/ultralytics/yolov5)是基于pytorch框架实现的,但是opencv的dnn模块不支持读取pytorch的训练模型文件的。如果想要把pytorch的训练模型.pth文件加载到opencv的dnn模块里,需要先把pytorch的训练模型.pth文件转换到.onnx文件,然后才能载入到opencv的dnn模块里。

因此,用opencv的dnn模块做yolov5目标检测的程序,包含两个步骤:(1).把pytorch的训练模型.pth文件转换到.onnx文件。(2).opencv的dnn模块读取.onnx文件做前向计算。

(1).把pytorch的训练模型.pth文件转换到.onnx文件

在做这一步时,我得吐槽一下官方代码:https://github.com/ultralytics/yolov5,这套程序里的代码混乱,在pytorch里,通常是在.py文件里定义网络结构的,但是官方代码是在.yaml文件定义网络结构,利用pytorch动态图特性,解析.yaml文件自动生成网络结构。在.yaml文件里有depth_multiple和width_multiple,它是控制网络的深度和宽度的参数。这么做的好处是能够灵活的配置网络结构,但是不利于理解网络结构,假如你想设断点查看某一层的参数和输出数值,那就没办法了。因此,在我编写的转换到.onnx文件的程序里,网络结构是在.py文件里定义的。其次,在官方代码里,还有一个奇葩的地方,那就是.pth文件。起初,我下载官方代码到本地运行时,torch.load读取.pth文件总是出错,后来把pytorch升级到1.7,就读取成功了。可以看到版本兼容性不好,这是它的一个不足之处。设断点查看读取的.pth文件里的内容,可以看到ultralytics的.pt文件里既存储有模型参数,也存储有网络结构,还储存了一些超参数,包括anchors,stride等等的。第一次见到有这种操作的,通常情况下,.pth文件里只存储了训练模型参数的。

查看models\yolo.py里的Detect类,在构造函数里,有这么两行代码:

我尝试过把这两行代码改成self.anchors = a 和 self.anchor_grid = a.clone().view(self.nl, 1, -1, 1, 1, 2),程序依然能正常运行,但是torch.save保存模型文件后,可以看到.pth文件里没有存储anchors和anchor_grid了,在百度搜索register_buffer,解释是:pytorch中register_buffer模型保存和加载的时候可以写入和读出。

在这两行代码的下一行:

它的作用是做特征图的输出通道对齐,通过1x1卷积把三种尺度特征图的输出通道都调整到 num_anchors*(num_classes+5)。

阅读Detect类的forward函数代码,可以看出它的作用是根据偏移公式计算出预测框的中心坐标和高宽,这里需要注意的是,计算高和宽的代码:

                                                                 pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]

没有采用exp操作,而是直接乘上anchors[i],这是yolov5与yolov3v4的一个最大区别(还有一个区别就是在训练阶段的loss函数里,yolov5采用邻域的正样本anchor匹配策略,增加了正样本。其它的是一些小区别,比如yolov5的第一个模块采用FOCUS把输入数据2倍下采样切分成4份,在channel维度进行拼接,然后进行卷积操作,yolov5的激活函数没有使用Mish)。

现在可以明白Detect类的作用是计算预测框的中心坐标和高宽,简单来说就是生成proposal,作为后续NMS的输入,进而输出最终的检测框。我觉得在Detect类里定义的1x1卷积是不恰当的,应该把它定义在Detect类的外面,紧邻着Detect类之前定义1x1卷积。

在官方代码里,有转换到onnx文件的程序: python models/export.py --weights yolov5s.pt --img 640 --batch 1

在pytorch1.7版本里,程序是能正常运行生成onnx文件的。观察export.py里的代码,在执行torch.onnx.export之前,有这么一段代码:

注意其中的for循环,我试验过注释掉它,重新运行就会出错,打印出的错误如下:

由此可见,这段for循环代码是必需的。SiLU其实就是swish激活函数,而在onnx模型里是不直接支持swish算子的,因此在转换生成onnx文件时,SiLU激活函数不能直接使用nn.Module里提供的接口,而需要自定义实现它。

(2).opencv的dnn模块读取.onnx文件做前向计算

在生成.onnx文件后,就可以用opencv的dnn模块里的cv2.dnn.readNet读取它。然而,在读取时,出现了如下错误:

我在百度搜索这个问题的解决办法,看到一篇知乎文章(Pytorch转ONNX-实战篇2(实战踩坑总结) - 知乎),文章里讲述的第一条:

于是查看yolov5的代码,在common.py文件的Focus类,torch.cat的输入里有4次切片操作,代码如下:

那么现在需要更换索引式的切片操作,观察到注释的Contract类,它就是用view和permute函数完成切片操作的,于是修改代码如下:

其次,在models\yolo.py里的Detect类里,也有切片操作,代码如下:

前面说过,Detect类的作用是计算预测框的中心坐标和高宽,生成proposal,这个是属于后处理的,因此不需要把它写入到onnx文件里。

总结一下,按照上面的截图代码,修改Focus类,把Detect类里面的1x1卷积定义在紧邻着Detect类之前的外面,然后去掉Detect类,组成新的model,作为torch.onnx.export的输入,

torch.onnx.export(model, inputs, output_onnx, verbose=False, opset_version=12, input_names=['images'], output_names=['out0', 'out1', 'out2'])

最后生成的onnx文件,opencv的dnn模块就能成功读取了,接下来对照Detect类里的forward函数,用python或者C++编写计算预测框的中心坐标和高宽的功能。

周末这两天,我在win10+cpu机器里编写了用opencv的dnn模块做yolov5目标检测的程序,包含Python和C++两个版本的。程序都调试通过了,运行结果也是正确的。我把这套代码发布在github上,地址是

https://github.com/hpc203/yolov5-dnn-cpp-python

后处理模块,python版本用numpy array实现的,C++版本的用vector和数组实现的,整套程序只依赖opencv库(opencv4版本以上的)就能正常运行,彻底摆脱对深度学习框架pytorch,tensorflow,caffe,mxnet等等的依赖。用openvino作目标检测,需要把onnx文件转换到.bin和.xml文件,相比于用dnn模块加载onnx文件做目标检测是多了一个步骤的。因此,我就想编写一套用opencv的dnn模块做yolov5目标检测的程序,用opencv的dnn模块做深度学习目标检测,在win10和ubuntu,在cpu和gpu上都能运行,可见dnn模块的通用性更好,很接地气。

生成yolov5s_param.pth 的步骤,首先下载https://github.com/ultralytics/yolov5 的源码到本地,在yolov5-master主目录(注意不是我发布的github代码目录)里新建一个.py文件,把下面的代码复制到.py文件里

import torch
from collections import OrderedDict
import pickle
import os

device = 'cuda' if torch.cuda.is_available() else 'cpu'

if __name__=='__main__':
    choices = ['yolov5s', 'yolov5l', 'yolov5m', 'yolov5x']
    modelfile = choices[0]+'.pt'
    utl_model = torch.load(modelfile, map_location=device)
    utl_param = utl_model['model'].model
    torch.save(utl_param.state_dict(), os.path.splitext(modelfile)[0]+'_param.pth')
    own_state = utl_param.state_dict()
    print(len(own_state))

    numpy_param = OrderedDict()
    for name in own_state:
        numpy_param[name] = own_state[name].data.cpu().numpy()
    print(len(numpy_param))
    with open(os.path.splitext(modelfile)[0]+'_numpy_param.pkl', 'wb') as fw:
        pickle.dump(numpy_param, fw)

运行这个.py文件,这时候就可以生成yolov5s_param.pth文件。之所以要进行这一步,我在上面讲到过:ultralytics的.pt文件里既存储有模型参数,也存储有网络结构,还储存了一些超参数,包括anchors,stride等等的。torch.load加载ultralytics的官方.pt文件,也就是utl_model = torch.load(modelfile, map_location=device)这行代码,在这行代码后设断点查看utl_model里的内容,截图如下

可以看到utl_model里含有既存储有模型参数,也存储有网络结构,还储存了一些超参数等等的,这会严重影响转onnx文件。此外,我还发现,如果pytorch的版本低于1.7,那么在torch.load加载.pt文件时就会出错的。

此外,有读者反映,在Focus类的构造函数里添加了 self.contract = Contract(gain=2) ,之后在forward函数里报错,错误信息是没有contract这个成员。之所以会出现这个错误,原因正如上面所说的在ultralytics的.pt文件里既存储有模型参数,也存储有网络结构。因而在Focus类的构造函数里添加的成员是不会生效的,做一个小实验来验证,在forward函数设断点,运行,截图如下

 可以看到self里并没有contract这个成员。想要在导出onnx文件时不出错的做法是,代码如下:

class Focus(nn.Module):  
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Focus, self).__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
        #self.contract = Contract(gain=2)

    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        if torch.onnx.is_in_onnx_export():
            contract = Contract(gain=2)
            return self.conv(contract(x))
        else:
            return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))

因此在程序里,我把模型参数转换到cpu.numpy形式的,最后保存在.pkl文件里。这时候在win10系统cpu环境里,即使你的电脑没有安装pytorch,也能通过python程序访问到模型参数。

pytorch转onnx常见坑:

1. onnx只能输出静态图,因此不支持if-else分支。一次只能走一个分支。如果代码中有if-else语句,需要改写。
2. onnx不支持步长为2的切片。例如a[::2,::2]
3. onnx不支持对切片对象赋值。例如a[0,:,:,:]=b, 可以用torch.cat改写
4. onnx里面的resize要求output shape必须为常量。可以用以下代码解决:

if isinstance(size, torch.Size):
    size = tuple(int(x) for x in size)

此外,在torch.onnx.export(model, inputs, output_onnx)的输入参数model里,应该只包含网络结构,也就是说model里只含有nn.Conv2d, nn.MaxPool2d, nn.BatchNorm2d, F.relu等等的这些算子组件,而不应该含有后处理模块的。图像预处理和后处理模块需要自己使用C++或者Python编程实现。

在明白了这些之后,在转换生成onnx文件,你需要执行两个步骤,第一步把原始训练模型.pt文件里的参数保存到新的.pth文件里,第二步编写yolov5.py文件,把yolov5的往来结构定义在.py文件里,此时需要注意网络结构里不能包含切片对象赋值操作,F.interpolate里的size参数需要加int强制转换。在执行完这两步之后才能生成一个opencv能成功读取并且做前向推理的onnx文件。

不过,最近我发现在yolov5-pytorch程序里,其实可以直接把原始训练模型.pt文件转换生成onnx文件的,而且我在一个yolov5检测人脸+关键点的程序里实验成功了。

        5月1日,我把这套程序发布在github上,地址是 https://github.com/hpc203/yolov5-face-landmarks-opencv 和 https://github.com/hpc203/yolov5-face-landmarks-opencv-v2

这套程序只依赖opencv库就可以运行yolov5检测人脸+关键点,程序依然是包含C++和Python两个版本的,这套程序里还有一个转换生成onnx文件的python程序文件。只需运行这一个.py文件就可以生成onnx文件,而不需要之前讲的那样执行两个步骤,这样大大简化了生成onnx文件的流程,使用方法可以阅读程序里的README文档。

在这个新的转换生成onnx文件的程序里,需要重新定义yolov5网络结构,主要是修改第一个模块Focus,用Contract类替换索引式的切片操作,在最后一个模块Detect类里,只保留三个1x1卷积,剩下的make_grid和decode属于后处理,不能包含在网络结构里,代码截图如下

如果要转换生成onnx文件,需要设置export = True,这时候Detect模块的forward就只进行1x1卷积,这时的网络结构就可以作为torch.onnx.export(model, inputs, output_onnx)的输入参数model。不过由于ultralytics的yolov5代码仓库几乎每天都在更新,因此你现在看到的ultralytics的yolov5里的Detect类很有可能不是这么写的,那这是需要你手动修改程序,然后再运行。

        8月8日,看到最近旷视发布的anchor-free系列的YOLOX,而在github开源的代码里,并没有使用opencv部署的程序。因此,我就编写了一套使用OpenCV部署YOLOX的程序,支持YOLOX-S、YOLOX-M、YOLOX-L、YOLOX-X、YOLOX-Darknet53五种结构,包含C++和Python两种版本的程序实现。在今天我在github发布了这套程序,地址是

 https://github.com/hpc203/yolox-opencv-dnn

在旷视发布的YOLOX代码里,提供了在COCO数据集上训练出来的.pth模型文件,并且也提供了导出onnx模型的export_onnx.py文件,起初我运行export_onnx.py生成onnx文件之后Opencv读取onnx文件失败了,报错原因跟文章最开始的第(2)节里的一样,这说明在YOLOX的网络结构里有切片操作,经过搜索后,在 yolox\models\network_blocks.py 里有个Focus类,它跟YOLOv5里的Focus是一样的,都是把输入张量切分成4份,然后concat+conv。这时按照第(2)节里讲述的解决办法,修改Focus类,重新运行export_onnx.py生成onnx文件,Opencv读取onnx文件就不会再出错了。

        8月22日,我在github发布了一套使用OpenCV部署Yolo-FastestV2的程序,依然是包含C++和Python两种版本的程序实现。地址是

https://github.com/hpc203/yolo-fastestv2-opencv

经过运行,体验到这个Yolo-FastestV2的速度确实很快,而且onnx文件只有957kb大小,不超过1M。在官方代码https://github.com/dog-qiuqiu/Yolo-FastestV2里,学习它的网络结构。设断点调试,查看中间变量可以看到,在model/detector.py,网络输出了6个张量

 它们的形状分别是

torch.Size([1, 12, 22, 22])
torch.Size([1, 3, 22, 22])
torch.Size([1, 80, 22, 22])
torch.Size([1, 12, 11, 11])
torch.Size([1, 3, 11, 11])
torch.Size([1, 80, 11, 11])

结合配置文件data/coco.data,可以看到模型输入是352x352的图片,而输出有22x22和11x11这两种尺度的特征图,这说明Yolo-FastestV2的输出只有缩放16倍和缩放32倍这两种尺度的特征图,比yolov3,v4,v5系列的都要少一个尺度特征图。其次在配置文件data/coco.data还可以看到anchor一共有6个,分别给两个尺度特征图里的网格点分配3个。观察输出的6个张量的形状信息,很明显前3个张量是22x22尺度特征图的检测框坐标回归量bbox_reg,检测框目标置信度obj_conf,检测框类别置信度cls_conf。由于给每个网格点分配3个anchor,检测框坐标包含(center_x, center_y, width, height),因此维数是4*3=12,这也就明白了bbox_reg的第1个维度是12,obj_conf的第1个维度是3,而COCO数据集有80类,那么cls_conf的第1个维度应该是3*80=240,但是在上面调试信息里显示的是80类。继续设断点调试代码,在utils/utils.py里,第326行有这么一行代码

类别置信度复制了3份,结合这个后处理代码,可以看出类别置信度对3个anchor是共享的。

        在观察出Yolo-FastestV2的这些特性之后,可以理解为何它的速度快和模型文件小的原因了。主要是因为它的输入图片尺寸比传统yolov3,v4,v5系列的要小,它的输出特征图尺寸个数,也比传统yolo的要少,最后对网格点上的3个anchor是共享类别置信度的,这也减少了特种通道数。

        8月29日,我在github发布了一套使用OpenCV部署全景驾驶感知网络YOLOP,可同时处理交通目标检测、可驾驶区域分割、车道线检测,三项视觉感知任务,依然是包含C++和Python两种版本的程序实现。地址是:

https://github.com/hpc203/YOLOP-opencv-dnn

在这里我讲一下生成onnx文件需要注意的地方,YOLOP的官方代码地址是 https://github.com/hustvl/YOLOP  ,它是华中科技大学视觉团队发布的,它的代码是使用pytorch作为深度学习框架。仔细阅读和运行调试他的代码,可以看出,它的代码是在ultralytics的yolov5里修改的,添加了可行驶区域分割和车道线分割这两个分割头,在bdd100k数据集上的训练的,不过YOLOP的检测类别只保留了bdd100k数据集里的车辆这一个类别。生成onnx文件,第一步是把我发布的代码里的export_onnx.py拷贝到https://github.com/hustvl/YOLOP的主目录里。第二步,在https://github.com/hustvl/YOLOP的主目录里,打开lib/models/common.py,首先修改Focus类,原始的Focus类的forward函数里是由切片操作的,那么这时按照第(2)节里讲述的解决办法,修改Focus类,示例代码如下

class Contract(nn.Module):
    # Contract width-height into channels, i.e. x(1,64,80,80) to x(1,256,40,40)
    def __init__(self, gain=2):
        super().__init__()
        self.gain = gain

    def forward(self, x):
        N, C, H, W = x.size()  # assert (H / s == 0) and (W / s == 0), 'Indivisible gain'
        s = self.gain
        x = x.view(N, C, H // s, s, W // s, s)  # x(1,64,40,2,40,2)
        x = x.permute(0, 3, 5, 1, 2, 4).contiguous()  # x(1,2,2,64,40,40)
        return x.view(N, C * s * s, H // s, W // s)  # x(1,256,40,40)
    
class Focus(nn.Module):
    # Focus wh information into c-space
    # slice concat conv
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Focus, self).__init__()
        self.conv = Conv(c1 * 4, c2, k, s, p, g, act)
        self.contract = Contract(gain=2)
    def forward(self, x):  # x(b,c,w,h) -> y(b,4c,w/2,h/2)
        # return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1))
        return self.conv(self.contract(x))

接下来修改Detect类里的forward函数,示例代码如下

def forward(self, x):
    if not torch.onnx.is_in_onnx_export():
        z = []  # inference output
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            # print(str(i)+str(x[i].shape))
            bs, _, ny, nx = x[i].shape  # x(bs,255,w,w) to x(bs,3,w,w,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
            # print(str(i)+str(x[i].shape))

            if not self.training:  # inference
                if self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
                y = x[i].sigmoid()
                # print("**")
                # print(y.shape) #[1, 3, w, h, 85]
                # print(self.grid[i].shape) #[1, 3, w, h, 2]
                y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i]  # xy
                y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh
                """print("**")
                print(y.shape)  #[1, 3, w, h, 85]
                print(y.view(bs, -1, self.no).shape) #[1, 3*w*h, 85]"""
                z.append(y.view(bs, -1, self.no))
        return x if self.training else (torch.cat(z, 1), x)
    else:
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            # print(str(i)+str(x[i].shape))
            bs, _, ny, nx = x[i].shape  # x(bs,255,w,w) to x(bs,3,w,w,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
            x[i] = torch.sigmoid(x[i])
            x[i] = x[i].view(-1, self.no)
        return torch.cat(x, dim=0)

修改完之后,运行export_onnx.py就能生成onnx文件,并且opencv读取正常的。

        9月18日,我在github上发布了一套使用ONNXRuntime部署anchor-free系列的YOLOR,依然是包含C++和Python两种版本的程序。起初我是想使用OpenCV部署的,但是opencv读取onnx文件总是出错,于是我换用ONNXRuntime部署。地址是:

https://github.com/hpc203/yolor-onnxruntime

       9月19日,我在github上发布了一套使用深度学习算法实现虚拟试衣镜,结合了人体姿态估计、人体分割、几何匹配和GAN,四种模型。仅仅只依赖opencv库就能运行,除此之外不再依赖任何库。源码地址是:

https://github.com/hpc203/virtual_try_on_use_deep_learning

      10月6日,我在github上发布了一套使用ONNXRuntime部署鲁棒性视频抠图的程序,依然是包含C++和Python两种版本的程序。起初,我想使用opencv的dnn模块作为推理引擎,但是程序运行到cv2.dnn.readNet(modelpath) 这里时报错,因此使用onnxruntime 作为推理引擎,源码地址是:

https://github.com/hpc203/robustvideomatting-onnxruntime

        10月17日,我在github上发布了使用OpenCV部署SCRFD人脸检测,依然是包含C++和Python两种版本的程序实现。SCRFD是一个FCOS式的人脸检测器,2021年5月在insightface仓库里发布的,它也是检测人脸矩形框和5个关键点。我发布在github上的源码地址是:

https://github.com/hpc203/scrfd-opencv

        11月6日,我在github发布了使用OpenCV部署libface人脸检测和SFace人脸识别,包含C++和Python两种版本的程序,仅仅只依赖OpenCV库就能运行。源码地址是:

https://github.com/hpc203/libface-sface_detect-recognition-opencv

人脸检测和人脸识别模块是由人脸识别领域的两位大牛设计的, 其中人脸检测是南科大的于仕琪老师设计的,人脸识别模块是北邮的邓伟洪教授设计,其研究成果SFace发表在图像处理顶级期刊IEEE Transactions on Image Processing。 人脸检测示例程序在opencv-master/samples/dnn/face_detect.cpp里,起初我在win10系统里,在visual stdio 2019 里新建一个空项目,然后把opencv-master/samples/dnn/face_detect.cpp拷贝进来作为主程序,尝试编译,发现编译不通过。 仔细看代码可以发现face_detect.cpp里使用了类的继承和虚函数重写,这说明依赖包含了其他的.cpp和.hpp头文件的。因此我就编写一套程序, 人脸检测和人脸识别程序从opencv源码里剥离出来,只需编写一个main.cpp文件,就能运行人脸检测和人脸识别程序。于仕琪老师设计的libface人脸检测,有一个特点就是输入图像的尺寸是动态的,也就是说对输入图像不需要做resize到固定尺寸,就能输入到神经网络做推理的,此前我发布的一些人脸检测程序都没有做到这一点,而且模型文件.onnx只有336KB。因此,这套人脸检测模型是 非常有应用价值的。在下载完代码之后,在visual stdio 2019里新建一个空项目,配置opencv,然后把main.cpp和weights文件拷贝进去,接下来编译运行就可以了。

        12月4日,我在github发布了使用OpenCV部署P2PNet人群检测和计数,包含C++和Python两种版本的实现,仅仅只依赖OpenCV库就能运行。源码地址是:

https://github.com/hpc203/crowdcounting-p2pnet-opencv

        12月12日,我在github发布了使用OpenCV部署faster-rcnn检测证件照,包含C++和Python两种版本的程序,仅仅只依赖opencv库就能运行。源码地址是:

https://github.com/hpc203/faster-rcnn-card-opencv

还发布了使用OpenCV部署YOLOV3检测二维码,包含C++和Python两种版本的程序,仅仅只依赖opencv库就能运行。源码地址是:

https://github.com/hpc203/yolo-qrcode-opencv

        12月18日,我在github发布了使用ONNXRuntime部署PicoDet目标检测,包含C++和Python两个版本的程序,源码地址是:

https://github.com/hpc203/picodet-onnxruntime

起初,我是想使用opencv部署PicoDet目标检测的,但是opencv的dnn模块读取.onnx文件失败了,报错信息是这样的

可以看到在onnx文件里有一个opencv的dnn模块不支持的层HardSigmoid,在PicoDet官方代码仓库里搜索HardSigmoid,结果截图如下

可以看到它是直接调用pytorch的接口函数HardSigmoid()来实现的。如果自定义函数实现HardSigmoid,那么转换生成的onnx文件就能被opencv的dnn模块正常读取的了。可是PicoDet官方代码是基于PaddlePaddle框架编写的,我对这个框架不熟悉,因此没有去修改源码重新生成.onnx文件的。于是我就是用onnxruntime库部署PicoDet目标检测,在编写这套代码时,我发现之前编写的使用opencv部署nanodet的程序里,有百分之90的代码是可以复用的(拷贝粘贴过来),除了模型初始化的构造函数,需要重新编写。最后八卦一下在知乎上看到一个帖子,链接: 如何看待百度picodet工程中大量复制粘贴nanodet,却在各公众号和pr中只讲如何如何吊打后者? - 知乎

打开之后,可以看到帖子是关闭状态的。

12月28日,我在github发布了

使用OpenCV部署NanoDet-Plus,包含C++和Python两个版本的程序

使用ONNXRuntime部署NanoDet-Plus,包含C++和Python两个版本的程序

源码地址是:https://github.com/hpc203/nanodet-plus-opencv

2022年1月16日,我在github发布了使用ONNXRuntime部署人脸动漫化——AnimeGAN,包含C++和Python两个版本的代码实现,源码地址是

https://github.com/hpc203/AnimeGAN-onnxruntime

1月21日,我在github发布了

使用OpenCV部署YOLOX+ByteTrack目标跟踪,包含C++和Python两个版本的程序。

使用ONNXRuntime部署YOLOX+ByteTrack目标跟踪,包含C++和Python两个版本的程序。

源码地址是:

https://github.com/hpc203/bytetrack-opencv-onnxruntime
1月22日,我在github发布了使用ONNXRuntime部署U-2-Net生成人脸素描画,包含C++和Python两个版本的程序,源码地址是

https://github.com/hpc203/u2net-onnxruntime

1月28日,我在github发布了

使用OpenCV部署yolov5检测车牌和4个角点,包含C++和Python两个版本的程序

使用ONNXRuntime部署yolov5检测车牌和4个角点,包含C++和Python两个版本的程序

程序会输出车牌的水平矩形框的左上和右下顶点的坐标(x,y),车牌的4个角点的坐标(x,y)

源码地址是

https://github.com/hpc203/yolov5-detect-car_plate_corner

2月7日,我在github发布了使用ONNXRuntime部署yolov5-lite目标检测,包含C++和Python两个版本的程序,源码地址是

https://github.com/hpc203/yolov5-lite-onnxruntime

2月17日,我在github发布了

使用OpenCV部署多任务的yolov5目标检测+语义分割,包含C++和Python两个版本的程序

使用ONNXRuntime部署多任务的yolov5目标检测+语义分割,包含C++和Python两个版本的程序

源码地址是

https://github.com/hpc203/multiyolov5-opencv-onnxrun

2月19日,我在github发布了

使用OpenCV部署yolov5旋转目标检测,包含C++和Python两个版本的程序

使用ONNXRuntime部署yolov5旋转目标检测,包含C++和Python两个版本的程序

程序输出矩形框的中心点坐标(x, y),矩形框的高宽(h, w),矩形框的倾斜角,源码地址是

https://github.com/hpc203/rotate-yolov5-opencv-onnxrun

在编写这套程序的过程中,发现在python程序里,opencv的dnn模块提供了现成的计算旋转矩形框的NMS函数cv2.dnn.NMSBoxesRotated。但是在C++程序里,opencv的dnn模块提供现成的计算旋转矩形框的NMS函数NMSBoxesRotated。因此在C++程序里,需要自己编写实现计算旋转矩形框的NMS函数,在这里最棘手的地方是如何求两个旋转矩形框的交叠面积。经调研发现opencv库里有表示旋转矩形框的结构体RotatedRect,也有计算两个旋转矩形框的交叠区域和交叠面积的函数rotatedRectangleIntersection和contourArea,最终编写完成了计算旋转矩形框的NMS函数。附上一张C++程序的运行结果图

2月26日,对于https://github.com/ultralytics/yolov5 在最近更新的v6.1版本的,我在github发布了

使用OpenCV部署yolov5-v6.1目标检测,包含C++和Python两个版本的程序。

使用ONNXRuntime部署yolov5-v6.1目标检测,包含C++和Python两个版本的程序。

支持yolov5s,yolov5m,yolov5l,yolov5n,yolov5x, yolov5s6,yolov5m6,yolov5l6,yolov5n6,yolov5x6的十种结构的yolov5-v6.1

源码地址是:
https://github.com/hpc203/yolov5-v6.1-opencv-onnxrun

在这里特别讲解一下转换生成onnx文件的方法,首先下载yolov5-v6.1的源码和模型.pt文件之后,在程序主目录里,打开models/yolo.py,进入到Detect类的forward函数里,插入代码,示例截图如下:

 插入的代码片段是:

        if torch.onnx.is_in_onnx_export():
            for i in range(self.nl):  # 分别对三个输出层处理
                x[i] = self.m[i](x[i])  # conv
                bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
                x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
                y = x[i].sigmoid()
                z.append(y.view(bs, -1, self.no))
            return torch.cat(z, 1)

不过需要注意的是,y = x[i].sigmoid()这一步并不是必须的,例如在yolov5车牌检测项目里,4个关键点的x, y值没有做sigmoid()

接下来,我尝试运行python export.py --weights=yolov5s.pt --include=onnx,但是生成onnx文件失败了。这时在export.py里,我自定义了一个导出onnx文件的函数,代码片段如下:

def my_export_onnx(model, im, file, opset, train, dynamic, simplify, prefix=colorstr('ONNX:')):
    print('anchors:', model.yaml['anchors'])
    wtxt = open('class.names', 'w')
    for name in model.names:
        wtxt.write(name+'\n')
    wtxt.close()
    # YOLOv5 ONNX export
    print(im.shape)
    if not dynamic:
        f = os.path.splitext(file)[0] + '.onnx'
        torch.onnx.export(model, im, f, verbose=False, opset_version=12, input_names=['images'], output_names=['output'])
    else:
        f = os.path.splitext(file)[0] + '_dynamic.onnx'
        torch.onnx.export(model, im, f, verbose=False, opset_version=12, input_names=['images'],
                          output_names=['output'], dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'},  # shape(1,3,640,640)
                                        'output': {0: 'batch', 1: 'anchors'}  # shape(1,25200,85)
                                        })
    try:
        import cv2
        net = cv2.dnn.readNet(f)
    except:
        exit(f'export {f} failed')
    exit(f'export {f} sucess')

接下来在官方定义的export_onnx函数里插入调用这个函数,代码截图如下:

这时运行

python export.py --weights=yolov5s.pt --include=onnx --imgsz=640

python export.py --weights=yolov5s6.pt --include=onnx --imgsz=1280

就能成功生成.onnx文件,并且opencv的dnn模块能读取onnx文件做推理。

3月28日,我在github发布了使用OpenCV部署yolov5-pose目标检测+人体姿态估计,包含C++和Python两个版本的程序,源码地址是:

https://github.com/hpc203/yolov5_pose_opencv

5月3日,我在github发布了分别使用OpenCV,ONNXRuntime部署yolov5旋转目标检测,包含C++和Python两个版本的程序,程序输出矩形框的中心点坐标(x, y),矩形框的高宽(h, w),矩形框的倾斜角的余弦值和正弦值。源码地址是:

https://github.com/hpc203/rotateyolov5-opencv-onnxrun

这是读取一张航拍图片的C++程序的运行结果图

深度学习模型的检测头的示意图如下

检测框的loss示意图如下

5月4日,我在github发布了分别使用OpenCV,ONNXRuntime部署yolov5不规则四边形目标检测,包含C++和Python两个版本的程序,程序输出不规则四边形的4个角点的坐标x,y。

源码地址是:

https://github.com/hpc203/polygonyolov5-opencv-onnxrun

这是读取一张航拍图片的C++程序的运行结果图

之前在2月19日的时候发布过一套yolov5旋转目标检测的程序,但是那套程序里,每个候选框里输出的是x,y,w,h,box_score,class_score,angle_score这种形式的,其中x,y,w,h表示检测矩形框的中心点坐标,宽度和高度,box_score表示检测框的置信度,class_score表示类别置信度,假如在coco数据集上训练的,那么class_score是一个长度为80的数组,它里面第i个元素表示第i个类别的置信度。angle_score表示倾斜角度的置信度,它是一个长度为180的数组,它里面第i个元素表示检测矩形框的倾斜角等于i度的置信度,也就是说让深度学习模型来学习矩形框的倾斜角,是一个180个类别的分类问题。那么这时候选框的长度是5+80+180=265,这就使得yolov5的检测头里的最后3个1x1卷积的输出通道数也别大,这无疑会增大模型的计算量。

而在5月3日发布的这套程序里,每个候选框里输出的是x,y,w,h, cos,sin, box_score,class_score这种形式的,其中cos表示检测矩形框的倾斜角的余弦值,sin表示检测矩形框的倾斜角的正弦值,也就是说让深度学习模型来学习矩形框的倾斜角。假如在coco数据集上训练的,那么这时候选框的长度是4+2+1+80=87,很明显这时模型的计算量会减少很多。

对于不规则四边形目标检测,之前在1月28日的时候发布过yolov5检测车牌和4个角点,但那时候程序输出的车牌的水平矩形框的左上和右下顶点的坐标(x,y),车牌的4个角点的坐标(x,y)。它的深度学习模型结构就是在ultralytics的yolov5结构里加入关键点检测分支,检测矩形框的分支和关键点检测分支值相互促进,缺一不可的。而在一些项目里,需要只输出不规则四边形的4个角点的坐标,不需要输出水平矩形框。这样做不仅能减少模型的分枝数,还能减少计算量。因此在5月4日发布的程序,就是满足这种需求的,在编译这套程序时,遇到一个较为棘手的地方是计算不规则四边形的面积,计算两个不规则四边形的重叠面积。如果编写Python程序,那使用shapely库就很轻松达到目的,如果是编写C++程序,那就没有现成的库可供使用了。经过调研,使用opencv就可以实现着两个功能的,具体实现细节可以阅读源码。

在5月3日发布的旋转目标检测和5月4日发布的不规则四边形目标检测,它们的差异比较如下

看到百度PaddleDetection团队发布了全新的PP-YOLOE,以极佳的性能表现刷新业界性能榜单指标,在目标检测领域引起广泛关注。在5月25日,我在github发布了使用ONNXRuntime部署PP-YOLOE目标检测,支持PP-YOLOE-s、PP-YOLOE-m、PP-YOLOE-l、PP-YOLOE-x四种结构,包含C++和Python两个版本的程序。源码地址是:

https://github.com/hpc203/pp-yoloe-onnxrun-cpp-py

起初我是想用OpenCV部署的,但是opencv的dnn模块读取onnx文件总是失败。在netron网站里可视化onnx的结构,发现它的输入有两个,结构图如下

从结构图里可以看到第一个输入是图片,第二个输入是常数。并且它的输出也是有两个,结构图如下

从结构图里可以看出,输出是经过nms处理之后的,并且还有topK算子,显然这些算子在opencv的dnn模块里是不支持的。因此想要生成opencv的dnn模块能正常读取的onnx文件,需要对pp-yoloe的检测头(PaddleDetection/ppyoloe_head.py at release/2.4 · PaddlePaddle/PaddleDetection · GitHub)里的forward函数里的代码做一些修改 ,使得在转换生成onnx文件的时候不包含后处理。并且对paddle2onnx也作修改,使得在转换生成onnx文件的时候,只有一个输入。可是我对paddlepaddle框架不熟悉,希望有兴趣的读者可以做这一件事。

6月11日,百度的飞桨目标检测开发套件 PaddleDetection 中PP-Human里的 PP-YOLOE行人检测模块,HRNet人体骨骼关键点检测模块做了paddle2onnx,转换生成onnx文件后,使用ONNXRuntime部署,不再依赖PaddlePaddle做推理引擎,依然是包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/pp-yoloe-hrnet-human_pose_estimation

6月26日,看到最近美团视觉团队研发了一款致力于工业应用的目标检测框架YOLOv6,看到他们在昨天发布公布了训练模型。 于是我在今天编写了一套分别使用OpenCV、ONNXRuntime部署YOLOV6目标检测,包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/yolov6-opencv-onnxruntime

7月3日,在知乎上看到文章《FastestDet: 比yolo-fastest更快!更强!更简单!全新设计的超实时Anchor-free目标检测算法》,于是我就导出onnx文件,编写了使用OpenCV部署FastestDet,依然是包含C++和Python两种版本的程序。github源码地址是:

https://github.com/hpc203/FastestDet-opencv-dnn

.onnx文件很小,只有960kb,不超过1M的。适合应用到对实时性要求高的场景里。

FastestDet跟yolo-fastestv2同一个作者dog-qiuqiu创作的

7月9日,我在github发布了使用ONNXRuntime部署StyleGAN人像卡通画,包含C++和Python两个版本的程序,github源码地址是: 

https://github.com/hpc203/photo2cartoon-onnxrun-cpp-py

程序运行结果如下,左边是原图,右边是程序运行结果图

GAN有很多有趣的应用,特别是在娱乐领域,在抖音上经常会看到人脸动漫化,人脸卡通画的视频,这些功能的背后都是GAN的应用。

7月13日,我编写了分别使用OpenCV、ONNXRuntime部署YOLOV7目标检测,一共包含14个onnx模型,依然是包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/yolov7-opencv-onnxrun-cpp-py

使用opencv部署的程序,有一个待优化的问题。onnxruntime读取.onnx文件可以获得输入张量的形状信息, 但是opencv的dnn模块读取.onnx文件无法获得输入张量的形状信息,目前是根据.onnx文件的名称来解析字符串获得输入张量的高度和宽度的。在opencv的dnn模块里有个函数getLayersShapes,从函数名称上看,它是获得层的输入和输出形状信息的。但是我在程序里尝试使用这个函数来获得输入张量的形状信息,但是出错了。

7月23日,我在github发布了分别使用OpenCV、ONNXRuntime部署PP-HumanSeg肖像分割模型,包含C++和Python两个版本的程序,源码地址是:

https://github.com/hpc203/PP-HumanSeg-opencv-onnxrun

百度PaddlePaddle团队发布的PP-HumanSeg人像分割模型,功能挺强的,不得不说百度是在BAT三家公司里做技术最扎实的。但是 原始程序需要依赖PaddlePaddle框架才能运行,于是我编写了一套分别使用OpenCV、ONNXRuntime部署PP-HumanSeg肖像分割的程序,彻底摆脱对任何深度学习框架的依赖,onnx模型文件很小,只有5.9M。这是程序读取一张人像图片的运行结果,左边是原图,右边是程序运行结果图

7月23日,我在github发布了使用ONNXRuntime部署HAWP线框检测,包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/HAWP-onnxrun-cpp-py

起初打算使用opencv部署的, 可是opencv的dnn模块读取onnx文件出错了,无赖只能使用onnxruntime做部署。程序读取一张图片的运行结果如下,左边是原图,右边是程序运行结果图

HAWP是CVPR2020里的一篇文章《Holistically-Attracted Wireframe Parsing》,做直线检测的。跟传统的目标检测输出矩形框不同的是,HAWP输出检测直线的两个端点的坐标。这个可以应用到检测车道线的场景里,不过在车道拐弯的地方,车道线也是弯曲的,这时检测直线的方法就会失灵。

8月21日,我在github发布了使用ONNXRuntime部署YOLOV7人头检测,包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/yolov7-head-detect-onnxrun-cpp-py

起初想使用opencv部署的,可是opencv读取onnx文件后在forward函数出错了, 无赖只能使用onnxruntime部署。 程序读取一张图片的运行结果如下,

 这套程序对onnxruntime的版本要求较高,需要使用最新的onnxruntime做推理。 我的机器上的onnxruntime版本是1.11.1,人头检测可以应用到人流密度估计场景里。

9月19日,我在github发布了分别使用OpenCV、ONNXRuntime部署YOLOPV2目标检测+可驾驶区域分割+车道线分割,一共包含54个onnx模型,依然是包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/yolopv2-opencv-onnxrun-cpp-py

程序读取一张图片的运行结果如下,

 onnx模型,有三种,它们的不同之处在于后处理的操作不一样。其中

(1).onnx_post文件夹里的onnx文件,是把最后3个yolo层在经过decode之后,经过torch.cat(z, 1)合并成一个张量,并且还包含nms的。 因此在加载onnx_post文件夹里的onnx文件做推理之后的后处理非常简单,只需要过滤置信度低的检测框。但是经过程序运行实验,onnxruntime能加载 onnx文件做推理并且结果正常,但是opencv的dnn模块不能。

(2). onnx_split文件夹里的onnx文件,是把最后3个yolo层在经过decode之后,经过torch.cat(z, 1)合并成一个张量。 因此在加载onnx_split文件夹里的onnx文件做推理之后的后处理,包括过滤置信度低的检测框,然后执行nms去除重叠度高的检测框。 经过程序运行实验,onnxruntime能加载onnx文件做推理并且结果正常,而opencv的dnn模块能加载onnx文件,但是在forward函数报错。

(3). onnx文件夹里的onnx文件, 是不包含最后3个yolo层的。因此在加载onnx文件夹里的onnx文件做推理之后的后处理,包括 3个yolo层分别做decode,过滤置信度低的检测框,执行nms去除重叠度高的检测框,一共3个步骤。 经过程序运行实验,onnxruntime和opencv的dnn模块都能加载onnx文件做推理并且结果正常。

9月23日,我在github发布了分别使用OpenCV、ONNXRuntime部署CenterNet目标检测,包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/centernet-opencv-onnxrun-cpp-py

CenterNet是典型的"Objects as Points"式的anchor-free系列的目标检测,它是在2019年诞生的。 不过我看到在github上没有使用opencv-dnn部署centernet的源码,于是我编写了一套。程序读取一张图片的运行结果如下,

11月5日,我在github发布了使用ONNXRuntime部署YOLOV7人脸+关键点检测,包含C++和Python两个版本的程序。github源码地址是:
https://github.com/hpc203/yolov7-detect-face-onnxrun-cpp-py

程序读取一张图片的运行结果如下,

 

12月11日,我在github发布了使用ONNXRuntime部署阿里达摩院开源DAMO-YOLO目标检测,一共包含27个onnx模型,依然是C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/DAMO-YOLO-detect-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。C++程序读取一张图片的运行结果如下,

12月12日,我在github发布了使用ONNXRuntime部署百度飞桨开源PP-YOLOE-Plus目标检测,使用ONNXRuntime部署百度飞桨开源PP-YOLOE-Plus目标检测,支持s、m、l、x四种结构,包含C++和Python两个版本的程序,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/PP-YOLOE-Plus-detect-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。C++程序读取一张图片的运行结果如下,

 在编写程序时,对比此前的PP-YOLOE,不难发现,百度飞桨提出的目标检测模型,有一个共同点,就是输出是有两个张量的,一个是输入图片,另一个是输入scale_factor,它仅仅是元素都是1,长度为2的数组。还有一个共同点是,onnx模型的输出是有两个,第一个是候选框,第二个是候选框数量,并且导出的onnx模型里面包含nms后处理的,这意味着编写C++推理程序的后处理代码非常简单,只需要对检测框坐标做等比例缩放还原到原图位置和过滤低置信度的检测框。这样做虽然减少了代码量,但是会导致有些推理引擎无法加载onnx文件做推理,例如opencv的dnn模块在加载onnx文件时就出错了,报错信息如下:

很明显这是在解析NMS节点时出错了。 

12月15日,我在github发布了使用ONNXRuntime部署E2Pose人体关键点检测,一共包含20个onnx模型,依然是C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/E2Pose-detect-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。C++程序读取一张图片的运行结果如下,

2023年1月2日,我在github发布了使用ONNXRuntime部署百度飞桨开源PP-Vehicle车辆分析,包含车辆检测,识别车型和车辆颜色,车牌检测,车牌识别5个功能,不依赖PaddlePaddle就能运行。github源码地址是:

https://github.com/hpc203/PP-Vehicle-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。程序运行结果如下:

1月8日,我在github发布了使用ONNXRuntime部署百度PaddleSeg发布的实时人像抠图模型PP-MattingV2,一共包含18个onnx模型,依然是C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/PP-MattingV2-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。程序输入一张漂亮妹子图片,运行结果如下,左边是原图,右边是人像分割结果图

1月14日,我在github发布了分别使用OpenCV、ONNXRuntime部署FreeYOLO目标检测,总共包含143个onnx模型,有COCO目标检测,人脸检测,密集行人检测的三种检测功能,依然是包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/FreeYOLO-opencv-onnxrun-cpp-py

以人脸检测功能为例,程序读取一张图片:

程序运行结果如下:

 密集行人检测,程序读取一张图片:

 程序运行结果如下: 

1月18日,我在github发布了使用ONNXRuntime部署Informative-Drawings生成素描画,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/informative-drawings-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。程序的原始paper是CVPR2022里的一篇文章《Learning to generate line drawings that convey geometry and semantics》 ,链接在

https://carolineec.github.io/informative_drawings/

本套程序里提供了3个onnx模型文件,为了比较3个onnx模型文件生成的素描图的风格差异,输入一张图片

加载 opensketch_style_512x512.onnx的运行结果如下:

 加载 anime_style_512x512.onnx的运行结果如下:

 加载 contour_style_512x512.onnx的运行结果如下:

可以看到三个onnx模型文件的输出结果,是有差异的,读者可以根据需求来选择onnx模型文件 。

不过还需要注意的是,当输入图片里的人脸区域占比不大,或者包含了太多背景,这时候需要先执行人脸检测,把人脸抠出来再运行Informative-Drawings生成素描画。

1月19日,我在github发布了使用ONNXRuntime部署面向轻量实时的M-LSD直线检测,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/M-LSD-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。本套程序对应的paper是顶会AAAI2022里的一篇文章《Towards Light-weight and Real-time Line Segment Detection》。

程序里在weights文件夹里有4个onnx模型文件,每个模型文件的大小不超过6M。 经过运行程序实验比较,model_512x512_large.onnx的检测效果最好。

加载model_512x512_large.onnx文件,输入几张图片,来看看直线检测效果

输入一张含有多个扑克牌的图片

程序运行结果:

 可以看到,由于J, Q, K扑克牌的里面还有一方框,使得程序把扑克牌里面的方框的四条线给检测出来了。如果只想保留扑克牌的的4条边界线,可以调整距离阈值和置信度阈值。

输入一张含有纸质文档的图片

 程序运行结果:

 扑克牌,纸质文档,都是具有封闭四边形的形状特征的物体,类似的物体还有很多,比如银行卡,身份证,等等的。在一些项目里,例如身份证OCR,有时候输入的图片里的身份证是倾斜的,这时候需要先对身份证做矫正,然后再检测身份证里的文字并识别。

对身份证做矫正的方法有很多种,常见的有:

(1). 传统的图像处理方法:对图片做二值化,查找连通域,计算出4个顶点,做透视变换,得到水平放置的身份证

(2). 深度学习方法:类似车牌检测的思路,让神经网络学习水平矩形框和4个角点。

(3). 深度学习方法:用4个微小矩形框标注车身份证的4个角点,矩形框的中心点就是角点。如下图所示

 然后用yolov5做训练

(4). 深度学习方法:也就是本套程序里的直线检测,检测身份证的4条边界线,然后根据4条线的夹角分成两组,计算两组直线之间的交点,也就是身份证的4个角点。有了4个角点,就可以对身份证做透视变换,得到水平放置的身份证。

        综上所述,第1种方法是传统图像处理方法,要求输入图片的背景是干净的,如果输入图片里有杂乱的背景,那么二值化分割,是不能找到身份证的连通域的,这时候就需要用深度学习算法了,也就是第2到第4种方法,第2种和第3种方法,是直接检测身份证的4个角点的。如果输入的是手持身份证图片,这时的身份证通常有角点被手遮挡住了,这时的检测4个角点的算法就会失效。这时候就可以使用第4种方法了,即使有角点被遮挡,也不影响检测直线。例如,输入图片

这时M-LSD直线检测的结果图如下 

        不过第4种方法检测直线,也是有缺陷的,就是对直线很敏感,如果背景区域有直线,或者身份证里有线条,这是程序就会检测到超过4条直线的。上面的运行结果是在设置dist_threshold = 60.0时,过滤掉短线段之后的结果,dist_threshold表示线段长度阈值,只有检测线段的长度大于dist_threshold才会被保留,上面有多个扑克牌的输入图片,也可以设置大一些的阈值dist_threshold,过滤掉扑克牌内部的直线。而且在一张图里有多个身份证时,需要对检测到的直线做分组,每四条直线做1组,表示身份证的四条边界线,这时会增加程序的后处理的复杂度,第3种方法直接检测4个角点,也会面临这个问题的。有兴趣的读者可以在我编写的程序里继续添加后处理的逻辑,例如计算4条直线的交点,多条直线做分组,每四个直线做1组。此外,在输入扑克牌的图片的场景里,我有一个想法是设计一个人工智能打扑克牌的程序,包括检测检测并且识别扑克牌里的点数,强化学习算法来做决策出牌,这两大模块。

1月24日,我在github发布了使用ONNXRuntime部署3D人脸重建,人脸Mesh,人头姿势估计,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/Dense-Head-Pose-Estimation-Face-Mesh-3D-Face-Reconstruction

本套程序对应的paper是ECCV2020里的一篇文章《Towards Fast, Accurate and Stable 3D Dense Face Alignment》,在本套程序里,包括人脸检测,人脸关键点检测,人头姿势估计,人脸网格Mesh生成, 3D人脸重建,其中3D人脸重建是本套程序里的重中之重。

首先演示一下人头姿势估计,输入一张图片

 在程序里,设置mode="pose",编译运行C++程序,人头姿势估计的结果如下:

 在程序里,设置mode="sparse",编译运行C++程序,稀疏人脸关键点的结果如下:

 接下来演示人脸网格Mesh生成,输入一张图片:

 在程序里,设置mode="dense",编译运行C++程序,生成人脸网格Mesh(也可以说是密集人脸关键点检测)的结果如下:

 接下来演示最重要的3D人脸重建,它是在生成人脸网格Mesh的基础上添加了图像渲染。输入一张图片

 在程序里,设置mode="mesh",编译运行C++程序,生成3D人脸重建的结果如下:

换一张图片做验证,

 运行C++程序,生成3D人脸重建的结果如下:

可以看到效果是挺好的。

在本套程序有3个onnx模型文件,opencv的dnn模块读取RFB-320_240x320_post.onnx文件失败了,读取sparse_face_Nx3x120x120.onnx 和dense_face_Nx3x120x120.onnx是正常的。RFB-320_240x320_post.onnx文件是人脸检测模型, 有兴趣使用opencv部署的开发者,可以换用一个opencv部署的人脸检测模型到本套程序里,这样在本套 程序里,全流程都是使用opencv部署。

此外,本套程序里的3D人脸重建,可以继续优化,使程序生成精细逼真的人脸,从而绕过支付宝微信的人脸识别认证。之所以有这个想法,是因为曾经有这篇文章报道《用3D面具破解多个人脸识别系统》,当然这里面用到的技术肯定比我写的程序里复杂得多,比如用到了一些数学知识,RBF径向基函数,TPS薄板样条插值,再生核希尔伯特空间等等的。

1月27日,我在github发布了使用ONNXRuntime部署LSTR基于Transformer的端到端实时车道线检测,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/LSTR-lane-detect-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。此前YOLOP,YOLOPv2都是用语义分割算法分割出车道线的,现在这套程序里,是检测到车道线上的一些点,并且是基于Transformer的,而且onnx文件只有2.93M,还是挺有价值的。我就编写了这样一套部署程序。

输入一张图片

 运行C++程序,检测结果如下:

2月10日,我在github发布了分别使用OpenCV、ONNXRuntime部署Ultra-Fast-Lane-Detection-v2车道线检测,依然是包含C++和Python两种版本的程序。github源码地址是:

https://github.com/hpc203/Ultra-Fast-Lane-Detection-v2-opencv-onnxrun

Ultra-Fast-Lane-Detection-v2是TPAMI2022期刊里的论文,它是速度精度双SOTA的最新车道线检测算法。TPAMI的英文全称是IEEE Transactions on Pattern Analysis and Machine Intelligence,TPAMI是人工智能、模式识别、图像处理和计算机视觉领域公认最顶级国际期刊。看到它的影响因子这么高,我就编写了一套部署程序,其中opencv部署的时候需要注意opencv的版本需要是4.7以上的,dnn模块才能正常读取onnx文件做推理的。演示一下效果,输入一张图片

 运行程序的检测结果如下:

        车道线检测是目标检测领域挺有趣的一个方向,有很多种思路做车道线检测,比如常见的语义分割,旋转目标检测,直线检测,这些方法可以检测到车道线。但是它们都有优点和缺点。例如,语义分割车道线,需要每个像素点求通道方向最大值,这样的像素级运算无疑会导致大规模运算从而增加CPU消耗。旋转目标检测和直线检测的思路,虽然能检测倾斜的车道线,使得检测完全包含车道线不包含无关背景,但是在车道拐弯的地方车道线是弯曲的,这时候旋转目标检测和直线检测算法就会失灵。这时,检测车道线的一群点集的思路,就可以正确检测弯曲车道线,还有拟合曲线的思路也可以检测弯曲车道线。

        此外,关于yolov5检测车道线,有一种魔改思路,可以把yolov5检测水平矩形框改成yolov5检测直线段。思路是这样的:标准的yolov5是检测输出水平矩形框的[cx, cy, w, h]这4个信息,其中cx, cy表示水平矩形框的中心点坐标,w和h表示水平矩形框的宽度和高度。现在yolov5检测直线段,那么检测输出就改成[cx, cy, length, angle],其中cx, cy表示直线段的中心点坐标,length表示直线段的长度,angle表示直线段的倾斜角。在代码实现上,需要修改yolov5官方代码的loss函数和anchor的数值,有兴趣的读者可以尝试一下。

3月2日,我在github发布了分别使用OpenCV、ONNXRuntime部署DIS高精度图像二类分割,包含C++和Python两种版本的程序。github源码地址是:

https://github.com/hpc203/DIS-opencv-onnxrun

DIS是ECCV2022的一篇文章《Highly Accurate Dichotomous Image Segmentation》,跟BASNet和U2-Net都是出自同一个作者写的。DIS是图像二类分割 ,旨在从自然图像中分割出高精度的物体。下面是程序运行结果保存图,左边是原图,右边是分割结果图。

换一个人像图片做测试,程序运行结果如下,左边是原图,右边是分割结果图。

DIS高精度图像二类分割可以跟直线检测配合使用,因为在直线检测程序里,很容易受到背景的干扰导致误捡,这时候可以使用DIS分割出目标,排除背景的干扰,然后使用直线检测。

3月26日,我在github发布了使用ONNXRuntime部署PaddleOCR-v3, 包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/PaddleOCR-v3-onnxrun-cpp-py

本套程序里包含dbnet文件检测,文字方向分类,crnn文字识别三个模块, onnx文件大小不超过15M。onnx文件是从百度PaddlePaddle团队的PaddleOCR项目里导出的,使用onnxruntime部署, 从而摆脱对深度学习框架PaddlePaddle的依赖。起初想用opnecv部署的,可是opencv的dnn模块读取onnx文件出错了,无赖只能使用onnxruntime部署。

4月16日,我在github发布了使用ONNXRuntime部署一种用于边缘检测的轻量级密集卷积神经网络LDC,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/LDC-onnxrun-cpp-py

起初我想使用opencv做部署的,但是opencv-dnn的推理输出的特征图,跟onnxruntime的 输出特征图有差异,导致最后生成的可视化结果图片差异很大,从视觉效果看, onnxruntime的的推理结果是正确合理的。输入一张图

程序运行结果保存图:

 在一些项目里,可以把 DIS高精度图像二类分割,M-LSD直线检测和边缘检测LDC配合在一起使用。

5月12日,我在github发布了使用OpenCV部署yolov8检测人脸和关键点,包含C++和Python两个版本的程序,只依赖opencv库就可以运行。github源码地址是:

https://github.com/hpc203/yolov8-face-landmarks-opencv-dnn

在本套程序里提供的onnx文件,输出的是3个特征图,输入原图分辨率是640x640,输出的3个特征图的形状分别是1x80x80x80,1x80x40x40,1x80x20x20。以第一个输出特征图为例,1表示batchsize,80表示输出通道数no,其中no=4*reg_max+num_class+3*5,其中reg_max=16,这个是在训练配置文件里就已经决定了的,num_class=类别数量1,因为只有人脸一个类别,5表示5个人脸关键点,3表示每个关键点里的(x,y,score)向量长度。这跟以往的onnx文件的输出方式不一样,在以往的yolov5,v6,v7的onnx文件里,最后是把3个特征图permute((0,2,3,1))维度置换,然后meshgrid解码,然后view((-1,no)),然后在torch.cat(),这样就把3个特征图合并成一个,并且输出通道在最后维度。之所以不这么做是因为考虑到算法在各种芯片硬件端部署时,这些操作算子会在某些芯片硬件尚不支持的。因此开发者如果想要把这些后处理添加到onnx文件里,可以自己修改export_onnx函数,重新生成.onnx文件。又或者对本套程序做修改,对每个特征图做维度置换,这个在python程序里,numpy有transpose函数,但是在C++的opencv库里,transpose函数只能对Mat的行列做转置,如果3维以上的Mat就不支持了,因此在我的C++程序里没有做维度转置,直接访问元素的。不过我看到现在opencv4.6里已经提供了对n维的Mat做维度置换的函数,opencv官方文档介绍如下:

https://docs.opencv.org/4.6.0/d2/de8/group__core__array.html#gab1b1274b4a563be34cdfa55b8919a4ec

transposeND函数:

 在特征图的每个输出通道,长度为80的向量里,前4*reg_max个元素,计算检测框的左上角和右下角顶点坐标(xmin,ymin,xmax,ymax),长度为4*reg_max的向量分成4段,每一段向量长度等于reg_max,对这一段向量做softmax,然后把向量跟向量[0,1,2, ... , reg_max-1]做内积得到一个数值,4段向量都这么做,就得到4个数值l,t,r,b,这时网格的中心点cx,cy,接下来xmin = cx - l , ymin = cy - t , xmax = cx + r , ymax = cy + b ,这样就得到了矩形检测框的左上角和右下角顶点坐标(xmin,ymin,xmax,ymax)。这个操作在训练源码里是DFL模块,在此前的nanodet里面,已经使用过这个模块的。而且观察nanodet训练源码里的loss函数跟yolov8训练源码里的loss函数,很相似的。yolov8作者仿佛是在致敬nanodet。

输入一张图,验证一下人脸+关键点检测效果

程序运行的人脸检测和5个关键点的可视化结果如下:

此外,最近在做人脸相册时发现了一个问题,输入一张大猩猩的图片,这时会把大猩猩的脸当做人脸检测出来,结果如下:

可以看到人脸的置信度0.73,依然很高,想通过置信度阈值来过滤掉它,是行不通的。我尝试过yolov7face,yolov5face,scrfd都存在这个问题的。这时候需要添加一个人脸质量评估模型fqa,就可解决这个问题,这是人脸质量评估模型fqa输出的质量分数,如下图所示

可以看到人脸质量评估的分数只有0.11,很低。如果输入图片里的人脸是侧脸的,人脸质量评估的分数也是很低的,如下示例所示

而如果输入图片里的人脸是正脸并且清晰,这时人脸质量评估的分数就挺高的,如下图所示

通过上面的例子,可以看到清晰正脸的人脸,侧脸和动物脸,这两种情况的图片输入到人脸质量评估模型之后输出的质量分数差异很大,这时候设定置信度阈值就可以过滤低质量的人脸了。

5月19日,我在github发布了使用OpenCV部署HybridNets,同时处理车辆检测、可驾驶区域分割、车道线分割,三项视觉感知任务,包含C++和Python两种版本的程序实现。本套程序只依赖opencv库就可以运行, 彻底摆脱对任何深度学习框架的依赖。github源码地址是:

https://github.com/hpc203/hybridnets-opencv-dnn

6月14日,我在github发布了分别使用OpenCV、ONNXRuntime部署DirectMHP:用于全范围角度2D多人头部姿势估计,包含C++和Python两种版本的程序,一共有186个onnx文件。github源码地址是:

https://github.com/hpc203/DirectMHP-opencv-onnxrun

输入一张人脸图片,程序的运行结果如下:

在编写opencv-dnn推理程序时,需要注意2个问题 ,第1个问题是opencv的版本需要是4.7的,如果低于4.7,那么在加载onnx文件的时候会出错。原因是因为在DirectMHP模型里含有ScatterND算子,这个算子在opencv4.7的版本里才开始支持的,在opencv4.7发布说明里有讲到的。而且ScatterND算子在TensoRT8里支持,在TensoRT7里就不支持的。

第2个问题是,在使用opencv4.7的C++程序里,在net.forward之前需要加上net.enableWinograd(false);这一行,否则在程序运行到net.forward这一行时会报错的,原因可以查看这个issue帖子

在使用opencv-dnn部署时,加载的是resources_nopost文件夹里的onnx文件,如果加载resources_withpost文件夹里的onnx文件,程序也会报错。原因是在resources_withpost文件夹里的onnx文件里面包含了nms,这个算子在opencv-dnn里是不支持的。如果用onnxruntime做部署,就可以加载onnx文件正常运行的,这时程序的后处理就变得很十分简单,不需要执行nms的。把nms合并到onnx文件里,这样使得编写推理部署的代码量很少,代码很简洁,例如这篇文章里讲述的例子。这样做虽然能使得代码看起来很简洁,减少了代码量,但是在某些芯片硬件或者推理引擎里会由于不支持特定算子导致出错的。因此,为了能保持可移植性和通用性,尽量不要把nms算子,还有在yolov5模型最后的YOLO层里的meshgrid和decode,合并到onnx文件里面,在onnx文件里只包含基本算子:卷积层,batchnorm层,池化层,全连接层,激活层等等的。

7月28日,我在github发布了使用ONNXRuntime部署Detic检测2万1千种类别的物体,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/Detic-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。Detic是facebook发表在ECCV2022会议上的文章《Detecting Twenty-thousand Classes using Image-level Supervision》,它能检测2万1千种类别的物体,做到了识别万物的功能。

输入一张图片

 程序输出的检测结果

7月30日,我在github发布了使用ONNXRuntime部署CodeFormer图像清晰修复,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/CodeFormer-onnxrun-cpp-py

起初,我想使用opencv做部署的,但是opencv的dnn模块读取onnx文件出错, 无赖只能使用onnxruntime做部署了。CodeFormer是一个很有名的图像超分辨率模型,它是NeurIPS2022会议论文。训练源码在https://github.com/sczhou/CodeFormer,用训练源码里的图片作为输入

 onnxruntime部署的推理结果如下,可以看到效果很OK,图片很清晰了

 然而,我测试了其他几张图片,发现推理返回的结果图片很不理想。例如,输入这样的图片

 推理程序返回的结果图片如下

 换一张图片输入,

 推理程序返回的结果图片如下

 可以看到,结果很不理想。原始输入图片不是模糊图片,输入到CodeFormer,效果适得其反。上面的这两幅不模糊的图片,如果你想对图片质量做进一步优化,用传统图像处理算法,可能更合适的,例如双边滤波。

从上面的3张图片的实验,可以得出结论,并不是所有图片在经过CodeFormer之后得到的图片是清晰的。在使用CodeFormer之前,需要对输入图片做一个模糊检测,如果图片是模糊的,那就送入到CodeFormer模型里。图片模糊检测的方法,可以用传统图像处理算法做,例如拉普拉斯算子,快速傅里叶变换,也可以用深度学习算法做,例如IQA图片质量评价。

8月12日,我在github发布了使用OpenCV部署L2CS-Net人脸朝向估计,包含C++和Python两个版本的程序,只依赖opencv库就可以运行。github源码地址是:

https://github.com/hpc203/face-gaze-estimation-opencv-dnn

输入一个视频文件,检测结果保存为视频文件,显示如下

output

9月14日,我在github发布了当下热门的模糊人脸修复模型的部署,分别是:Codeformer,GFPGAN,GPEN,Restoreformer。github源码地址是:

https://github.com/hpc203/Face-restoration-models

9月18日,我在github发布了使用ONNXRuntime部署DeDoDe:"局部特征匹配:检测,不要描述——描述,不要检测"。依然是C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/DeDoDe-onnxrun-cpp-py

输入两幅建筑物图片,程序运行结果如下:

一张漂亮妹子图片和旋转30度之后的的图片,如下图中的左右所示

以此图作为输入,程序的运行结果如下:

这套程序是端到端运行的,可用在图像配准,图像拼接,视觉SLAM等等的领域中。onnx文件里包含了特征点检测和匹配,onnxruntime加载能正常运行,但是opencv-dnn加载就会出错,由此可见onnxruntime对onnx文件的支持度是最高的。

12月22日,我在github发布了使用OpenCV部署图像描述Image_Captioning,包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/image_captioning-opencv-onnxrun

输入一张图片,输出一句话描述图片里的内容。示例如下,文字写在图片左上角

本套程序里的模型使用传统CNN做图像描述的,包含encoder和decoder两个模块。不过现在火热的多模态大模Clip,连接了图像和语义文字两个领域的 这使得Clip天生就适合做图片描述的,例如从Clip衍生来的ClipCap和Blip,阿里达摩院发布的OFA,都是做图片生文的。但是模型太大,在我的个人笔记本电脑运行会占用大量内存,因此我暂时没有编写用Clip做图片生成文字的程序。

12月24日,我在github发布了使用OpenCV部署中文clip做以文搜图,给出一句话来描述想要的图片,就能从图库中搜出来符合要求的图片,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/Chinese-CLIP-opencv-onnxrun

现在相册里有这么些图片

输入一句话: 踢足球的人

这时候运行程序,把从相册中搜出来符合要求的图片列举出来,如下所示

当然clip的玩法还有好多种,例如,Zero Shot 分类,文本生成图像,输入图像生成描述文本,搜索相似图片。不过我最感兴趣的功能是,输入一句话,从相册中搜出来符合要求的图片。现在市场上已经有这个功能的软件了,例如这里 https://github.com/mazzzystar/Queryable

12月28日,我在github发布了使用OpenCV部署眼睛凝视估计,包含C++和Python两个版本的程序,只依赖opencv库就可以运行。github源码地址是:

https://github.com/hpc203/gaze-estimation-opencv-dnn

输入一张图片,

程序运行的输出可视化结果如下,红色的箭头表示眼睛注视的方向

输入一个视频文件,可视化结果如下

output2

之所以编写这套程序,是因为我看到在手机里有一项黑科技,用眼睛来控制手机屏幕,这里面就用到了眼睛注视估计,例如这篇文章里介绍的

2024年1月4日,我在github发布了使用OpenCV+onnxruntime部署开放域目标检测,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/Open-Vocabulary-Object-Detection-opencv-onnxrun

本套程序对应的文章是OWL-ViT,OWL-ViT 是谷歌于 22 年 5 月提出的一种新的 OVD(Open Vocabulary Detection)算法。传统的检测算法会收到训练时标注类别的限制,无法在推理时检测出训练集中未出现的类别;而 OVD 算法,在推理时可以检测由开放词表定义的任意新类。

演示一下效果,输入一张图片,输入一组提示词:  ["football", "a photo of person"]

它就能检测出图片里的足球和人,可视化结果图如下

1月22日,我在github发布了使用onnxruntime部署GroundingDINO开放世界目标检测,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/GroundingDINO-onnxrun

起初我想用opencv-dnn部署的,但是opencv-dnn加载onnx文件报错,因而使用onnxruntime做部署。Python程序只依赖opencv,onnxruntime,numpy这3个库就能运行,C++程序只依赖opencv,onnxruntime这2个库就能运行。做到程序的极简主义,以最少的依赖库来运行程序。

GroundingDINO是粤港澳大湾区数字经济研究院在2023年提出的一个开集目标检测模型,通过输入任意的提示词类别,检测图片里的目标,从而达到检测任何类别的物体的目的。开集目标检测简单概括就是测试集里的类别不在训练集标签里,这在现实场景里经常出现的,例如自动驾驶场景。

展示一下效果,输入一张含有人的图片,输入提示词: "face . person . hand ."

检测结果图如下,可以看到检测出了图片里的人,人脸,手

换一个场景的图片,输入提示词:  "card ."

检测结果图如下,可以看到检测出了火车票证

换一个公路交通的场景,输入提示词: "car . license ."

检测结果如下图,可以看到检测出了车辆和车牌

换一个场景的图片,输入提示词:  "phone ."

检测结果图如下,可以看到检测出了手机

再换一个场景的图片,输入提示词:  "fire ."

检测结果图如下,可以看到检测出了火焰

再换一个场景的图片,输入提示词:  "cigarette ."

检测结果图如下,可以看到检测出了香烟

再换一个场景的图片,输入提示词:  "helmet ."

检测结果图如下,可以看到检测出了安全帽

再换一个场景的图片,输入提示词:  "mask ."

检测结果图如下,可以看到检测出了口罩

从上面一系列试验可以看到GroundingDINO在多种场景里都能正确检测出目标,做到了通用化。

2月11日,春节过年在家闲着无聊,于是编写了5套使用深度学习算法做图像画质增强的程序,第1套程序是使用OpenCV部署图像修饰,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/image-retouching-opencv-dnn

模型名称简称CSRNet,下面的图片里左边是原图右边是结果图

可以看到拍摄灰暗的图片在经过图像修饰模型之后的图片是明亮的,这个功能很像photoshop里的图片美化功能。

第2套程序是使用OpenCV部署低光照图像增强,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/low-light-image-enhancement-opencv-dnn

模型名称简称PairLIE,下面的图片里左边是原图右边是结果图,可以看到图像亮度增强是明显的。

这两套程序里的onnx文件都是很小的,最大不超过2M的,非常适合在边缘端部署, 例如在手机部署。

第3套程序是使用onnxruntime部署LYT-Net轻量级低光图像增强,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/Low-Light-Image-Enhancement-onnxrun

模型名称简称LYT-Net,它是2024年发布的基于transformer的低亮度图像增强,从测试结果图看,它比上面一套程序的效果好,下面的图片里左边是原图右边是结果图

很明显,它的结果图更明亮的,并且它的onnx文件更小,不超过1M的。

第4套程序是使用onnxruntime部署夜间雾霾图像的可见度增强,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/nighttime_dehaze-onnxrun

模型名称简称nighttime_dehaze,下面的图片里左边是原图,右边是去雾霾之后的结果图,可以看到去雾霾是明显的。

第5套程序是使用onnxruntime部署C2PNet图像去雾,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/C2PNet-onnxrun

模型分为indoor室内和outdoor室外这两套场景的,起初我是用opencv-dnn做推理,可是输出结果图是全黑的。 换做使用onnxruntime做推理,输出结果图是合理的。先看一下室内场景的去雾效果,下面的图片里左边是原图,右边是去雾之后的结果图,可以看到去雾是明显的。

看一下室外场景的去雾效果,下面的图片里左边是原图,右边是去雾之后的结果图,可以看到去雾是明显的。

低光照图像增强和图像去雾都是热门研究课题,每一届的顶会论文都有做这个的。在图像画质增强领域,细分了很多领域,例如图片修饰,低光照图像增强,图片去模糊,图像去雾,图像去雨,图像去雪花,图像去阴影等等的研究工作。

2月14日,我在github发布了使用onnxruntime部署实时视频帧插值,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/Real-Time-Frame-Interpolation-onnxrun

模型名称简称rife,它是旷世研究院在ECCV2022发表的一篇文章《Real-Time Intermediate Flow Estimation for Video Frame Interpolation》,onnx模型文件只有8.79M,适合在工业界落地应用,例如insta360全景运动相机,下面展示效果,取一个抛篮球的运动场景里的若干帧图片,作为测试,如下图所示

这8张原始图片合成到gif动态图,如下所示

这8张图片,每相邻的两张图做一次帧插值,得到7张图片,加上原始图片一共15张图片,合成到新的gif动态图如下所示

这样看不出差异效果,把原始gif动态图跟帧插值之后的gif动态图在水平方向合并到一个动态图,如下图所示,左边是原始图,右边是帧插值之后的,可以看到右边的篮球的运动更为细致且连续

这说明帧插值是有效的。

3月27日,我在github发布了使用onnxruntime部署YOWOv2视频动作检测,包含C++和Python两个版本的程序。github源码地址是:https://github.com/hpc203/YOWOv2-video-action-detect-onnxrun

起初我使用opencv-dnn模块能成功加载onnx文件,但是视频动作检测模型的输入张量是5维的,opencv-dnn只支持4维输入张量,因此使用onnxruntime做推理引擎了。视频动作检测结果保存为gif动态图,效果如下

4月13日,我在github发布了使用onnxruntime部署facefusion换脸,包含C++和Python两个版本的程序。github源码地址是:

https://github.com/hpc203/facefusion-onnxrun

本套程序是对最近火热的换脸程序 https://github.com/facefusion/facefusion  的剥离。在官方程序里,定义了太多的.py文件和函数模块,模块之间的嵌套太复杂,阅读源码和设断点调试程序,像走迷宫一样,有太多的.py文件之间的相互调用, 因此我重新编写了Python程序,我的程序里只有7个.py文件,换脸程序里一共包含5个模块, 除去main.py和utils.py文件,在我的程序里每个模块对应一个.py文件,接着我编写了C++程序。我编写的Python程序只需要依赖onnxruntime,opencv,numpy就可以运行,C++程序只需要依赖onnxruntime,opencv就可以运行,之编写少量的程序文件,做到极简主义。下面展示一下效果图

输入两幅图如下,运行换脸程序把左边图片里漂亮女生的人脸替换到右边图片里的人脸上去

换脸结果图如下,可以看到人脸的确是上方的左图里的人脸

换一个样本测试,输入两幅图如下

运行换脸程序把左边图片里的人脸替换到右边图片里的人脸上去,换脸结果图如下

再换一个样本测试,输入两幅图如下

运行换脸程序把左边图片里的人脸替换到右边图片里的人脸上去,换脸结果图如下

6月9日,我在github发布了使用OpenCV部署RecRecNet广角图像畸变矫正,包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/recrecnet-opencv-dnn

RecRecNet是ICCV2023顶会的paper,它是通过薄光板样板模型和基于 DOF 的课程学习,改序修正广角图像的。thin-plate-spine薄板样条,简称TPS,它是一个很经典的图像扭曲性变模型,在图像扭曲形变问题里,经常可以看到它的身影。在TPS的文章讲述里,最主要的一段是扭曲能量最小的优化目标函数:

这个数学表达式,涉及到了数学领域的索伯列夫空间,再生核希尔比特空间。而满足这个能量最小的函数是r^2logr,其中r是两个控制点的欧氏距离。

TPS的详细原理,在网上可以找到很多文章介绍,在此就不做详细介绍了。下面看一下程序运行效果图,输入一张扭曲的图片

校正后的图和形变网格图,如下图里的左右所示

6月29日,我在github发布了使用OpenCV部署CoupledTPS,包含了肖像矫正,不规则边界的图像矩形化,旋转图像矫正,三个模型。依然是包含C++和Python两个版本的程序,github源码地址是:

https://github.com/hpc203/CoupledTPS-opencv-dnn

它的论文名称是《Semi-Supervised Coupled Thin-Plate Spline Model for Rotation Correction and Beyond》 发表在了顶级期刊 IEEE Transactions on Pattern Analysis and Machine Intelligence 上了,足以见得它的牛逼程度了。

其中的上采样upsample和grid_sample,没有把这两个算子导入到onnx文件里的, 自己独立编写了C++程序实现了,输入和输出都是4维张量的Mat。在编写C++程序时,需要注意 对于四维Mat的索引访问像素值,不能使用at函数,能使用ptr函数,,而大于四维的Mat,既不能使用at,也不能使用ptr访问元素, 只能使用指针形式访问,例如float型的Mat, float* pdata = (float*)Mat.data; 

并且在编写C++程序时发现了一个坑,在4维张量转到RGB三通道彩图时,使用ptr函数的方式给像素点赋值,最后得到的结果图跟Python程序运行的结果图不一致。 但是使用指针形式给像素点赋值,最后得到的结果图跟Python程序运行的结果图就是一致的, 看来使用指针形式访问像素值是最稳妥不出错的方式了。这个坑在C++代码里的convert4dtoimage函数里,我在函数里有写注释。

下面看一下效果图,第一个模型,肖像矫正,结果图如下,左边是输入原图,右边是结果图。仔细观察对比,可以看出右边的图片里的右下方人头没有左边图片里的宽大,并且右边图片里的建筑物线条笔直一些。

第2个模型,不规则边界的图像矩形化矫正,结果图如下,左边和右边分别是输入原图和结果图。

第3个模型,旋转图像矫正,结果图如下,左边和右边分别是输入原图和结果图。

旋转图像矫正,是论文里的重点工作,作者提供了2个训练模型的,其中有一个是半监督模型。换一张图片做测试,半监督模型的旋转图像矫正的效果,如下图所示,左边和右边分别是输入原图和结果图

再换一张测试图片,如下图所示,左边和右边分别是输入原图和结果图

  • 280
    点赞
  • 1220
    收藏
    觉得还不错? 一键收藏
  • 297
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值