提交 192d50c2 编写于 作者: M MaoXianxin

添加OpenCV技术文章

上级 52f8f4a0
**春季更新OpenCV 4.5.2发布了!**
来看看4.5.2都有哪些重要改进:
- **core模块**:增加并行后端的支持。特殊编译的OpenCV可以允许选择并行后端,并/或通过plugin动态载入。
- **imgpro模块**:增加智能剪刀功能(如下演示)。CVAT标注工具已经集成此功能,可在线体验https://cvat.org。
- **videoio模块**:改进硬件加速的视频编解码任务。从4.5.2开始,新的内置属性让用户更容易使用API
```
VideoCapture capture(filename, CAP_FFMPEG,
{
CAP_PROP_HW_ACCELERATION, VIDEO_ACCELERATION_ANY,
}
);
```
- **DNN模块:**
- - 改进TensorFlow解析错误的调试
- 改进layers和activations,支持更多模型
- - 优化NMS处理、DetectionOutput
- 修复Div with constant、MatMul、Reshape(TensorFlow)
- 支持Mish ONNX子图、NormalizeL2(ONNX)、LeakyReLU(TensorFlow)、TanH(Darknet)、SAM(Darknet)和Exp
- 支持*OpenVINO* 2021.3 release支持
- **G-API模块:**
- - Python支持
- - 引入新的Python后端:G-API可以运行Python的任意kernels作为pipeline的一部分
- 扩展G-API Python绑定的推理支持
- G-API Python绑定增加更多的图数据类型支持
- 推理支持
- - OpenVINO推理后端引入动态输入/CNN reshape功能
- OpenVINO推理后端引入异步执行支持:推理可以在多个request并行运行以增加流密度/处理量
- ONNX后端扩展对INT64/INT32数据类型的支持,OpenVINO后端扩展对INT32的支持
- ONNX后端引入cv::GFrame/cv::MediaFrame和常量支持
- 媒体支持
- - 在绘制/渲染接口引入cv::GFrame/cv::Media支持
- Streaming模式引入multi-stream输入支持以及帧同步以支持某些情况如Stereo
- 增加Y和UV操作以访问图级别cv::GFrame的NV12数据;若媒体格式不同,转换是同时的
- 运算符和核
- - 增加新操作(MorphologyEx, BoundingRect, FitLine, FindLine, FindContours, KMeans, Kalman, BackgroundSubtractor)的性能测试
- 修复PlaidML后端的RMat输入支持
- 增加Fluid AbsDiffC, AddWeighted和位操作的ARM NEON优化
- 其他静态分析和警告修复
- **文档:**
- - [GSoC]增加TF和PyTorch分类转换案例
- [GSoC]增加TF和PyTorch分割转换案例
- [GSoC]增加TF和PyTorch检测转换案例
- **社区贡献:**
- - core:增加带cuda stream标志的cuda::Stream构造函数
- highgui:Win32上的OpenGL暴露VSYNC窗口属性
- highgui:Win32上的pollKey()实现
- imgcodecs:增加PNG的Exif解析
- imgcodecs:OpenEXR压缩类型可选
- imgproc:优化connectedComponents
- videoio:Android NDK摄像头支持
- (opencv_contrib):腾讯微信QR码识别模块
- (opencv_contrib):实现cv::cuda::inRange()
- (opencv_contrib):增加Edge Drawing Library中的算法
- (opencv_contrib):Viz模块增加Python绑定
更多详细信息请参考:
https://github.com/opencv/opencv/wiki/ChangeLog#version452
\ No newline at end of file
单目标跟踪是计算机视觉中的一个基本问题。在如今深度学习时代也涌现出多种基于深度学习的单目标跟踪算法,其中基于SiamFC(双流网络)的单目标跟踪算法占据了半壁江山。DaSiamRPN就是典型的基于SiamFC的方法。
GSoC 2020为OpenCV增加了Python版本的DaSiamRPN,后来发现大家对C++版本的需求挺强烈的,于是我就为OpenCV贡献了一个C++版本的DaSiamRPN。
对于DaSiamRPN,在inference阶段,模型的主要结构如下图所示:
![](./imgs/24.jpg)
其中**黄色的CNN网络****橙色的Conv网络**是模型文件,DaSiamRPN最后的输出有两个分别为分类分支:17 x 17 x 2k 和 回归分支:17 x 17 x 4k。
在实际代码中我们将上图中的模型整合成三个模型:siamRPN,siamKernelCL1,siamKernelR1。其中siamRPN是主要模型,会在每一帧的跟踪中用到,而siamKernelCL1和siamKernelR1仅会在设定模板参数时用到。注意:图中的两个黄色的CNN是公用参数的,详细介绍请看原文[1]。
C++版本使用了与Python版本同样的逻辑和模型文件,下面简单介绍一下实现的主要逻辑。
在单目标跟踪中,首先需要设定模板参数,如以下代码所示:
```
siamRPN.setInput(blob); // blob 为输入的template
Mat out1;
siamRPN.forward(out1, "63"); // 63层的输出为文中描述的黄色CNN的输出
siamKernelCL1.setInput(out1); // 分别作为回归和分类分支的输入
siamKernelR1.setInput(out1);
Mat cls1 = siamKernelCL1.forward(); // 获取模板分类分支的特征
Mat r1 = siamKernelR1.forward(); // 获取模板回归分支的特征
std::vector<int> r1_shape = { 20, 256, 4, 4 }, cls1_shape = { 10, 256, 4, 4 };
siamRPN.setParam(siamRPN.getLayerId("65"), 0, r1.reshape(0, r1_shape)); // 将获取到的参数写入主模型
siamRPN.setParam(siamRPN.getLayerId("68"), 0, cls1.reshape(0, cls1_shape));
```
设定模板参数之后进入跟踪主循环:
```
// 主循环
for (int count = 0; ; ++count)
{
cap >> image;
// ...
float score = trackerEval(image, trackState, siamRPN); // 每一帧的跟踪计算
// 绘制图像
Rect rect = {
int(trackState.targetBox.x - int(trackState.targetBox.width / 2)),
int(trackState.targetBox.y - int(trackState.targetBox.height / 2)),
int(trackState.targetBox.width),
int(trackState.targetBox.height)
};
Mat render_image = image.clone();
rectangle(render_image, rect, Scalar(0, 255, 0), 2);
// ...
imshow(winName, render_image);
int c = waitKey(1);
if (c == 27 /*ESC*/)
break;
}
```
其中上述的`trackerEval`函数即为跟踪目标的计算,主体如下所示:
```
float trackerEval(Mat img, trackerConfig& trackState, Net& siamRPN)
{
// 第一步:确认搜索区域。跟踪算法根据前一帧中目标位置确定搜索区域
float searchSize = float((trackState.instanceSize - trackState.exemplarSize) / 2);
float pad = searchSize / scaleZ;
float sx = sz + 2 * pad;
Mat xCrop = getSubwindow(img, targetBox, (float)cvRound(sx), trackState.avgChans);
static Mat blob;
std::vector<Mat> outs;
std::vector<String> outNames;
Mat delta, score;
Mat sc, rc, penalty, pscore;
// 第二步:用siamRPN网络推理
blobFromImage(xCrop, blob, 1.0, Size(trackState.instanceSize, trackState.instanceSize), Scalar(), trackState.swapRB, false, CV_32F);
siamRPN.setInput(blob);
outNames = siamRPN.getUnconnectedOutLayersNames();
siamRPN.forward(outs, outNames);
delta = outs[0];
score = outs[1];
// score 和 delta即为文章开头结构图中的两个输出矩阵
score = score.reshape(0, { 2, trackState.anchorNum, trackState.scoreSize, trackState.scoreSize });
delta = delta.reshape(0, { 4, trackState.anchorNum, trackState.scoreSize, trackState.scoreSize });
// 第三步:后处理
// ...太长,这里省略
return score.at<float>(bestID); // 返回最好的跟踪框
}
```
运行效果:
![](./imgs/25.png)
算法存在的问题:模型训练集中包含的小物体较少,该算法在目标为小物体情况下的性能较弱,只能重新训练解决这个问题。
全部代码请参考:
https://github.com/opencv/opencv/blob/master/samples/dnn/dasiamrpn_tracker.cpp
参考资料:
1. 原始论文:https://arxiv.org/abs/1808.06048
2. 原始PyTorch实现:https://github.com/foolwood/DaSiamRPN
3. OpenCV中Python实现:https://github.com/opencv/opencv/blob/master/samples/dnn/dasiamrpn_tracker.py
\ No newline at end of file
OpenCV中的parallel_for_为用户提供了快速并行化代码的途径。
先来看一个用parallel_for_并行化矩阵相乘的简单例子。这个例子是对输入为128x128x32进行核为3x3x64的卷积操作。
直接计算的代码如下:
```
void conv3x3(const Mat& inp, const Mat& weights, Mat& out)
{
int out_cn = weights.size[0];
int inp_cn = weights.size[1];
int kh = weights.size[2], kw = weights.size[3];
int inp_h = inp.size[1], inp_w = inp.size[2];
int out_h = inp_h - kh + 1, out_w = inp_w - kw + 1;
int size[] = {out_cn, out_h, out_w};
CV_Assert( inp.size[0] == inp_cn && kw == 3 && kh == 3 );
out.create(3, size, CV_32F);
for( int oc = 0; oc < out_cn; oc++ )
for( int y = 0; y < out_h; y++ )
{
int iidx[] = { 0, y, 0 };
int widx[] = { oc, 0, 0, 0 };
int oidx[] = { oc, y, 0 };
const float* iptr0 = inp.ptr<float>(iidx); // &inp[0, y, 0]
const float* wptr0 = weights.ptr<float>(widx); // &weights[oc, 0, 0, 0]
float* optr = out.ptr<float>(oidx); // &out[oc, y, 0]
for( int x = 0; x < out_w; x++ )
{
float sum = 0.f;
for( int ic = 0; ic < inp_cn; ic++ )
{
const float* iptr = iptr0 + x + ic*(inp_h*inp_w); // &inp[ic, y, x]
const float* wptr = wptr0 + ic*(kw*kh); // &weights[oc, ic, 0, 0]
sum += iptr[0]*wptr[0] + iptr[1]*wptr[1] + iptr[2]*wptr[2] +
iptr[inp_w]*wptr[3] + iptr[inp_w+1]*wptr[4] + iptr[inp_w+2]*wptr[5] +
iptr[inp_w*2]*wptr[6] + iptr[inp_w*2+1]*wptr[7] + iptr[inp_w*2+2]*wptr[8];
}
optr[x] = sum;
}
}
}
```
使用parallel_for_进行并行计算的代码如下:
```
void conv3x3_parallel(const Mat& inp, const Mat& weights, Mat& out)
{
int out_cn = weights.size[0];
int inp_cn = weights.size[1];
int kh = weights.size[2], kw = weights.size[3];
int inp_h = inp.size[1], inp_w = inp.size[2];
int out_h = inp_h - kh + 1, out_w = inp_w - kw + 1;
int size[] = {out_cn, out_h, out_w};
CV_Assert( inp.size[0] == inp_cn && kw == 3 && kh == 3 );
out.create(3, size, CV_32F);
// 用parallel_for_按ch进行并行计算
parallel_for_(Range(0, out_cn), [&](const Range& r)
{
for( int oc = r.start; oc < r.end; oc++ )
for( int y = 0; y < out_h; y++ )
{
int iidx[] = { 0, y, 0 };
int widx[] = { oc, 0, 0, 0 };
int oidx[] = { oc, y, 0 };
const float* iptr0 = inp.ptr<float>(iidx); // &inp[0, y, 0]
const float* wptr0 = weights.ptr<float>(widx); // &weights[oc, 0, 0, 0]
float* optr = out.ptr<float>(oidx); // &out[oc, y, 0]
for( int x = 0; x < out_w; x++ )
{
float sum = 0.f;
for( int ic = 0; ic < inp_cn; ic++ )
{
const float* iptr = iptr0 + x + ic*(inp_h*inp_w); // &inp[ic, y, x]
const float* wptr = wptr0 + ic*(kw*kh); // &weights[oc, ic, 0, 0]
sum += iptr[0]*wptr[0] + iptr[1]*wptr[1] + iptr[2]*wptr[2] +
iptr[inp_w]*wptr[3] + iptr[inp_w+1]*wptr[4] + iptr[inp_w+2]*wptr[5] +
iptr[inp_w*2]*wptr[6] + iptr[inp_w*2+1]*wptr[7] + iptr[inp_w*2+2]*wptr[8];
}
optr[x] = sum;
}
}
});
}
```
来运行一下
```
int main(int argc, char** argv)
{
const int inp_h = 128, inp_w = 128, inp_cn = 32;
const int out_cn = 64;
const int kh = 3, kw = 3;
Mat inp, w, out_ref, out_curr;
gen_inp(inp_cn, inp_h, inp_w, inp);
gen_weights(out_cn, inp_cn, kh, kw, w);
conv3x3(inp, w, out_ref);
conv3x3(inp, w, out_curr);
double t = (double)getTickCount();
conv3x3(inp, w, out_curr);
t = (double)getTickCount() - t;
double t2 = (double)getTickCount();
conv3x3_parallel(inp, w, out_curr);
t2 = (double)getTickCount() - t2;
printf("conv3x3 time = %.1fms\n", t * 1000 / getTickFrequency());
printf("conv3x3_parallel time = %.1fms\n", t2 * 1000 / getTickFrequency());
return 0;
}
```
conv3x3和conv3x3_parallel在我的笔记本电脑上运行时间对比如下:
![](./imgs/7.png)
对比conv3x3和conv3x3_parallel的内部实现,基本相同,只是conv3x3_parallel代码中多了一句用parallel_for_按照输出通道的数量进行并行计算。parallel_for_根据用户计算机上的并行框架在其内部完成了代码的并行化,非常简便易用!
使用parallel_for_的一个前提条件是OpenCV需要与并行框架一起编译。OpenCV中支持以下并行框架,并按照下面的顺序选取进行处理:
- Intel TBB (第三方库,需显式启用)
- C=并行C/C++编程语言扩展 (第三方库,需显式启用)
- OpenMP (编译器集成, 需显式启用)
- APPLE GCD (苹果系统自动使用)
- Windows RT并发(Windows RT自动使用)
- Windows并发(运行时部分, Windows,MSVC++ >= 10自动使用)
- Pthreads
在刚发布的[OpenCV 4.5.2版本](http://mp.weixin.qq.com/s?__biz=MjM5NTE3NjY5MA==&mid=2247485520&idx=1&sn=fbdcafdbdecb75cb327cbc91f280960d&chksm=a6fdc1cd918a48dbcd156126656c8c6a5c29618bf57669d1d5045573b3d7cc6e911b978967e6&scene=21#wechat_redirect),增加了支持并行框架的选择。特殊编译的OpenCV可以允许选择并行后端,并/或通过plugin动态载入。如:
```
# TBB plugin
cd plugin_tbb
cmake <opencv>/modules/core/misc/plugins/parallel_tbb
cmake --build . --config Release
# OpenMP plugin
cd plugin_openmp
cmake <opencv>/modules/core/misc/plugins/parallel_openmp
cmake --build . --config Release
```
第二个条件是所要完成的计算任务是适宜并可以进行并行化的。简言之,可以分解成多个子任务并且没有内存依赖的就容易并行化。例如,对一个像素的处理并不与其他像素的处理冲突,计算机视觉相关的任务多数情况下容易进行并行化。
参考资料:
[1] https://docs.opencv.org/master/d7/dff/tutorial_how_to_use_OpenCV_parallel_for_.html
[2] https://github.com/opencv/opencv/wiki/ChangeLog
[3] https://github.com/opencv/opencv/pull/19470
\ No newline at end of file
# OpenCV_技术文章
企业技术文章精选,**觉得不错的话别忘了点个Star哈**
计划每天更新,欢迎大家一起来完善,也欢迎大家成为贡献者,有什么问题可以直接私信我或者提Issue
**有文章投稿的欢迎哈,我这边可以帮忙内推CSDN公众号等渠道**
**微信联系方式,搜索:mynamemao**
**2021-05-25新增**[用OpenCV实现条形码识别-2021-04-22](./用OpenCV实现条形码识别-2021-04-22.md)
**2021-05-25新增**[OpenCV中的并行计算parallel_for_(1)-2021-04-07](./OpenCV中的并行计算parallel_for_(1)-2021-04-07.md)
**2021-05-25新增**[OpenCV 4.5.2 发布-2021-04-06](./OpenCV 4.5.2 发布-2021-04-06.md)
**2021-05-25新增**[用OpenCV实现超轻量的NanoDet目标检测模型-2021-03-17](./用OpenCV实现超轻量的NanoDet目标检测模型-2021-03-17.md)
**2021-05-25新增**[OpenCV中单目标跟踪算法DaSiamRPN的C++实现-2021-03-09](./OpenCV中单目标跟踪算法DaSiamRPN的C++实现-2021-03-09.md)
\ No newline at end of file
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
文件已添加
作者:梁峻豪,王天麒,孙中夏 (南方科技大学计算机科学与工程系)
最近,我们为OpenCV贡献了一维条形码识别模块,代码收录在:
https://github.com/opencv/opencv_contrib/tree/master/modules/barcode。
我们收集的数据集(数据集地址:https://github.com/SUSTech-OpenCV/BarcodeTestDataset,
共250张条码图片)上进行了测试,我们的识别算法正确率达到了96%,速度为20ms每张图像。作为对比,我们也测试了ZXing在该数据集上的表现,其正确率为64.4%,速度为90ms每张图像。
注:测试速度不包含初始化以及读图时间。同时,我们的算法是C++实现,ZXing是Java实现。另外,对于用图片数据集进行的测试,ZXing99%的时间是在做彩色图到灰度图的转换。
本文将对此模块的原理和使用方式进行介绍。
# 条形码介绍
条形码是将宽度不等的多个黑条和空白,按照一定的编码规则排列,用以表达一组信息的图形标识符,如下图所示:
![](./imgs/1.png)
条码区域与其他图像相比有如下两个重要特点:第一,条码区域内的条空是平行排列的,方向趋于一致;第二,为了条码的可识读性,条码在制作时条和空之间有着较大的反射率差,从而条码区域内的灰度对比度较大,而且边缘信息丰富。
# 基于方向一致性的条码定位算法
根据条形码方向趋于一致的特点,我们可以将图像分块,通过计算每个块内**梯度方向的一致性**,来滤除那些**低一致性**的块。下图是筛选过后剩下的块:
![](./imgs/2.png)
由于包含条码区域的块**一定连续存在**的特性,我们可以通过对这些图像块再进行一个改进的**腐蚀**操作过滤掉部分背景图像块。下图是滤除部分背景图像块后剩余的块:
![](./imgs/3.png)
得到这些块之后,我们再根据每个图像块内的**平均梯度方向进行连通**。因为如果是相邻的图像块都属于同一个条码的话,那么他们的平均梯度方向也一定相同。
得到连通区域之后我们再根据条码图像的特性进行筛选,比如连通区域内的梯度大于阈值的点的比例,组成连通区域的图像块数量等。
最后,用**最小外接矩形**去拟合每个连通区域,并计算外界矩形的方向是否和连通区域内的平均梯度方向一致,过滤掉差距较大的连通区域。将平均梯度方向作为矩形的方向,并将矩形作为最终的定位框。
![](./imgs/4.png)
# 条形码解码
目前我们支持了三种类型的条码解码,它们分别是EAN13、 EAN8 和UPC-A。(下图为EAN13 条码示例)
![](./imgs/5.png)
条码的识别主要流程如下图:
![](./imgs/6.png)
其中:
1. 优化的超分辨率策略指的是对较小的条码进行**超分辨率放大**,不同大小条码做不同处理。
2. 解码算法的核心是基于条码编码方式的**向量距离计算**。因为条码的编码格式为固定的数个"条空",所以可以在约定好"条空"间隔之后。将固定的条空读取为一个向量,接下来与约定好的编码格式相匹配,取匹配程度最高的编码为结果。
3. 在解码步骤中,解码的单位为一条线,由于噪点,条空的粘连等原因,单独条码的解码结果存在较大的不确定性,因此我们加入了对**多条线的扫码**,通过对均匀分布的扫描与解码,能够将二值化过程中的一些不完美之处加以抹除。
**具体实现为**:首先在检测线上寻找起始符,寻找到起始符之后,对前半部分进行读取与解码,接着寻找中间分割符,接着对后半部分进行读取与解码,最后寻找终结符,并对整个条码进行首位生成与校验(此处以EAN13格式举例,不同格式不尽相同)。最后,每条线都会存在一个解码结果,所以对其进行投票,只将最高且总比例在有效结果50%以上的结果返回。这一部分我们基于ZXing的算法实现做了一些改进(投票等)。
4. **更换二值化和解码器**指的是在为解码成功遍历使用每种解码器和二值化尝试解码。
# 使用方式
C++
```
#include "opencv2/barcode.hpp"
#include "opencv2/imgproc.hpp"
using namespace cv;
Ptr<barcode::BarcodeDetector> bardet = makePtr<barcode::BarcodeDetector>("sr.prototxt", "sr.caffemodel"); //如果不使用超分辨率则可以不指定模型路径
Mat input = imread("your file path");
Mat corners; //返回的检测框的四个角点坐标,如果检测到N个条码,那么维度应该是[N][4][2]
std::vector<std::string> decoded_info; //返回的解码结果,如果解码失败,则为空string
std::vector<barcode::BarcodeType> decoded_format; //返回的条码类型,如果解码失败,则为BarcodeType::NONE
bool ok = bardet->detectAndDecode(input, decoded_info, decoded_format, corners);
```
Python
```
import cv2
bardet = cv2.barcode_BarcodeDetector()
img = cv2.imread("your file path")
ok, decoded_info, decoded_type, corners = bardet.detectAndDecode(img)
```
更多使用方式请参考文档:
https://docs.opencv.org/master/dc/df7/classcv_1_1barcode_1_1BarcodeDetector.html
# 参考文献
王祥敏,汪国有. 一种基于方向一致性的条码定位算法[EB/OL]. 北京:中国科技论文在线 [2015-04-22]. http://www.paper.edu.cn/releasepaper/content/201504-338.
\ No newline at end of file
本文用OpenCV部署了超轻量目标检测模型NanoDet,实现了C++和Python两个版本,并对此进行了解析。
2020年,在深度学习目标检测领域诞生了yolov4,yolov5和nanodet这些优秀的检测模型,有许多的微信公众号报道这些算法模型。深度学习目标检测方法可划分为 Anchor-base 和 Anchor-free 两大类,nanodet是一个速度超快和轻量级的移动端 Anchor-free 目标检测模型,并且它的精度不亚于yolo系列的。
nanodet通过一些论文里的trick组合起来得到了一个兼顾精度、速度和体积的检测模型。作者用到的一些trick,主要参考自:(1)参考FCOS 式的单阶段 anchor-free 目标检测模型,FCOS特点是让模型学习feature map中每个位置到检测框的四条边的距离,如下图所示。
![](./imgs/8.jpg)
(2)使用 ATSS 进行目标采样,该方法提出了自适应训练样本选择方法,该方法根据目标的统计特征(方差和均值)自动划分正训练样本和负训练样本,弥合了基于锚的探测器与无锚探测器之间的差距。(3)使用 Generalized Focal Loss 损失函数执行分类和边框回归(box regression),该函数能够去掉 FCOS 的 Centerness 分支,省去这一分支上的大量卷积,从而减少检测头的计算开销。
为了达到轻量化的目的,作者在设计网络结构时,使用 ShuffleNetV2 1.0x 作为骨干网络,他去掉了该网络的最后一层卷积,并且抽取 8、16、32 倍下采样的特征输入到 PAN 中做多尺度的特征融合。
在FPN模块里,去掉所有卷积,只保留从骨干网络特征提取后的 1x1 卷积来进行特征通道维度的对齐,上采样和下采样均使用插值来完成。与 YOLO 使用的 concat操作不同,项目作者选择将多尺度的 Feature Map 直接相加,使整个特征融合模块的计算量变得非常小。
在检测头模块里,使用了共享权重的检测头,即对 FPN 出来的多尺度 Feature Map 使用同一组卷积预测检测框,然后每一层使用一个可学习的 Scale 值作为系数,对预测出来的框进行缩放。与此同时,使用了 Group Normalization(GN)作为归一化方式.FCOS 的检测头使用了 4 个 256 通道的卷积作为一个分支,也就是说在边框回归和分类两个分支上一共有 8 个 c=256 的卷积,计算量非常大。为了将其轻量化,项目作者首先选择用深度可分离卷积替换普通卷积,并且将卷积堆叠的数量从 4 个减少为 2 组。在通道数上,将 256 维压缩至 96 维,之所以选择 96,是因为需要将通道数保持为 8 或 16 的倍数,能够享受到大部分推理框架的并行加速。
最后,项目作者借鉴了 YOLO 系列的做法,将边框回归和分类使用同一组卷积进行计算,然后 split 成两份。最后,项目作者借鉴了 YOLO 系列的做法,将边框回归和分类使用同一组卷积进行计算,然后 split 成两份,这样就组成了nanodet网络。
作者把nanodet发布在github上,项目地址: https://github.com/RangiLyu/nanodet,
下载代码和模型文件到本地,按照README文档运行一个前向推理程序。接下来,我阅读前向推理主程序demo.py文件,尝试理解在运行这个主程序时需要调用哪些函数和.py文件。在前向推理主程序demo.py文件,对一幅图片做目标检测是在Predictor类的成员函数inference里实现的,它里面包含了对输入图片做预处理preprocess,前向计算forward和后处理postprocess这三个步骤。Predictor类的定义如下图所示
![](./imgs/9.jpg)
对输入原图做预处理,预处理模块是使用Pipeline类实现,对应的代码是
![](./imgs/10.jpg)
看到这段代码时,我有些懵逼了。第一次见到functools.partial这个模块,我百度查了一下它的作用是包装函数,接着看warp_resize函数,这个函数对应的代码很复杂,里面有多个if条件判断,调用了多个自定义函数。限于篇幅,在这里展示部分截图代码,如下图所示
![](./imgs/11.jpg)
从代码不难猜测出warp_resize函数的作用是对原图做resize,于是我把warp_resize函数返回的图像做可视化并打印出图像的尺寸是高宽:320x320,可视化结果如下图所示。
![](./imgs/12.jpg)
从图中可以看到,warp_resize函数是保持原图高宽比的resize,短边剩下的部分用黑色像素填充。这种功能在ultralytics的yolov3和yolov5代码仓库里有一个letterbox函数实现的,在letterbox函数使用opencv库里的resize和copyMakeBorder就可以实现保持高宽比的resize,这种方法简洁明了。接着我对warp_resize函数和letterbox函数对同一幅图片做保持原图高宽比的resize的结果比较。可视化结果如下,从视觉上看不出它们有何差异。把这两幅图的像素矩阵做减法比较,发现它们并不等于0,也是是说它们的像素值还是有差异的。
![](./imgs/13.jpg)
接着看预处理模块Pipeline类里的第二个函数color_aug_and_norm,代码截图如下。可以看出,这个函数的作用是对输入图片的RGB三通道分别做减均值除以标准差的操作,不过在最开始对img除以255,在最后对均值和标准差分别除以255,这三次除以255是完全没必要的,因为在最后一步 (img - mean) / std,分子分母可以约掉1/255,这和img,mean,std不做除以255这一步计算,直接(img - mean) / std是等价的。
![](./imgs/14.jpg)
综上所述,在预处理模块Pipeline类包含了很多冗余的计算,图像预处理本身是一个简单问题,但是在官方代码里却把简单问题搞复杂化了。
官方代码仓库(https://github.com/RangiLyu/nanodet)
里提供了基于 ncnn 推理框架的实现,基于mnn,libtorch,openvino的实现,但是没有基于Opencv的dnn模块的实现。于是我就编写一套基于Opencv的dnn模块的实现,程序里包含Python和C++两个版本的代码。
**地址是****https://github.com/hpc203/nanodet-opncv-dnn-cpp-python**
在这套程序里,图像预处理模块沿用了ultralytics代码仓库里的letterbox函数使用opencv库里的resize和copyMakeBorder就可以实现保持高宽比的resize。此外,在网上有很多介绍nanodet网络结构的文章,但是在文章里没有对nanodet后处理模块做详细介绍的。因此,在编写这套程序时,我最关注的是nanodet的后处理模块,在nanodet网络输出之后,经过怎样的矩阵计算之后得到检测框的左上和右下两个顶点的坐标(x,y)的值的。接下来,我结合代码来理解后处理模块的运行原理。首先,原图经过预处理之后形成一个320x320的图片作为nanodet网络的输入,经过forward前向计算后会得到40x40,20x20,10x10这三种尺度的特征图(换言之就是原图缩小8倍,16倍,32倍),在程序代码里设断点调试,查看中间变量,截图如下:
![](./imgs/23.jpg)
![](./imgs/15.jpg)
从上图可以看到,经过forward前向计算后,有6个输出矩阵。第1个输出的维度是(1600,80),它对应的是40x40的特征图(拉平后是长度为1600的向量,也就是说一共有1600个像素点)里的每个像素点在coco数据集的80个类别里的每个类的置信度。第2个输出的维度是(1600,32),它对应的是40x40的特征图(拉平后是长度为1600的向量,也就是说一共有1600个像素点)里的每个像素点的检测框的预测偏移量,可以看到这个预测偏移量是一个长度为32的向量,它可以分成4份,每份向量的长度为8,接下来的第3,4,5,6个输出矩阵的意义以此类推。
前面讲到过nanodet的特点是让神经网络学习feature map中每个位置到检测框的四条边的距离,接下来我们继续在程序里设断点调试,来理解这4份长度为8的预测偏移量是如何经过一系列的矩阵计算后得到到检测框的四条边的距离。代码截图如下:
![](./imgs/16.jpg)
从上图可以看到,把形状为(1600,32)的矩阵reshape成(6400,8)的矩阵bbox_pred,其实就等同于把每一行切分成4份组成新的矩阵,然后做softmax变换,把数值归一化到0至1的区间内。继续调试接下来的一步,代码截图如下:
![](./imgs/17.jpg)
可以看到project是一个长度8的向量,元素值是从0到7。形状为(6400,8)的矩阵bbox_pred与向量project做乘法得到6400的列向量,然后reshape为(1600,4)的矩阵,接下来乘以缩放步长。这时候就得到的形状为(1600,4)的矩阵bbox_pred,它的几何意义就是40x40的特征图里的每个像素点到检测框的四条边的距离。有了这个值之后,接下来的计算就简单了,在此不做详细讲述,可以参阅我的代码。简单来说就是计算特征图的每个像素点在coco数据集里的80类里的最大score值作为类别置信度,然后把特征图的所有像素点的类别置信度从高到低排序,取前topk个像素点,然后根据上一步计算出的到检测框四条边的距离换算出检测框的左上和右下两个顶点的(x,y)值,最后做NMS去除重叠的检测框。为了更好的理解从nanodet输出特征图到最终计算出目标检测框的左上和右下顶点坐标(x,y)值的这个过程,我在草稿纸上画图演示,如下所示:
![](./imgs/18.jpg)
![](./imgs/19.jpg)
![](./imgs/20.jpg)
在编写完调用opencv的做nanodet目标检测的程序后,为了验证程序的有效性,从COCO数据集里选取几张图片测试并且与官方代码做比较,官方代码是用python编写的调用pytorch框架做目标检测的。结果如下,左边的图是官方代码的可视化结果,右边的图是opencv做nanodet目标检测的可视化结果。
![](./imgs/21.jpg)
把官方代码和我编写的代码做了一个性能比较的实验,实验环境是ubuntu系统,8G显存的gpu机器。在实验中读取一个视频文件,对视频里的每一帧做目标检测,分别运行官方的调用pytorch框架做目标检测的python代码和我编写的调用opencv做目标检测的python代码,在terminal终端输入top查看这两个程序运行时占用的内存,截图如下。第一行是opencv做nanodet目标检测程序运行时占用的内存,第二行是官方代码运行时占用的内存,可以看到使用opencv做nanodet目标检测对内存的消耗明显要低于官方代码的pytorch框架做nanodet目标检测的。
![](./imgs/22.jpg)
**Github传送门****https://github.com/hpc203/nanodet-opncv-dnn-cpp-python**
\ No newline at end of file
......@@ -2,7 +2,7 @@
企业技术文章精选,**觉得不错的话别忘了点个Star哈**
计划每天更新
计划每天更新,欢迎大家一起来完善,也欢迎大家成为贡献者,有什么问题可以直接私信我或者提Issue
**有文章投稿的欢迎哈,我这边可以帮忙内推CSDN公众号等渠道**
......@@ -44,16 +44,14 @@
往期精彩链接: [点击这里](./人人都是产品经理)
## 我爱计算机视觉
**2021-05-24-新增**[推荐几篇新出的 CVPR 2021开源论文,含图像分割、域适应、图像检索、视线估计等](./我爱计算机视觉/推荐几篇新出的 CVPR 2021开源论文,含图像分割、域适应、图像检索、视线估计等.md)
**2021-05-24-新增**[ICCV2021多模态视频分析与推理比赛参赛邀请](./我爱计算机视觉/ICCV2021多模态视频分析与推理比赛参赛邀请.md)
往期精彩链接: [点击这里](./我爱计算机视觉)
## NVIDIA英伟达
**2021-05-24-新增**[AI 艺术画廊 —— 用 AI 诠释艺术与音乐 一](./NVIDIA英伟达/AI 艺术画廊 —— 用 AI 诠释艺术与音乐 一.md)
往期精彩链接: [点击这里](./NVIDIA英伟达)
## OpenCV
**2021-05-25-新增**[用OpenCV实现条形码识别](./OpenCV/用OpenCV实现条形码识别.md)
往期精彩链接: [点击这里](./OpenCV)
ICCV2021 “多模态视频分析与推理比赛”开放注册。此次比赛提供四项分任务竞赛:
- 视频问答(Video Question Answering);
- 基于骨架的动作识别(Skeleton-based Action Recognition);
- 基于鱼眼视频的动作识别(Fisheye Video-based Action Recognition);
- 行人重识别(Person Re-Identification)。
第一个视频问答的比赛是基于最新的VideoQA数据集:SUTD-TrafficQA。这个数据集重点关注基于交通事件的视频推理,包含了6个非常有挑战性的推理任务。SUTD-TrafficQA 包含有10,080个真实场景的视频和62,535个人工标注的问题。下图是SUTD-TrafficQA数据集中的一个例子。
![](./imgs/9.jpg)
另外三个竞赛(基于骨架的动作识别,基于鱼眼视频的动作识别和行人重识别)是基于最新的,有挑战性的基于无人机视角的视频理解数据集,UAV-Human。UAV-Human重点关注从无人机视角来理解推理人类行为,包含了67,428个视频样本,6种不同的模态,4个人类行为理解任务和119个视频主题。下图包含UAV-Human数据中的多种样例。
![](./imgs/10.jpg)
下面是本次比赛的时间安排表:
| 比赛报名开放 | 2021.05.20 |
| ---------------------------- | ---------- |
| 训练数据发布 | 2021.06.09 |
| 测试数据发布 | 2021.06.13 |
| 结果提交开放以及比赛报名结束 | 2021.06.25 |
| 结果提交关闭 | 2021.07.05 |
欢迎大家参加比赛,获胜队伍将会被邀请到ICCV2021 Workshop介绍自己的工作。有关本次比赛的更多详细信息,请访问网站:
https://sutdcv.github.io/multi-modal-video-reasoning
\ No newline at end of file
CV君一直在整理 CVPR 2021 论文:
https://github.com/52CV/CVPR-2021-Papers
本文分享几篇近期值得关注的 CVPR 2021 的开源论文,包括图像到图像翻译、全景分割、语义分割、域适应、图像检索、无监督学习,以及一篇关于首次对从人们相互注视的图像/视频中学习弱监督的三维视线范式(LAEO)的研究。
相信其中有些工作肯定会让你大开眼界。
# High-Resolution Photorealistic Image Translation in Real-Time: A Laplacian Pyramid Translation Network
来自香港理工大学&阿里达摩院
作者称是首个在 4K 分辨率图像上实时实现逼真 I2IT(图像到图像翻译) 的工作。其次,在轻量级和快速的推理模型,仍然在转换能力和逼真度方面在逼真的I2IT应用上实现了可比或优越的性能。定性和定量的结果都表明,所提出的方法与先进的方法相比表现良好。
- 论文链接:https://arxiv.org/abs/2105.09188
- 项目链接:https://github.com/csjliang/LPTN
![](./imgs/1.jpg)
标签:图像到图像翻译
# PPR10K: A Large-Scale Portrait Photo Retouching Dataset with Human-Region Mask and Group-Level Consistency
来自香港理工大学&阿里达摩院
与一般的人像照片修饰任务不同,portrait photo retouching(PPR)的目的是提高一组外观扁平的人像照片的视觉质量,有其特殊的实际要求,如 human-region priority(HRP)和 group-level consistency(GLC)。HRP 要求对更多关注 human regions(人体区域),GLC 则要求将一组人像照片修饰成一致的色调。而在现有的一般照片修饰数据集上训练的模型,很难满足 PPR 的这些要求。
为了促进这一高频任务的研究,作者在本次工作中构建了一个大规模的 PPR 数据集:PPR10K,并称这是首个此类相关的数据集。PPR10K 共包含 1,681 个组和 11,161 张高质量的原始人像照片。以及人类区域的高分辨率分割掩码。每张原始照片都由三位专家进行修饰,并对每组照片进行精心调整,使其色调一致。作者定义了一套评估 PPR性能的客观指标,并提出了学习具有良好 HRP 和 GLC 性能的 PPR 模型的策略。而 PPR10K 数据集为研究自动 PPR 方法提供了一个很好的基准,实验证明,所提出的学习策略能够有效地提高修饰性能。
- 论文链接:https://arxiv.org/abs/2105.09180
- 项目链接:https://github.com/csjliang/PPR10K
![](./imgs/2.jpg)
标签:portrait photo retouching+数据集
# Railroad is not a Train: Saliency as Pseudo-pixel Supervision for Weakly Supervised Semantic Segmentation
来自延世大学&成均馆大学
现有的使用图像级弱监督的弱监督语义分割(WSSS)研究的局限性有:sparse object coverage(稀疏的对象覆盖),不准确的对象边界,以及来自非目标对象的共同出现的像素。
本次工作所提出方案:提出 Explicit Pseudo-pixel Supervision(EPS),通过结合两个弱监督从像素级反馈中学习;图像级标签通过 localization map,以及来自现成的显著检测模型提供丰富边界的 saliency map 来提供目标身份。作者进而又设计一种联合训练策略,可以充分利用两种信息之间的互补关系。所提出方法可以获得准确的物体边界,并摒弃共同出现的像素,从而显著提高 pseudo-masks 的质量。
结论:实验结果表明,所提出方法通过解决 WSSS 的关键挑战而明显优于现有方法,并在 PASCAL VOC 2012 和MS COCO 2014 数据集上取得了新的 SOTA。
- 论文链接:https://arxiv.org/abs/2105.08965
- 项目链接:https://github.com/halbielee/EPS
![](./imgs/3.jpg)
# Exemplar-Based Open-Set Panoptic Segmentation Network
来自首尔大学&Adobe Research
该文先是定义开放集全景分割(OPS)任务,并通过深入分析其固有的挑战,利用合理的假设使其可行。通过重新组织 COCO 构建一个全新的 OPS 基准,并作为 Panoptic FPN 的变种展示其基线的性能。EOPSN 是基于典范理论的开放集全景分割框架,在检测和分割未知类别的例子方面被实验证明是有效的。
- 论文链接:https://arxiv.org/abs/2105.08336
- 项目链接:https://github.com/jd730/EOPSN
- 主页地址:https://cv.snu.ac.kr/research/EOPSN/
![](./imgs/4.png)
标签:全景分割+开放集
# Learning to Relate Depth and Semantics for Unsupervised Domain Adaptation
来自苏黎世联邦理工学院
提出一种在 UDA 背景下进行语义分割和单目深度估计的新方法。亮点如下:Cross-Task Relation Layer(CTRL),为域对齐学习一个联合特征空间;该联合空间编码特定任务的特征和跨任务的依赖关系,显示对UDA有用;semantic refinement head(SRH)有助于学习任务的关联性;深度离散技术有利于学习不同语义类别和深度级别之间的独特关系;iterative self-learning(ISL)方案通过利用目标域的高置信度预测,进一步提高模型的性能。
在三个具有挑战性的 UDA 基准上,所提出的方法始终大幅超越先前的工作。
- 论文链接:https://arxiv.org/abs/2105.07830
- 项目链接:https://github.com/susaha/ctrl-uda
![](./imgs/5.jpg)
标签:域适应
# Prototype-supervised Adversarial Network for Targeted Attack of Deep Hashing
来自哈工大(深圳)&鹏城实验室&港中文&深圳市大数据研究院&电子科技大学&Koala Uran Tech
该文提出一个用于灵活的 targeted hashing attack(定向哈希攻击)的 prototype-supervised adversarial network(ProS-GAN),包括一个 PrototypeNet,一个生成器和一个判别器。实验证明,ProS-GAN 可以实现高效和卓越的攻击性能,比最先进的深度哈希的定向攻击方法具有更高的可迁移性。
- 论文链接:https://arxiv.org/abs/2105.07553
- 项目链接:https://github.com/xunguangwang/ProS-GAN
![](./imgs/6.jpg)
标签:图像检索+对抗攻击
# Weakly-Supervised Physically Unconstrained Gaze Estimation
来自英伟达&罗切斯特理工大学&Lunit Inc
本次工作所探讨的问题是从人类互动的视频中进行弱监督的视线估计,基本原理是利用人们在进行 "相互注视"(LAEO)活动时存在的与视线相关的强烈的几何约束这一发现。通过提出一种训练算法,以及为该任务特别设计的几个新的损失函数,可以从 LAEO 标签中获得可行的三维视线监督信息。在两个大规模的 CMU-Panoptic 和 AVA-LAEO 活动数据集的弱监督下,证明了半监督视线估计的准确性和对最先进物理无约束的自然 Gaze360 视线估计基准的跨域泛化的显著改善。
- 论文链接:https://arxiv.org/abs/2105.09803
- 项目链接:https://github.com/NVlabs/weakly-supervised-gaze
![](./imgs/7.jpg)
标签:CVPR 2021 Oral+视线估计
# SMURF: Self-Teaching Multi-Frame Unsupervised RAFT with Full-Image Warping
来自谷歌&Waymo
SMURF 是一种用于无监督学习光流的方法,在所有的基准上提高了 36% 到 40%(相比之前最好的方法UFlow),甚至超过了一些有监督的方法,如 PWC-Net 和 FlowNet2。该方法是将有监督光流的结构改进,即RAFT 模型,与无监督学习的新思路相结合,包括一个序列感知的自监督损失,一个处理帧外运动的技术,以及一个从多帧视频数据中有效学习的方法,同时仍然只需要两帧推理。
- 论文链接:https://arxiv.org/abs/2105.07014
- 项目链接:https://github.com/google-research/google-research/tree/master/smurf
![](./imgs/8.jpg)
标签:无监督学习
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册