<?xml version="1.0" encoding="utf-8"?>
<search>
  <entry>
    <title><![CDATA[VScode Remote Setting]]></title>
    <url>%2FVscode_Remote.html</url>
    <content type="text"><![CDATA[VScode RemoteVisual Studio Code Remote 允许开发者将容器，远程计算机，或 Windows Subsystem for Linux (WSL) 作为完整的开发环境 General Install安装Remote Development插件（在vscode下Ctrl/Command + Shift + X搜索下载安装) 配置config 设置中勾选Show Log Terminal 连接Remote进行开发 Windows坑 本地.ssh权限问题 找到.ssh文件夹。它通常位于C:\Users 右键单击.ssh文件夹，然后单击“属性”。 找到并点击“安全”标签。 然后单击“高级”。 单击“禁用继承”，单击“确定”。 将出现警告弹出窗口。单击“从此对象中删除所有继承的权限”。 接下来，单击“添加”以显示“选择用户或组”窗口。 搜索用户名，添加用户权限 Remote Extension 无法安装 settion.json 中使用git的ssh进行替换 &quot;remote.SSH.path&quot;: &quot;C:\\Program Files\\Git\\usr\\bin\\ssh.exe&quot;]]></content>
      <categories>
        <category>System</category>
      </categories>
      <tags>
        <tag>Tool</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[Good Software Engineering Practices to Follow]]></title>
    <url>%2FEmbeddes_System_Reliability.html</url>
    <content type="text"><![CDATA[Good Software Engineering Practices to Follow如果在嵌入式系统上开发自动驾驶系统。这样的系统必须安全可靠、高效地工作。对低延迟、可靠性、故障恢复等都有要求。为了生产这样的软件产品，开发过程中都应当遵循某些好的软件工程原则。 GitLab-CI Requirements Latency Critical：与非嵌入式系统不同，嵌入式系统会实时响应来自环境的数据，并采取迅速的行动。我们需要绝对的保证，所有的计算任务必须在一个固定的时间窗口内完成。在我们的代码的任何部分都不允许有无限期的延迟。 High throughput：自动驾驶汽车从环境中接收到的传感器数据量和计算量的要求都非常高，我们希望能够增加更多的数据输入和计算量，以达到更高的安全目标。所有这些数据必须由一个系统资源有限的平台处理。因此，高数据吞吐率是另一个需求。 Functional Correct：任何计算机程序都需要产生正确的计算结果才能按设计工作。然而，许多模式识别技术，如计算机视觉和机器学习，大量应用于自动驾驶汽车。对于哪些被认为是功能性正确的，哪些不是，存在一些模糊。我们需要仔细定义概念，并确保软件代码产生的预期计算结果一致和可预测。 Failure resilience：任何失败都是可能的。我们的系统必须能够处理所有可能的故障情况，以确保乘客和车辆的安全。要做到这一点，我们需要了解所有可能失败的事情，并找出如何处理它们。 How software code or hardware can fail 它没有进行正确的计算来产生预期的结果。 它计算并产生预期的结果，但结果仍然不够好。 它进行正确的计算并产生正确的结果，而且结果很好。但是计算时间太长了。 它正确地完成了所有事情，并及时地交付结果，但却消耗了太多的系统资源，在其他地方造成了问题。 Before Write Code优秀的软件工程不仅仅是编写正确有效的代码。我们的工程师需要学习如何在编写代码时提出正确的问题。他们应该问:1.特性的外部依赖关系是什么?2.函数行为的可预测性是什么?3.有什么是极端情况和未定义的行为吗?4.函数的副作用是什么？它们是阻塞还是非阻塞?他们是否泄露了数据?它们是否受到来自其他线程或其他地方的泄漏的影响?5.系统资源要求是什么?资源是否得到有效利用?6.功能的复杂性是多少?一个普通的程序员能处理它吗? Good Programming Practices SIMPLIFY: 软件工程是关于问题分解的。我们把复杂的大问题分解成简单的小问题。我们一直这样做，直到碎片变得足够小，足够简单，我们可以理解和处理。所以保持简化。 Multi-threading: 系统必须有效地执行多个任务，因此需要多线程。但是多线程编程要复杂得多。因此，我们必须小心并遵循良好的编程实践。 Synchronous vs Asynchronous: 我们的大脑更容易理解什么时候任务需要等待数据才能继续，而在等待过程中CPU周期被浪费了。因此为了提高性能，我们更喜欢异步完成任务，并使用各种同步技术来允许多个线程协调它们的任务。 Blocking vs Non-Blocking. Atomic Operations. Lock-free programming: 我们的思维过程更习惯于顺序地思考事情，所以我们倾向于写代码来顺序地做事情，当数据或资源不能立即获得时阻塞。大多数人没有意识到的是，当函数调用阻塞时，它会对后续任务产生不良影响。因此，我们应该更倾向于编写非阻塞代码。每个执行路径都应该能够在有限的时间窗口完成。此外，在数据竞争或不太可能发生竞争的最简单情况下，我们更喜欢使用原子操作来保护数据完整性，而不是使用传统的互斥锁方法。即使可以立即获得互斥锁，对互斥锁的操作也会非常昂贵。鼓励人们探索无锁编程的主题。 Critical Path: 通过关键路径的任务的延迟很重要。阻塞代码不能存在于任何关键路径中。如何确保完成所有需要的计算，而不会在关键路径中创建阻塞代码?将阻塞计算移到单独的执行线程中，然后尝试使用同步对象在线程之间同步任务。 Synchronizations Objects: 允许不同线程相互通信并保持其任务一致和同步的数据对象。同步对象包括:读写锁、信号量、互斥锁、自旋锁、原子操作。对同步对象的操作可能会阻塞和死锁，可能导致阻塞甚至死锁的极端情况的数量可以呈指数增长。因此，在使用这些同步对象时，我们必须非常小心，将它们的使用最小化到绝对必要的程度，这样极端例子的数量就不会呈指数增长，并且保持在可管理的范围内。记录并证明为什么每次使用都是必要的，为什么它们不会造成阻塞或死锁问题。尤其要避免获取多锁，这会导致死锁。当代码被更改时，再次评估并记录和证明为什么新代码不引入阻塞或死锁的新情况。 System Resource: 要尽量减少系统资源的使用，并尽量减少持有资源的时间。唯一的例外是你希望几乎所有时间都持有的资源。分配和释放资源的时间成本可能很高，因此不妨一次性分配它们，然后永久持有。但即便如此，代码的不同部分需要最小化时间持有一种资源。(通过{}来管理生命周期) Good Engineering Practices 我们必须认识到自己的极限。我们的思维过程容易出错。即使是中等复杂的逻辑，我们正确处理的能力也非常有限。因此，我们不应该通过人肉发现错误或判断一段代码是否正确。 用数据说话。我们唯一能确保方法就是测试它。我们不应该提交未经测试的代码。未经测试的代码就是未经验证的代码，因此是可能有bug的。 简化单元测试，编写大量代码进行广泛的单元测试。理想情况下，单元测试应该是非常全面的，它应该涵盖我们所能想到的任何边界情况。这里的关键是创建一个好的框架，以便人们能够轻松地编写和运行单元测试。如果太难了，人们就不会去做。 简化依赖关系。如果一个功能依赖于许多其他部分，那么就不太确定该功能是否每次都能一致工作。具有最小外部依赖关系的功能应该很容易测试，我们应该觉得为它编写单元测试很容易。如果我们不能进行单元测试，那么我们需要考虑如何简化代码。 集成的整个系统回归测试应该是测试的最后手段。我们希望单元测试能够识别并修复几乎所有的代码错误。当我们把所有的工作部分放在一起时，期望是整个系统在试验中应该工作。 强烈推荐谷歌测试框架]]></content>
      <categories>
        <category>学习</category>
      </categories>
      <tags>
        <tag>Code Style</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[NVIDIA边缘端设备刷机]]></title>
    <url>%2FNvidia-Jetson.html</url>
    <content type="text"><![CDATA[NVIDIA Jetson Products Jetson Products 刷机准备 Host主机(Ubuntu 18.04.5) NVIDIA SDK Manager Jetson设备 通过sdk-manager刷机 安装SDK Manager并且升级到最新版本 使用NVIDIA开发者账户登录 选择Jetson设备以及安装配置(Host Machine可不选) 选择离线安装(多Retry几次) 用原装usb先将host与Jetson设备连接 Xavier连接 xavier注意是连接电源灯旁边的插口; 确保连接电源并保持Xavier为关闭状态； 按住中间的按键（Force Recovery）不松手； 按住左边的电源（Power）不松手； 过一两秒，同时松手。 TX2连接 USB 连接 接通电源，按下 power 键，开机 刚开机时，迅速 按住 Recovery 键不放开，然后按一下 Reset 键，过 2s 以后再松开 Recovery 键。此时开发板应该处于强制恢复模式。 lsusb可以查看到NVidia Corp; 安装完第一部分后Jetson设备会开机，此时进行系统配置 输入系统配置的用户名以及密码 完成剩余的安装(出现问题的话按照终端的Log输出在设备系统里进行修复) ifconfig 查看ssh地址 查看Jetson设备性能123456## systemtegrastats## thrid party jetson monitor scriptsudo -H pip install -U jetson-statsjtop 工作模式TX2:支持FP32, FP16推理1234# 查看当前工作模式sudo nvpmodel -q verbose# 如果想切换到模式0sudo nvpmodel -m 0 Xavier:支持FP32, FP16， INT8推理1234# 查看当前工作模式sudo nvpmodel --query# 如果想切换到模式0sudo nvpmodel -m 0 环境配置(个人习惯)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970# 生成ssh keyssh-keygen -t rsa -C ”yourEmail@example.com”cat ~/.ssh/id_rsa.pub# 删除python2sudo apt-get remove python2.7sudo apt-get remove --auto-remove python2.7# 安装pipsudo apt-get install python3-pip# 默认python为python3sudo ln -s /usr/bin/python3.6 /usr/bin/python# 默认pip为pip3sudo ln -s /usr/bin/pip3 /usr/bin/pip# 升级pip并切换源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -Upip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple# Pytorch aarch64# https://forums.developer.nvidia.com/t/pytorch-for-jetson-version-1-7-0-now-available/72048# 下载对应pytorch进行安装sudo apt-get install python3-pip libopenblas-base libopenmpi-dev pip install Cythonpip install numpy torch-1.x.x-cp36-cp36m-linux_aarch64.whl# 编译对应Pytorch版本的torchvisionPyTorch v1.0 - torchvision v0.2.2PyTorch v1.1 - torchvision v0.3.0PyTorch v1.2 - torchvision v0.4.0PyTorch v1.3 - torchvision v0.4.2PyTorch v1.4 - torchvision v0.5.0PyTorch v1.5 - torchvision v0.6.0PyTorch v1.6 - torchvision v0.7.0PyTorch v1.7 - torchvision v0.8.1sudo apt-get install libjpeg-dev zlib1g-dev libpython3-dev libavcodec-dev libavformat-dev libswscale-dev# 编译其余的库# 1. git clone 对应库# 2. python setup.py bdist_wheel# 3. 在dist中找到编译好的whl文件进行安装# 升级cmakesudo apt-get install software-properties-commonsudo add-apt-repository ppa:george-edison55/cmake-3.xsudo apt-get updateapt-get install libprotobuf-dev protobuf-compiler# ONNX-TENSORRTgit clone https://github.com.cnpmjs.org/onnx/onnx-tensorrt.gitcd onnx-tensorrtgit checkout 7.1rm -r third_party/onnxcd third_partygit clone https://github.com.cnpmjs.org/onnx/onnx.gitcd onnxgit checout 553df22cd ../../mkdir buildcd buildcmake .. -DTENSORRT_ROOT=/usr/src/tensorrtexport LD_LIBRARY_PATH=/home/nvidia/workspace/thrid_party/whl/pytorch/onnx-tensorrt/build:$LD_LIBRARY_PATHsudo sh -c "echo '/usr/local/cuda/lib64' &gt; /etc/ld.so.conf.d/cuda.conf"sudo ldconfigsudo makesudo make install# fix Illegal instruction (core dumped) -&gt; numpy 1.19.5 issuepip install numpy==1.19.4]]></content>
      <categories>
        <category>TensorRT</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[ShangHai House Knowledge]]></title>
    <url>%2FShangHai_House.html</url>
    <content type="text"><![CDATA[1.1 城市定位上海的城市性质 上海是我国的直辖市之一 长江三角洲世界级城市群的核心城市 国际经济、金融、贸易、航运、科技创新中心和文化大都市 国家历史文化名城 并将建设成为卓越的全球城市、具有世界影响力的社会主义现代化国际大都市 上海的城市目标愿景“卓越的全球城市，令人向往的创新之城、人文之城、生态之城，具有世界影响力的社会主义现代化国际大都市”，通过“创新、人文、生态”三个分目标，深化了“卓越的全球城市”的内涵，充分体现创新对未来城市竞争力的核心引领，突显对绿色可持续发展的核心支撑，彰显以人为本理念和城市特色魅力。 1.2 人口目标“上海2035”要求，缓解人口快速增长与资源环境紧约束之间的矛盾，严格控制常住人口规模，至2035年常住人口控制在2500万人左右。而在2015年，上海常住人口规模为2415万人。 1.3 二手房术语 满五唯一在二手房交易中，“满五”“唯一”是两个非常重要的概念，二手房是否“满五”“唯一”直接影响交易营业税及个人所得税是否能够减免。“满五”是指购房者拥有房屋的时间满5年。 具体判断方法如下： 商品房与经济适用房：均以契税票填发日期或房产证登记日期为准。 已购公房:一看房产证填发日期；二看原始购房合同签署日期；三看第一笔购房房款的银钱收据日期。以上三个时间只需一个满5年就算“满五”。 继承的房产以原房产证为准计算是否满5年。“唯一”是指购房者是家庭唯一住房。购买普通住宅若同时满足“满五”“唯一”两个条件，可以免征营业税即个人所得税，从而为购房者节省下一笔不少的费用，因此，“满五且唯一” 的二手房在市场上十分畅销。 =实例举证= 假设小明购买一套面积为90平米的二手房，合同价为300万，原值为150万，首套房，税费如何缴纳分为如下几种情况。 情况1：小明买的是满五唯一的普通住宅，税费为：契税: 300万x1%=3万共计: 3万 情况2：小明买的满五唯一的非普通住宅，税费为：契税: 300万x1%=3万增值税及附加: (300万-150万) / (1+5%) x 5.65% = 8.07万共计: 11.07万 情况3：小明购买的是未满2年的普通住宅、非普通住宅，税费为：契税: 300万x1%=3万增值税及附加: 300万 / (1+5%) x 5.65% = 16万个人所得税: (300万-150万) x 20% = 30万共计: 49万 情况4：小明购买的是满2年未满5年的普通住宅，税费为：契税: 300万x1%=3万个人所得税: (300万-150万) x 20% = 30万共计: 33万 情况5：小明购买的是满2年未满5年的非普通住宅，税费为：契税: 300万x1%=3万增值税及附加: (300万-150万) / (1+5%) x 5.65% = 8.07万个人所得税: (300万-150万) x 20% = 30万共计: 41.07万]]></content>
      <categories>
        <category>My Life</category>
      </categories>
      <tags>
        <tag>ShangHai</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[Roofline Model 分析深度学习模型性能]]></title>
    <url>%2FRoofline.html</url>
    <content type="text"><![CDATA[深度学习模型性能分析在真实世界中，任何模型（例如 VGG / MobileNet 等）都必须依赖于具体的计算平台（例如CPU / GPU / ASIC 等）才能展现自己的实力。此时，模型和计算平台的”默契程度”会决定模型的实际表现。Roofline Model 提出了使用 Operational Intensity（计算强度）进行定量分析的方法，并给出了模型在计算平台上所能达到理论计算性能上限公式。 1. 计算平台的两个指标：算力 $\pi$ 与带宽 $\beta$ 算力 $\pi$ :也称为计算平台的性能上限，指的是一个计算平台倾尽全力每秒钟所能完成的浮点运算数。单位是 FLOPS or FLOP/s。 带宽 $\beta$ ：也即计算平台的带宽上限，指的是一个计算平台倾尽全力每秒所能完成的内存交换量。单位是 Byte/s。 计算强度上限 $I_{max}$ ：两个指标相除即可得到计算平台的计算强度上限。它描述的是在这个计算平台上，单位内存交换最多用来进行多少次计算。单位是 FLOPs/Byte。 注：这里所说的“内存”是广义上的内存。对于CPU计算平台而言指的就是真正的内存；而对于GPU计算平台指的则是显存。 2. 模型的两个指标：计算量 与 访存量 计算量：指的是输入单个样本（对于CNN而言就是一张图像），模型进行一次完整的前向传播所发生的浮点运算个数，也即模型的时间复杂度。单位是 #FLOP or FLOPs。其中卷积层的计算量公式如下: $M$: 每个卷积核输出特征图(Feature Map)的边长 $K$: 每个卷积核(Kernel)的边长 $C_{in}$: 每个卷积核的通道数，也即输入通道数，也即上一层的输出通道数。 $C_{out}$: 本卷积层具有的卷积核个数，也即输出通道数。 其中，输出特征图尺寸本身又由输入矩阵尺寸 $X$ 、卷积核尺寸 $K$ 、$Padding$、 $Stride$ 这四个参数所决定，表示如下： 注1：为了简化表达式中的变量个数，这里统一假设输入和卷积核的形状都是正方形。 注2：严格来讲每层应该还包含 1 个 Bias 参数，这里为了简洁就省略了。 访存量：指的是输入单个样本，模型完成一次前向传播过程中所发生的内存交换总量，也即模型的空间复杂度。在理想情况下（即不考虑片上缓存），模型的访存量就是模型各层权重参数的内存占用（Kernel Memory）与每层所输出的特征图的内存占用（Output Memory）之和。单位是Byte。由于数据类型通常为float32 ，因此需要乘以四。 模型的计算强度 $I$ ：由计算量除以访存量就可以得到模型的计算强度，它表示此模型在计算过程中，每Byte内存交换到底用于进行多少次浮点运算。单位是FLOPs/Byte。可以看到，模计算强度越大，其内存使用效率越高。 模型的理论性能 $P$ ：我们最关心的指标，即模型在计算平台上所能达到的每秒浮点运算次数（理论值）。单位是 FLOPS or FLOP/s。下面我们即将介绍的 Roof-line Model 给出的就是计算这个指标的方法。 3. Roof-line Model其实 Roof-line Model 说的是很简单的一件事：模型在一个计算平台的限制下，到底能达到多快的浮点计算速度。更具体的来说，Roof-line Model 解决的，是计算量为A且访存量为B的模型在算力为C且带宽为D的计算平台所能达到的理论性能上限E是多少这个问题。 算力决定“屋顶”的高度（绿色线段） 带宽决定“房檐”的斜率（红色线段） 计算瓶颈区域 Compute-Bound不管模型的计算强度 $I$ 有多大，它的理论性能 $P$ 最大只能等于计算平台的算力 $\pi$ 。当模型的计算强度 $I$ 大于计算平台的计算强度上限 $I_{max}$ 时，模型在当前计算平台处于 Compute-Bound状态，即模型的理论性能 $P$ 受到计算平台算力 $\pi$ 的限制，无法与计算强度 $I$ 成正比。但这其实并不是一件坏事，因为从充分利用计算平台算力的角度上看，此时模型已经完全利用了计算平台的全部算力。可见，计算平台的算力 $\pi$ 越高，模型进入计算瓶颈区域后的理论性能 $P$ 也就越大。 带宽瓶颈区域 Memory-Bound当模型的计算强度 $I$ 小于计算平台的计算强度上限 $I_{max}$ 时，由于此时模型位于“房檐”区间，因此模型理论性能 $P$ 的大小完全由计算平台的带宽上限 $\beta$ （房檐的斜率）以及模型自身的计算强度 $I$ 所决定，因此这时候就称模型处于 Memory-Bound 状态。可见，在模型处于带宽瓶颈区间的前提下，计算平台的带宽 $\beta$ 越大（房檐越陡），或者模型的计算强度 $I$ 越大，模型的理论性能 $P$ 可呈线性增长。 3. 模型实例分析 VGG16从上表可以看到，仅包含一次前向传播的计算量就达到了 15 GFLOPs，访存量则是 Kernel Mem 和 Output Mem 之和再乘以四(float)，大约是 600MB。因此 VGG16 的计算强度就是 25 FLOPs/Byte。在检测中去除FC层的话(参数量占整个模型80%)那么它的实际计算强度可以再提升四倍以上。 MobileNetMobileNet 是以轻量著称的小网络代表。相比简单而庞大的 VGG16 结构，MobileNet 的网络更为细长，加入了大量的BN，每一层都通过 DW + PW 的方式降低了计算量，同时也付出了计算效率低的代价。MobileNet 的计算量只有大约 0.5 GFLOPs（VGG16 则是 15 GFLOPs），其访存量也只有 74 MB（VGG16 则是约 600 MB）。这样看上去确实轻量了很多，但是由于计算量和访存量都下降了，而且相比之下计算量下降的更厉害，因此 MobileNet 的计算强度只有 7 FLOPs/Byte。 两个模型在 1080Ti 上的对比1080Ti 的算力 $\pi=11.3$TFLOP/s1080Ti 的带宽 $\beta=484$GB/s因此 1080Ti 计算平台的最大计算强度 $I_{max}=24$由上图可以非常清晰的看到: MobileNet 处于 Memory-Bound 区域。在 1080Ti 上的理论性能只有 3.3 TFLOP/s。 VGG16 刚好迈入 Compute-Bound 区域。完全利用 1080Ti 的全部算力。 Roofline 模型讲的是程序在计算平台的算力和带宽这两个指标限制下，所能达到的理论性能上界，而不是实际达到的性能，因为实际计算过程中还有除算力和带宽之外的其他重要因素，它们也会影响模型的实际性能，这是 Roofline Model 未考虑到的。例如矩阵乘法，会因为 cache 大小的限制、GEMM 实现的优劣等其他限制，导致你几乎无法达到 Roofline 模型所定义的边界（屋顶）。]]></content>
      <categories>
        <category>学习</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[使用Docker部署GitLab-CI-Runner]]></title>
    <url>%2FGitlab-Runner.html</url>
    <content type="text"><![CDATA[GitLab-CIGitLab CI是为GitLab提供持续集成服务的一整套系统。在GitLab8.0以后的版本是默认集成了GitLab-CI并且默认启用的。使用GitLab CI需要在仓库跟目录创建一个gitlab-ci.yml的文件，它用来指定持续集成需要运行的环境，以及要执行的脚本。还需要设置一个gitlab-runner，当有代码push变更的时候，gitlab-runner会自动开始pipeline，并在gitlab上显示持续集成的结果。 GitLab-Runner执行情况如下： 本地代码改动 变动代码推送到GitLab上 GitLab 将这个变动通知GitLab-CI GitLab-CI找出这个工程相关联的gitlab-runner gitlab-runner把代码更新到本地 根据预设置的条件配置好环境 根据预定义的脚本(一般是.gitlab-ci.yml)执行 把执行结果通知给GitLab GitLab显示最终执行的结果 gitlab-runner可以在不同的主机上部署，也可以在同一个主机上设置多个gitlab-runner ,还可以根据不同的环境设置不同的环境，比如我们需要区分研发环境，测试环境以及正式环境等。 GitLab-RunnerGitlab CI 对 Docker 的支持非常好，文档之类的东西非常全面，建议直接阅读官方文档即可： Run GitLab Runner in a containerDocker section of Registering Runners 创建 Runner 容器创建一个 gitlab-runner-docker 目录，然后新建一个 docker-compose.yml 文件，内容如下：123456789version: "3"services: app: image: gitlab/gitlab-runner container_name: gitlab-runner-docker restart: always volumes: - ./config:/etc/gitlab-runner - /var/run/docker.sock:/var/run/docker.sock 在 gitlab-runner-docker 目录下执行 up --build -d``` 命令，```docker ps -a``` 即可看见刚才创建的容器，同时目录下会生成一个 config 目录，用于存放 Runner 的配置文件1234567891011### 注册 Runner执行命令 ```docker exec -it gitlab-runner-docker gitlab-runner register``` （或者进入容器内部执行 ```gitlab-runner register``` 也可以）接下来会看到一系列的输入项，一步一步输入即可。* 输入 GitLab 的地址。如果是使用的官方的 GitLab，就输入 https://gitlab.com，自建的 GitLab 就输入自己的IP或域名即可。```shPlease enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )https://gitlab.com 输入 Token 来注册 Runner在 GitLab 的仓库 Setting -&gt; CI/CD 设置页面中，展开 Runners 部分，即可看到生成的 Token，复制粘贴即可。 12Please enter the gitlab-ci token for this runnerxxxToken 输入一段 Runner 的描述，之后可以在 GitLab 管理页面进行修改。 12Please enter the gitlab-ci description for this runner[hostame] my-runner 输入 Runner 关联的标签，之后可以在 GitLab 管理页面进行修改 12Please enter the gitlab-ci tags for this runner (comma separated):my-tag,another-tag Enter the Runner executorGitLab Runner 内置了多种 executor，不同类型的 executor 的区别，可以参考文档：Executors。这里我们填写 docker。 12Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell: docker If you chose Docker as your executor, you’ll be asked for the default image to be used for projects that do not define one in .gitlab-ci.yml如果选择了 executor 为 docker，那么就需要选择一个默认的镜像。 官方文档的例子中，默认镜像使用的是 alpine:latest，是个精简版的 linux 镜像，也就意味着啥工具都没有，每次执行构建任务前先装 git 等一系列工具显然不合理。 推荐使用 tico/docker 作为默认镜像，这个镜像在官方 docker 镜像的基础上，加入了 curl、php、git 等等一系列常用的工具。 也可以使用本地的镜像，这样免去每次配置的时间(提前准备好一个配置好测试环境的镜像) 配置 Runner进入 config 目录，会发现一个 config.toml 文件，里面是 gitlab-runner 相关的配置信息。1234567891011121314151617181920212223concurrent = 1check_interval = 0[session_server] session_timeout = 1800[[runners]] name = "home-runner-docker" url = "https://gitlab.com" token = "xxxxxxxxxxxxxxx" executor = "docker" [runners.docker] tls_verify = false image = "tico/docker" privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0 [runners.cache] [runners.cache.s3] [runners.cache.gcs] 当执行构建任务时如果出现以下报错，请将上面的配置文件中的 privileged 的值改为 true。 需要使用本地镜像的话, 添加pull_policy到配置文件中123456789101112131415161718192021222324concurrent = 1check_interval = 0[session_server] session_timeout = 1800[[runners]] name = "home-runner-docker" url = "https://gitlab.com" token = "xxxxxxxxxxxxxxx" executor = "docker" [runners.docker] tls_verify = false image = "tico/docker" privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] **pull_policy = "if-not-present"** shm_size = 0 [runners.cache] [runners.cache.s3] [runners.cache.gcs] 使用以博客为例，首先是一个专门用来执行构建任务 Runner 容器。 当接收到博客的构建任务时，创建一个基于 tico/docker 镜像的容器，然后再在这个容器中执行构建脚本。 而构建脚本中，又调用 docker 命令创建了一个 nodejs 容器来进行打包编译，然后再将 build 之后生成的静态文件移入一个 nginx 镜像，作为最终部署使用的镜像并上传到阿里云容器服务。 接着 GitLab CI 会将部署任务发送至另一个专门用来执行部署任务的 Runner 容器，这个 Runner 容器会 ssh 登录上目标服务器，拉取最新的镜像并运行。 .gitlab-ci.yml 例子: 12345678910111213141516171819202122232425262728293031323334353637383940414243stages: - build - test - release# 构建build: stage: build tags: - xs1 - docker image: tico/docker:latest services: - docker:dind variables: DOCKER_HOST: tcp://docker:2375/ DOCKER_DRIVER: overlay2 DOCKER_REPO: $docker_repo before_script: - echo 'before_script' - docker login --username=$docker_user -p $docker_pwd $docker_host script: - sh ./ci/build/build.sh only: - master# 发布release: stage: release tags: - xs1 - ssh variables: DOCKER_REPO: $docker_repo PROJECT_NAME: $project_name SERVER: $deploy_server PORT: $port script: - sh ./ci/release/release.sh environment: name: production only: - master 结果:]]></content>
      <categories>
        <category>System</category>
      </categories>
      <tags>
        <tag>Tool</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[无权二分图的匹配]]></title>
    <url>%2Fbipartite_graph.html</url>
    <content type="text"><![CDATA[二分图:又称作二部图，是图论中的一种特殊模型。 设$G=(V,E)$是一个无向图，如果顶点$V$可分割为两个互不相交的子集$(A,B)$，并且图中的每条边$（i，j）$所关联的两个顶点$i$和$j$分别属于这两个不同的顶点集$(i \in A,j \in B)$，则称图$G$为一个二分图。 二分图的一个等价定义是：不含有「含奇数条边的环」的图。图 1 是一个二分图。为了清晰，我们以后都把它画成图 2 的形式。 颜色法判断是否为二分图12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364#include &lt;iostream&gt;#include &lt;cstring&gt;using namespace std;const int N = 1e6 + 10, M = 2 * N;int h[N], e[M], ne[M], idx;int color[N];int n, m;// 头插法void add(int a, int b)&#123; e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;&#125;bool dfs(int u, int c)&#123; color[u] = c; for (int i = h[u]; i != -1; i = ne[i]) &#123; int j = e[i]; if (!color[j]) &#123; if (!dfs(j, 3 - c)) return false; // 给下一个点相反的颜色 &#125; else if (color[j] == c) return false; // 相邻点不能为同一颜色 &#125; return true;&#125;int main()&#123; memset(h, -1, sizeof h); // init cin &gt;&gt; n &gt;&gt; m; for (int i = 0; i &lt; m; i ++) &#123; int a, b; cin &gt;&gt; a &gt;&gt; b; add(a, b), add(b, a); // 头插法连接图 &#125; bool flag = true; // 是否为二分图 for (int i = 1; i &lt;= n; i ++) // 数据从1开始 if (!color[i]) &#123; if (!dfs(i, 1)) &#123; flag = false; break; &#125; &#125; if (flag) puts("Yes"); else puts("No"); return 0;&#125; 匹配:在图论中，一个「匹配」（matching）是一个边的集合，其中任意两条边都没有公共顶点。例如，图 3、图 4 中红色的边就是图 2 的匹配。 我们定义匹配点、匹配边、未匹配点、非匹配边，它们的含义非常显然。例如图 3 中 1、4、5、7 为匹配点，其他顶点为未匹配点；1-5、4-7为匹配边，其他边为非匹配边。 最大匹配:一个图所有匹配中，所含匹配边数最多的匹配，称为这个图的最大匹配。图 4 是一个最大匹配，它包含 4 条匹配边。 完美匹配：如果一个图的某个匹配中，所有的顶点都是匹配点，那么它就是一个完美匹配。图 4 是一个完美匹配。显然，完美匹配一定是最大匹配（完美匹配的任何一个点都已经匹配，添加一条新的匹配边一定会与已有的匹配边冲突）。但并非每个图都存在完美匹配。 求解最大匹配问题的一个算法是匈牙利算法，下面讲的概念都为这个算法服务。交替路：从一个未匹配点出发，依次经过非匹配边、匹配边、非匹配边… 形成的路径叫交替路。9 -&gt; 4 -&gt; 8 -&gt; 1 -&gt; 6 增广路：从一个未匹配点出发，走交替路，如果途径另一个未匹配点（出发的点不算），则这条交替路称为增广路（agumenting path）。9 -&gt; 4 -&gt; 8 -&gt; 1 -&gt; 6 -&gt; 2 增广路有一个重要特点：非匹配边比匹配边多一条。因此，研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边，所以这样做不会破坏匹配的性质。交换后，图中的匹配边数目比原来多了 1 条。 我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时，达到最大匹配（这是增广路定理）。匈牙利算法正是这么做的。 匈牙利树一般由 BFS 构造（类似于 BFS 树）。从一个未匹配点出发运行 BFS（唯一的限制是，必须走交替路），直到不能再扩展为止。例如，由图 7，可以得到如图 8 的一棵 BFS 树： 这棵树存在一个叶子节点为非匹配点（7 号），但是匈牙利树要求所有叶子节点均为匹配点，因此这不是一棵匈牙利树。如果原图中根本不含 7 号节点，那么从 2 号节点出发就会得到一棵匈牙利树。这种情况如图 9 所示（顺便说一句，图 8 中根节点 2 到非匹配叶子节点 7 显然是一条增广路，沿这条增广路扩充后将得到一个完美匹配）。 Python12345678910111213141516171819202122232425262728293031323334353637383940414243444546M=[]class DFS_hungary(): def __init__(self, nx, ny, edge, cx, cy, visited): self.nx, self.ny=nx, ny self.edge = edge self.cx = cx self.cy = cy self.visited = visited def max_match(self): res=0 for i in self.nx: if self.cx[i]==-1: for key in self.ny: # 将visited置0表示未访问过 self.visited[key]=0 res+=self.path(i) return res def path(self, u): for v in self.ny: if self.edge[u][v] and (not self.visited[v]): self.visited[v]=1 if self.cy[v]==-1: self.cx[u] = v self.cy[v] = u M.append((u,v)) return 1 else: M.remove((self.cy[v], v)) if self.path(self.cy[v]): self.cx[u] = v self.cy[v] = u M.append((u, v)) return 1 return 0if __name__ == '__main__': nx, ny = ['A', 'B', 'C', 'D'], ['E', 'F', 'G', 'H'] edge = &#123;'A':&#123;'E': 1, 'F': 0, 'G': 1, 'H':0&#125;, 'B':&#123;'E': 0, 'F': 1, 'G': 0, 'H':1&#125;, 'C':&#123;'E': 1, 'F': 0, 'G': 0, 'H':1&#125;, 'D':&#123;'E': 0, 'F': 0, 'G': 1, 'H':0&#125;&#125; # 1 表示可以匹配， 0 表示不能匹配 cx, cy = &#123;'A':-1,'B':-1,'C':-1,'D':-1&#125;, &#123;'E':-1,'F':-1,'G':-1,'H':-1&#125; visited = &#123;'E': 0, 'F': 0, 'G': 0,'H':0&#125; print(DFS_hungary(nx, ny, edge, cx, cy, visited).max_match()) C++1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162#include &lt;iostream&gt;#include &lt;cstring&gt;using namespace std;const int N = 510, M = 100010;int h[N], e[M], ne[M], idx;int match[N];bool st[N];int n1, n2, m;void add(int a, int b)&#123; e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;&#125;bool find(int u)&#123; for (int i = h[u]; i != -1; i = ne[i]) &#123; int j = e[i]; if (!st[j]) &#123; st[j] = true; if (match[j] == 0 || find(match[j])) // 当前匹配到的另一边未匹配，或者已经匹配的人有其他匹配选择 &#123; match[j] = u; //更新匹配给u return true; &#125; &#125; &#125; return false;&#125;int main()&#123; memset(h, -1, sizeof h); cin &gt;&gt; n1 &gt;&gt; n2 &gt;&gt; m; while(m --) &#123; int a, b; cin &gt;&gt; a &gt;&gt; b; add(a, b); &#125; int res = 0; // 选择一边进行匹配 for (int i = 1; i &lt;= n1; i ++) &#123; memset(st, false, sizeof st); if (find(i)) res ++; &#125; printf("%d\n", res); return 0;&#125; 匈牙利算法的要点如下: 从左边第 1 个顶点开始，挑选未匹配点进行搜索，寻找增广路。 如果经过一个未匹配点，说明寻找成功。更新路径信息，匹配边数 +1，停止搜索。 如果一直没有找到增广路，则不再从这个点开始搜索。事实上，此时搜索后会形成一棵匈牙利树。我们可以永久性地把它从图中删去，而不影响结果。 由于找到增广路之后需要沿着路径更新匹配，所以我们需要一个结构来记录路径上的点。DFS 版本通过函数调用隐式地使用一个栈，而 BFS 版本使用 prev 数组。 性能比较: 两个版本的时间复杂度均为$O(V⋅E)$。DFS 的优点是思路清晰、代码量少，但是性能不如 BFS。我测试了两种算法的性能。对于稀疏图，BFS 版本明显快于 DFS 版本；而对于稠密图两者则不相上下。在完全随机数据 9000 个顶点 4,0000 条边时前者领先后者大约 97.6%，9000 个顶点 100,0000 条边时前者领先后者 8.6%, 而达到 500,0000 条边时 BFS 仅领先 0.85%。 补充定义和定理： 最大匹配数：最大匹配的匹配边的数目 最小点覆盖数：选取最少的点，使任意一条边至少有一个端点被选择 最大独立数：选取最多的点，使任意所选两点均不相连 最小路径覆盖数：对于一个 DAG（有向无环图），选取最少条路径，使得每个顶点属于且仅属于一条路径。路径长可以为 0（即单个点）。 定理1：最大匹配数 = 最小点覆盖数（这是 Konig 定理） 定理2：最大匹配数 = 最大独立数 定理3：最小路径覆盖数 = 顶点数 - 最大匹配数]]></content>
      <categories>
        <category>学习</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[PickGo 搭建图床]]></title>
    <url>%2FImage_Host.html</url>
    <content type="text"><![CDATA[PicGo 算得上一款比较优秀的图床工具。它是一款用 Electron-vue 开发的软件，可以支持微博，七牛云，腾讯云COS，又拍云，GitHub，阿里云OSS，SM.MS，imgur 等8种常用图床，功能强大，简单易用 Install软件发布地址 Mac 选择 dmg 下载 Windows 选择 exe 下载 Linux 选择 Appimage 下载 UbantuPickGo 2.2.2 12chmod a+x PicGo-2.2.2.AppImage./PicGo-2.2.2.AppImage GitHub图床首先登录GitHub，新建一个仓库或者也可以使用一个已有仓库(使用公有仓库)。 创建好后，需要在 GitHub 上生成一个 token 以便 PicGo 来操作我们的仓库，来到个人中心，选择 Developer settings 就能看到 Personal access tokens，我们在这里创建需要的 token。点击 Generate new token 创建一个新 token，选择 repo，同时它会把包含其中的都会勾选上，我们勾选这些就可以了。然后拉到最下方点击绿色按钮，Generate token 即可。之后就会生成一个 token ，记得复制保存到其他地方，这个 token 只显示一次！！ 打开PicGo并且设置github图床 之后就可以上传图片到我们设定的github图床了 Blog快速加载图片JSDelivr 是一个在中国访问速度极快的公有CDN, 使用也特别方便。在需要引用的仓库图片图片地址前加入 https://cdn.jsdelivr.net/gh/ 即可。 1![image](https://cdn.jsdelivr.net/gh/Trouble404/Image/blog20201019194150.png)]]></content>
      <categories>
        <category>System</category>
      </categories>
      <tags>
        <tag>Tool</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[Deploy by TensorRT]]></title>
    <url>%2FRetina_Face_Deploy.html</url>
    <content type="text"><![CDATA[TensorRTXProject Adress: https://github.com/wang-xinyu/tensorrtx Retina FaceProject Adress: https://github.com/wang-xinyu/Pytorch_Retinaface Jetson AGX XavierDeploy: https://developer.nvidia.com/embedded/jetson-agx-xavier-developer-kit Xavier Jetpack 4.4DPJetPack 4.4 components: L4T R32.4.2 CUDA 10.2 cuDNN 8.0.0 (Developer Preview) TensorRT 7.1.0 (Developer Preview) VisionWorks 1.6 OpenCV 4.1 Vulkan 1.2 VPI 0.2.0 (Developer Preview) Nsight Systems 2020.2 Nsight Graphics 2020.1 Nsight Compute 2019.3 通过sdk-manager刷机123456mkdir -p ~/sdkmanager cd ~/sdkmanager下载 sdkmanager_1.1.0-6343_amd64.debcd ~/sdkmanager sudo apt install ./sdkmanager_1.1.0-6343_amd64.debsdkmanager 选择对应xavier型号安装jetpack 4.4环境，选择离线安装(多Retry几次) 手动方式就需要自己动手进入recovery模式： 给xavier插上网线 用原装usb先将host与Xavier连接，还要注意是连接电源灯旁边的插口(lsusb可以查看到NVidia Corp)； 确保连接电源并保持Xavier为关闭状态； 按住中间的按键（Force Recovery）不松手； 按住左边的电源（Power）不松手； 过一两秒，同时松手。 安装完第一部分后配置xavier的ubantu系统 完成剩余的安装 ifconfig 查看地址 查看Xavier性能1tegrastats or jetson monitor scripthttps://github.com/rbonghi/jetson_stats PS: 测试TensorRT性能时需要跑多次测试 Retina Face TensorRTX compile generate retinaface.wts from pytorch 12345// download its weights &apos;Resnet50_Final.pth&apos;, put it in Pytorch_Retinaface/weightscd Pytorch_Retinafacepython detect.py --save_modelpython genwts.py// a file &apos;retinaface.wts&apos; will be generated. put retinaface.wts into tensorrtx/retinaface enviroment setting 123mkdir cmakecd cmaketouch FindTensorRT.cmake Find TensorRT script 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172# This module defines the following variables:## ::## TensorRT_INCLUDE_DIRS# TensorRT_LIBRARIES# TensorRT_FOUND## ::## TensorRT_VERSION_STRING - version (x.y.z)# TensorRT_VERSION_MAJOR - major version (x)# TensorRT_VERSION_MINOR - minor version (y)# TensorRT_VERSION_PATCH - patch version (z)## Hints# ^^^^^# A user may set ``TensorRT_ROOT`` to an installation root to tell this module where to look.#set(_TensorRT_SEARCHES)if(TensorRT_ROOT) set(_TensorRT_SEARCH_ROOT PATHS $&#123;TensorRT_ROOT&#125; NO_DEFAULT_PATH) list(APPEND _TensorRT_SEARCHES _TensorRT_SEARCH_ROOT)endif()# appends some common pathsset(_TensorRT_SEARCH_NORMAL PATHS "/usr/src/tensorrt/" # or custom tensorrt path)list(APPEND _TensorRT_SEARCHES _TensorRT_SEARCH_NORMAL)# Include dirforeach(search $&#123;_TensorRT_SEARCHES&#125;) find_path(TensorRT_INCLUDE_DIR NAMES NvInfer.h $&#123;$&#123;search&#125;&#125; PATH_SUFFIXES include)endforeach()if(NOT TensorRT_LIBRARY) foreach(search $&#123;_TensorRT_SEARCHES&#125;) find_library(TensorRT_LIBRARY NAMES nvinfer $&#123;$&#123;search&#125;&#125; PATH_SUFFIXES lib) endforeach()endif()mark_as_advanced(TensorRT_INCLUDE_DIR)if(TensorRT_INCLUDE_DIR AND EXISTS "$&#123;TensorRT_INCLUDE_DIR&#125;/NvInfer.h") file(STRINGS "$&#123;TensorRT_INCLUDE_DIR&#125;/NvInfer.h" TensorRT_MAJOR REGEX "^#define NV_TENSORRT_MAJOR [0-9]+.*$") file(STRINGS "$&#123;TensorRT_INCLUDE_DIR&#125;/NvInfer.h" TensorRT_MINOR REGEX "^#define NV_TENSORRT_MINOR [0-9]+.*$") file(STRINGS "$&#123;TensorRT_INCLUDE_DIR&#125;/NvInfer.h" TensorRT_PATCH REGEX "^#define NV_TENSORRT_PATCH [0-9]+.*$") string(REGEX REPLACE "^#define NV_TENSORRT_MAJOR ([0-9]+).*$" "\\1" TensorRT_VERSION_MAJOR "$&#123;TensorRT_MAJOR&#125;") string(REGEX REPLACE "^#define NV_TENSORRT_MINOR ([0-9]+).*$" "\\1" TensorRT_VERSION_MINOR "$&#123;TensorRT_MINOR&#125;") string(REGEX REPLACE "^#define NV_TENSORRT_PATCH ([0-9]+).*$" "\\1" TensorRT_VERSION_PATCH "$&#123;TensorRT_PATCH&#125;") set(TensorRT_VERSION_STRING "$&#123;TensorRT_VERSION_MAJOR&#125;.$&#123;TensorRT_VERSION_MINOR&#125;.$&#123;TensorRT_VERSION_PATCH&#125;")endif()include(FindPackageHandleStandardArgs)FIND_PACKAGE_HANDLE_STANDARD_ARGS(TensorRT REQUIRED_VARS TensorRT_LIBRARY TensorRT_INCLUDE_DIR VERSION_VAR TensorRT_VERSION_STRING)if(TensorRT_FOUND) set(TensorRT_INCLUDE_DIRS $&#123;TensorRT_INCLUDE_DIR&#125;) if(NOT TensorRT_LIBRARIES) set(TensorRT_LIBRARIES $&#123;TensorRT_LIBRARY&#125;) endif() if(NOT TARGET TensorRT::TensorRT) add_library(TensorRT::TensorRT UNKNOWN IMPORTED) set_target_properties(TensorRT::TensorRT PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "$&#123;TensorRT_INCLUDE_DIRS&#125;") set_property(TARGET TensorRT::TensorRT APPEND PROPERTY IMPORTED_LOCATION "$&#123;TensorRT_LIBRARY&#125;") endif()endif() Modify CMakeLists.txt 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849cmake_minimum_required(VERSION 2.6)project(retinaface)add_definitions(-std=c++11)# CMake path# For using Find TensorRT scriptlist(APPEND CMAKE_MODULE_PATH "$&#123;PROJECT_SOURCE_DIR&#125;/cmake")list(APPEND CMAKE_PREFIX_PATH "$&#123;PROJECT_SOURCE_DIR&#125;/cmake")option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)set(CMAKE_CXX_STANDARD 11) set(CMAKE_BUILD_TYPE Debug)find_package(CUDA REQUIRED)find_package(TensorRT REQUIRED)set(CUDA_NVCC_PLAGS $&#123;CUDA_NVCC_PLAGS&#125;;-std=c++11;-g;-G;-gencode;arch=compute_30;code=sm_30)if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") message("embed_platform on") include_directories(/usr/local/cuda/targets/aarch64-linux/include) link_directories(/usr/local/cuda/targets/aarch64-linux/lib)else() message("embed_platform off") include_directories(/usr/local/cuda/include) link_directories(/usr/local/cuda/lib64)endif()set(CMAKE_CXX_FLAGS "$&#123;CMAKE_CXX_FLAGS&#125; -std=c++11 -Wall -Ofast -Wfatal-errors -D_MWAITXINTRIN_H_INCLUDED")cuda_add_library(decodeplugin SHARED $&#123;PROJECT_SOURCE_DIR&#125;/decode.cu)target_include_directories(decodeplugin PUBLIC $&#123;TensorRT_INCLUDE_DIRS&#125;)target_link_libraries(decodeplugin $&#123;TensorRT_LIBRARIES&#125;)target_link_libraries(decodeplugin $&#123;CUDA_LIBRARIES&#125;)find_package(OpenCV)include_directories(OpenCV_INCLUDE_DIRS)add_executable(retina_50 $&#123;PROJECT_SOURCE_DIR&#125;/retina_r50.cpp)target_link_libraries(retina_50 nvinfer)target_link_libraries(retina_50 cudart)target_link_libraries(retina_50 decodeplugin)target_link_libraries(retina_50 $&#123;OpenCV_LIBRARIES&#125;)target_link_libraries(retina_50 $&#123;TensorRT_LIBRARIES&#125;)target_link_libraries(retina_50 $&#123;CUDA_LIBRARIES&#125;)add_definitions(-O2 -pthread) PerformanceXavier:Input IMG Size(768, 1344)Speed: 102ms/per img]]></content>
      <categories>
        <category>TensorRT</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
        <tag>Object Detection</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[图像分割-3D or 医学领域]]></title>
    <url>%2FLearning-3D-Image-Segmentation.html</url>
    <content type="text"><![CDATA[医学相关的图像分割技术 医学2D 医学3D A Novel Domain Adaptation Framework for Medical Image Segmentation 3D U-Net: Learning Dense Volumetric Segmentation from Sparrse Annotation DeepMedic for Brain Tumor Segmentation Joint Sequence Learning and Cross-Modality Convolution for 3D Biomedical Segmentation Simultaneous Super-Resolution and Cross-Modality Synthesis of 3D Medical Image using Weekly-Supervised Joint Convolutional Sparse Coding A Novel Domain Adaptation Framework for Medical Image Segmentation论文地址 脑部肿瘤切割 难点在于难以精确定位肿瘤(肿瘤形状各异，分布广泛) 核磁共振4种模态(Modality) 自旋晶格驰像(T1) T1对比(T2) 自旋松弛(T2) 流体衰减反转恢复(FLAIR) 论文创新点: A biophysics based domain adaptation method(物理肿瘤生长模型加上对抗网络生成逼真的MR图像补充数据集) An automatic method to 分割健康的组织(白质灰质，脑脊液)，通过健康的组织轮廓辅助脑补图像切割 方法 Data Augmentation: 使用基于生物学的肿瘤生长模型(PDE)模拟合成的肿瘤，在使用一个辅助的神经网络修正模拟肿瘤到correct intensities distribution对比真实的MR图像(会通过强加循环一致性限制分布情况) Extened segmentation: 扩展分割到健康的薄壁组织。先使用(in-house diffeomorphic registration code)微分同胚技术(是一种光滑可逆的变换，在MRI图像配准中可以保证形变后的拓扑结构保持不变，同时避免出现不合理的物理现象)处理过的数据进行训练。 然后再使用DNN去分割健康的组织(神经胶质，脑脊液，灰质和白质)，结果如上图。 这样可以增加健康组织的轮廓作为重要的训练信息并且改进了原来的类不平衡的问题。 具体的步骤分为: Affine registration of each atlas image to the brats image Diffeomorphic registration of each atlas image to the BraTS image(把健康组织的信息匹配BraTS的数据集结构) Majority voting to fuse labels of all deformed atlases to get the final healthy tissuse segmentation 模型 3D U-Net(会在下面讨论) 使用3D进行第一阶段的检测，获得肿瘤的初始位置。 U-Net(在前一章节讲过) 使用2D的U-Net以及domain adaptation results获得最终的分割结果 缺点 这个框架只支持2D的domain trasformations，所以对3D的数据只能进行切片并且最后是用的2D的神经网络，这样没有3D的网络来的efficient以及精确。 3D U-Net: Learning Dense Volumetric Segmentation from Sparse Annotation论文地址 在生物医学领域，3D数据是很多的，一层一层转化为2D数据去标注训是不现实的(及其耗时)，而且用整个3D体积的全部数据去训练既低效又极有可能过拟合(相邻切片的数据是非常相近的)。这篇文章提出的3D Unet只需要少部分2D的标注切片就可以生成密集的立体的分割。此网络主要有两个不同的作用方式: Semi-automated setup: 在一个少量稀疏标注的数据集上训练并在此数据集的图像上预测其他未标注的地方。 Fully-automated setup: 在representative的稀疏标注的数据集训练，然后用来切割新的图像。 改进 在2D U-Net的基础上，仍然使用encoder去分析整个图片, 但是扩展了decoder来产生full-resolution的切割。 使用3D数据作为输入，因此网络改用3D convolutions, 3D max pooling 和 3D up-convolutional。 避免使用bottlenecks结构因为输入的数据不会很多，避免丢失重要的信息。 网络 Encoder部分: 每层包含两个$3 \times 3 \times 3$卷积和ReLU, 然后是一个strides为2的$2 \times 2 \times 2$的最大池化层。 Decoder部分: 每层包含strides为2的$2 \times 2 \times 2$的upconvolution以及两个$3 \times 3 \times 3$卷积和ReLU 这里作者还用了Batch Normalization 防止梯度爆炸，并且在BN后增加了缩放和平移$x_{new}=\alpha \cdot x + \beta$, 其中两个超参是学习出来的。 能在稀疏标注的训练集训练的原因是使用了weighted softmanx loss function, 把unlabeled pixels的权重置为0让网络只学习有标注的部分并且提高了泛化能力。 Joint Sequence Learning and Cross-Modality Convolution for 3D Biomedical Segmentation论文地址 目前对于定位肿瘤的难点在于： 肿瘤形状各异，例如神经胶质瘤与胶质母细胞瘤形状不同 肿瘤分布广泛，可能分布于大脑的任何区域 这篇文章提出来一个思路就是交叉形态卷积的方法做一个 encoder-decoder 的网络结构，然后同时用LSTM对2D的切片序列建模。 MRI也是跟CT一样断层扫描的过程且包含4种模态(Modality)，就是它一层一层，一层扫出来的就是一个2D的图片，然后多层累计起来就是3D的，但是其实切割是要切割出3D的脑部肿瘤位置，这样就需要把2D的变成3D的，把2D的切片之间的关系通过LSTM描述出来，最后把多模态卷积和LSTM网络结合在一起，达到3D切割。 模型 这个方法的framework大概是这样的，从左到右看。 首先每一个脑部的MRI数据，他都是通过四种模态切出来的，这里用四种不同的颜色来表示，相当于每一个slice就是我说的那个2D的图片。 切完之后他会把四个模态，就是到图b这个阶段了，四个模态交叉在一起做一个multi-modal的encoder，这个encoder就是用一个神经网络来实现的。 四个模态encode到一起之后，在这一步就用神经网络把四个模态下的脑部切割出来了，这是2D的情况下。 然后再加上convolution LSTM把2D的切割、2D和2D之间的dependency描述出来之后就形成了3D的切割，然后再做一下decoder，展现成最后这种形式。在最中间有一个切割出来的东西，其他没被切割到的background。 方法 MME(Multi-Modal Encoder) 类似于SegNet里的编码器结构, 因为数据集比较小，因此网络也简化了，使用四个卷积核，通过batch-normalization，然后加一个非线性变换，在后面有四个最大池化层。 MRF(Multi-Resolution Fusion) 结合多尺度多模态的信息，通过在不同的尺度的encoder和decoder中进行feature multiplication代替级联因此不会增加特征映射的大小。 CMC(Cross-Modality COnvolution) CMC可以把空间信息以及不同模态的关系结合到一起。 四个模态的数据进入到这个卷积网络之后，就会把相同channel下的每一个模态stack在一起形成一个block, 就是每个channel里面有 C 个slice，就是说它是一个立体结构了，一个的长宽是H、W，高是C的这种。四个模态弄到一起就是C×4×H×W。然后通过一个三维的卷积，卷积的大小里有个C×4，也就是用4×1×1的一个卷积核，做卷积之后得到每一层的切割出来的特征。 Slice Sequence Learning 使用一个端到端的切片序列学习框架去建模切片之间的相关性。这个convolution LSTM跟普通的LSTM有一个区别，就是把原来的矩阵相乘替换为一个卷积操作，就是普通的乘法变成卷积层，这样它就能够在把之前状态的空间信息保留着。其实它的目的就是，卷积LSTM会描述一个2D切割边缘的趋势，比如说这一张中切片它的形态是这样的，然后到下一张它会有一个轻微的变化，要把这种变化描述出来。 此外不同切片的convLSTM的权重是共享的因此需要更新的参数不会随着序列增加。 Decoder 包含上采样以及soft-max进行分类 训练 Single Slice Training: 使用了median frequency balancing调节cross-entropy loss的权重来缓解数据不平衡的情况(98%的都是健康区域)$$\alpha_{c}=\frac{median_freq}{freq(c)}$$ Two-Phase Training: 第一阶段：只采样包含肿瘤的切片并且使用median frequency balancing的方法进行训练。 第二阶段：去除median frequency策略并且调低学习率，在真实的肿瘤分布概率下进行训练 结果]]></content>
      <categories>
        <category>学习</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
        <tag>Image Segmentation</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[图像分割-2D]]></title>
    <url>%2FLearning-Image-Segmentation.html</url>
    <content type="text"><![CDATA[Image Segmentation Introduction在计算机视觉领域，图像分割（Segmentation）指的是将数字图像细分为多个图像子区域（像素的集合）（也被称作超像素）的过程。图像分割的目的是简化或改变图像的表示形式，使得图像更容易理解和分析。图像分割通常用于定位图像中的物体和边界（线，曲线等）。更精确的，图像分割是对图像中的每个像素加标签的一个过程，这一过程使得具有相同标签的像素具有某种共同视觉特性。 图像分割的结果是图像上子区域的集合（这些子区域的全体覆盖了整个图像），或是从图像中提取的轮廓线的集合（例如边缘检测）。一个子区域中的每个像素在某种特性的度量下或是由计算得出的特性都是相似的，例如颜色、亮度、纹理。邻接区域在某种特性的度量下有很大的不同。 应用 医学影像：1. 肿瘤和其他病理的定位；2. 组织体积的测量；3. 计算机引导的手术；4. 诊断；5. 治疗方案的定制；6. 解剖学结构的研究 卫星图像中定位物体 人脸识别 指纹识别 交通控制系统 Tranditional Methods参考 基于阙值的分割方法: 1. 固定阙值分割；2. 直方图双峰法；3. 迭代阙值图像分割；自适应阙值图像分割（最大分类方差法，均值法，最佳阙值）. 基于边缘的分割方法: 1. Canny边缘检测器；2. Harris角点检测器；3. SIFT检测器；3. SURF检测器. 基于区域的分割方法: 1. 种子区域生长法；2. 区域分裂合并法；3. 分水岭法. 基于图论的分割方法: 1. GraphCut; 2. GrabCut; 3. Random Walk. 基于能量泛函的分割方法: 参数活动轮廓模型（1. Snake模型；2. Active Shape Model; 3. Active Apperance Models; 4. Constrained local model）；几何活动轮廓模型. Datasets Pascal VOC: 20个类别，6929张标注图片 CityScapes：道路驾驶场景，30个类别，5000张精细标注，20000张粗糙标注 MS COCO：80类，33万张图片，超过20万张有标注，150万个物体的个体 医学影像领域的ImageNet: DeepLesion, 10000多个病例研究的超过32000个病变标注 图像分割的度量标准假设共有$k+1$个类（从$L_{o}$到$L_{k}$,其中包含一个空类或背景）, $P_{ij}$表示本属于类i但被预测为类j的像素数量。即，$p_{ii}$表示真正的数量，而$p_{ij}$和$p_{ji}$则分别被解释为假正和假负，尽管两者都是假正与假负之和。 Pixel Accuracy(PA, 像素精度): 最简单的度量，为标记正确的像素占总像素的比例。$$PA=\frac{\sum_{i=0}^{k}p_{ii}}{\sum_{i=0}^{k}\sum_{j=0}^{k}p_{ij}}$$ Mean Pixel Accuracy(MAP,均像素精度): PA的一种简单提升，计算每个类内被正确分类像素数的比例，之后求所有类的平均。$$MAP=\frac{1}{k+1}\sum_{i=0}^{k}\frac{p_{ii}}{\sum_{j=0}^{k}p_{ij}}$$ Mean Intersection over Union(MIoU, 均交并比): 为语义分割的标准度量。其计算两个集合的交集和并集之比，这两个集合为真实值(ground truth)和预测值(predicted segmentation). 这个比例可以变形为正真数(intersection)比上真正、假负、假正(并集)之和，之后在每个类上计算IoU再平均。$$MIoU = \frac{1}{k+1}\sum_{i=0}^{k}\frac{p_{ii}}{\sum_{j=0}^{k}p_{ij}+\sum_{j=0}^{k}(p_{ji}-p_{ii})}$$ Frequency Weighted Intersection over Union(FWIoU, 频权交并比): 为MIoU的一种提升，根据每个类出现的频率为其设置权重。$$FWIoU = \frac{1}{\sum_{i=0}^{k}\sum_{j=0}^{k}p_{ij}}\sum_{i=0}^{k}\frac{p_{ii}}{\sum_{j=0}^{k}p_{ij}+\sum_{j=0}^{k}(p_{ji}-p_{ii})}$$ 深度学习算法Fully Convolutional Networks论文地址传统神经网络做分类的步骤是，首先是一个图像进来之后经过多层卷积得到降维之后的特征图，这个特征图经过全连接层变成一个分类器，最后输出一个类别的向量，这就是分类的结果。 而 FCN 是把所有的全连接层换成卷基层，原来只能输出一个类别分类的网络可以在特征图的每一个像素输出一个分类结果。这样就把分类的向量，变成了一个分类的特征图。 上图中的猫, 输入AlexNet, 得到一个长为1000的输出向量, 表示输入图像属于每一类的概率, 其中在“tabby cat”这一类统计概率最高。而FCN对图像进行像素级的分类，从而解决了语义级别的图像分割（semantic segmentation）问题。FCN可以接受任意尺寸的输入图像，采用反卷积层对最后一个卷积层的feature map进行上采样, 使它恢复到输入图像相同的尺寸，从而可以对每个像素都产生了一个预测, 同时保留了原始输入图像中的空间信息, 最后在上采样的特征图上进行逐像素分类。最后逐个像素计算softmax分类的损失, 相当于每一个像素对应一个训练样本。 全连接-&gt;卷积层： 一个 K=4096 的全连接层，输入数据体的尺寸是 7∗7∗512，这个全连接层可以被等效地看做一个 F=7,P=0,S=1,K=4096 的卷积层. 假设一个卷积神经网络的输入是 224x224x3 的图像，一系列的卷积层和下采样层将图像数据变为尺寸为 7x7x512 的激活数据体。AlexNet使用了两个尺寸为4096的全连接层，最后一个有1000个神经元的全连接层用于计算分类评分。我们可以将这3个全连接层中的任意一个转化为卷积层： 针对第一个连接区域是[7x7x512]的全连接层，令其滤波器尺寸为F=7，这样输出数据体就为[1x1x4096]了。 针对第二个全连接层，令其滤波器尺寸为F=1，这样输出数据体为[1x1x4096]。 对最后一个全连接层也做类似的，令其F=1，最终输出为[1x1x1000] end to end, pixels to pixels network经过多次卷积和pooling以后，得到的图像越来越小，分辨率越来越低。其中图像到$\frac{H}{32} \times \frac{W}{32}$的时候图片是最小的一层时，所产生图叫做heatmap热图，热图就是最重要的高维特征图，得到高维特征的heatmap之后就是最重要的一步也是最后的一步对原图像进行upsampling，把图像进行放大、放大、放大，到原图像的大小。最后的输出是1000张heatmap经过upsampling变为原图大小的图片，为了对每个像素进行分类预测label成最后已经进行语义分割的图像，这里有一个小trick，就是最后通过逐个像素地求其在1000张图像该像素位置的最大数值描述（概率）作为该像素的分类。因此产生了一张已经分类好的图片，如上图右侧有狗狗和猫猫的图。 现在我们有1/32尺寸的heatMap，1/16尺寸的featureMap和1/8尺寸的featureMap，1/32尺寸的heatMap进行upsampling操作之后，因为这样的操作还原的图片仅仅是conv5中的卷积核中的特征，限于精度问题不能够很好地还原图像当中的特征，因此在这里向前迭代。把conv4中的卷积核对上一次upsampling之后的图进行反卷积补充细节（相当于一个插值过程），最后把conv3中的卷积核对刚才upsampling之后的图像进行再次反卷积补充细节，最后就完成了整个图像的还原。 缺点： 是得到的结果还是不够精细。进行8倍上采样虽然比32倍的效果好了很多，但是上采样的结果还是比较模糊和平滑，对图像中的细节不敏感。 是对各个像素进行分类，没有充分考虑像素与像素之间的关系。忽略了在通常的基于像素分类的分割方法中使用的空间规整（spatial regularization）步骤，缺乏空间一致性。 补充：插值法上采样upsampling的主要目的是放大图像，几乎都是采用内插值法，即在原有图像像素的基础上，在像素点值之间采用合适的插值算法插入新的元素。 线性插值法：使用连接两个已知量的直线来确定在这个两个已知量之间的一个未知量的值的方法。该直线方程可表示为：$\frac{y-y_{0}}{y_{1}-y_{0}}=\frac{x-x_{0}}{x_{1}-x_{0}}$ 假设方程两边的值是$\alpha$，那么这个值就是插值系数，即$\alpha =\frac{y-y_{0}}{y_{1}-y_{0}}=\frac{x-x_{0}}{x_{1}-x_{0}}$. 所以y可以表示为: $y=(1-\alpha)y_{0}+\alpha y_{1} = (1-\frac{x-x_{0}}{x_{1}-x_{0}})y_{0}+\frac{x-x_{0}}{x_{1}-x_{0}}y_{1}=\frac{x_{1}-x}{x_{1}-x_{0}}y_{0}+\frac{x-x_{0}}{x_{1}-x_{0}}y_{1}=\frac{x_{1}-x}{x_{1}-x_{0}}f(x_{1})+\frac{x-x_{0}}{x_{1}-x_{0}}f(x_{0})$ 双线性插值双线性插值是插值算法中的一种，是线性插值的扩展。利用原图像中目标点四周的四个真实存在的像素值来共同决定目标图中的一个像素值，其核心思想是在两个方向分别进行一次线性插值。 X方向的线性插值：在$Q_{12}$,$Q_{22}$中插入蓝色点$R_{2}$，$Q_{11}$，$Q_{21}$中插入蓝色点$R_{1}$ $f(R_{1}=\frac{x_{2}-x}{x_{2}-x_{1}}f(Q_{11})+\frac{x-x_{1}}{x_{2}-x_{1}}f(Q_{21})$; $f(R_{2}=\frac{x_{2}-x}{x_{2}-x_{1}}f(Q_{12})+\frac{x-x_{1}}{x_{2}-x_{1}}f(Q_{22})$ Y方向的线性插值：通过第一步计算出的$R_{1$}与$R_{2}$在y方向上插值计算出P点 $f(P)=\frac{y_{2}-y}{y_{2}-y_{1}}f(R_{1})+\frac{y-y_{1}}{y_{2}-y_{1}}f(R_{2})$ U-Net: Convolutional Networks for Biomedical Image Segmentation论文地址卷积网络被大规模应用在分类任务中，输出的结果是整个图像的类标签。然而，在许多视觉任务，尤其是生物医学图像处理领域，目标输出应该包括目标类别的位置，并且每个像素都应该有类标签。另外，在生物医学图像往往缺少训练图片。所以，Ciresan等人训练了一个卷积神经网络，用滑动窗口提供像素的周围区域（patch）作为输入来预测每个像素的类标签。这个网络有两个优点： 第一，输出结果可以定位出目标类别的位置； 第二，由于输入的训练数据是patches，这样就相当于进行了数据增广，解决了生物医学图像数量少的问题。 但是，这个方法也有两个很明显缺点。 第一，它很慢，因为这个网络必须训练每个patch，并且因为patch间的重叠有很多的冗余,造成资源的浪费，减慢训练时间和效率; 第二，定位准确性和获取上下文信息不可兼得。大的patches需要更多的max-pooling层这样减小了定位准确性,小的patches只能看到很小的局部信息，包含的背景信息不够。 U-Net Architecture 使用全卷积神经网络。(全卷积神经网络就是卷积取代了全连接层，全连接层必须固定图像大小而卷积不用，所以这个策略使得，你可以输入任意尺寸的图片，而且输出也是图片，所以这是一个端到端的网络。) 左边的网络是收缩路径：使用卷积和maxpooling。 右边的网络是扩张路径:使用上采样产生的特征图与左侧收缩路径对应层产生的特征图进行concatenate操作。（pooling层会丢失图像信息和降低图像分辨率且是不可逆的操作，对图像分割任务有一些影响，对图像分类任务的影响不大，为什么要做上采样？因为上采样可以补足一些图片的信息，但是信息补充的肯定不完全，所以还需要与左边的分辨率比较高的图片相连接起来（直接复制过来再裁剪到与上采样图片一样大小），这就相当于在高分辨率和更抽象特征当中做一个折衷，因为随着卷积次数增多，提取的特征也更加有效，更加抽象，上采样的图片是经历多次卷积后的图片，肯定是比较高效和抽象的图片，然后把它与左边不怎么抽象但更高分辨率的特征图片进行连接）。 最后再经过两次反卷积操作，生成特征图，再用两个1X1的卷积做分类得到最后的两张heatmap,例如第一张表示的是第一类的得分，第二张表示第二类的得分heatmap,然后作为softmax函数的输入，算出概率比较大的softmax类，选择它作为输入给交叉熵进行反向传播训练。 Overlap-tile strategy 医学图像是一般相当大，但是分割时候不可能将原图太小输入网络，所以必须切成一张一张的小patch，在切成小patch的时候，Unet由于网络结构原因适合有overlap的切图，可以看图，红框是要分割区域，但是在切图时要包含周围区域，overlap另一个重要原因是周围overlap部分可以为分割区域边缘部分提供文理等信息。可以看黄框的边缘，分割结果并没有受到切成小patch而造成分割情况不好。 训练 最后一层使用了交叉熵函数与softmax：$$E=\sum_{x\in \Omega}w(x)log(p_{\ell(x)}(x))$$ 并且为了补偿训练每个类像素的不同频率使得网络更注重学习相互接触的细胞之间的小的分割边界，引入了权重图计算$w(x)$:$$w(x)=w_{c}(x)+w_{0} \times exp(-\frac{(d_{1}(x)+d_{2}(x))}{2\sigma^{2}})$$ Data Augmentation在只有少量样本的情况况下，要想尽可能的让网络获得不变性和鲁棒性，数据增加是必不可少的。因为本论文需要处理显微镜图片，我们需要平移与旋转不变性，并且对形变和灰度变化鲁棒。将训练样本进行随机弹性形变是训练分割网络的关键。使用随机位移矢量在粗糙的3*3网格上产生平滑形变(smooth deformations)。 位移是从10像素标准偏差的高斯分布中采样的。然后使用双三次插值(Bicubic interpolation)计算每个像素的位移。在contracting path的末尾采用drop-out 层更进一步增加数据。 双三次插值在这种方法中，函数f在点(x,y)的值可以通过矩形网络中最近的16个采样点加权平均得到。 DeepLab V1论文地址 DeepLab是结合了深度卷积神经网络(DCNNs)和概率图模型(DenseCRFs)的方法. DCNN在图像标记任务中存在两个技术障碍： 信号下采样 空间不敏感(invariance) 第一个问题涉及到：在DCNN中重复最大池化和下采样带来的分辨率下降问题，分辨率的下降会丢失细节。DeepLab是采用的atrous(带孔)算法扩展感受野，获取更多的上下文信息。 第二个问题涉及到：分类器获取以对象中心的决策是需要空间变换的不变性，这天然的限制了DCNN的定位精度，DeepLab采用完全连接的条件随机场(DenseCRF)提高模型捕获细节的能力。 除空洞卷积和 CRFs 之外，论文使用的 tricks 还有 Multi-Scale features。其实就是 U-Net 和 FPN 的思想，在输入图像和前四个最大池化层的输出上附加了两层的 MLP，第一层是 128 个 3×3 卷积，第二层是 128 个 1×1 卷积。最终输出的特征与主干网的最后一层特征图融合，特征图增加 5×128=640 个通道。实验表示多尺度有助于提升预测结果，但是效果不如 CRF 明显。 CRF-&gt;语义分割 对于每个像素位置$i$具有隐变量$x_{i}$(这里隐变量就是像素的真实类别标签，如果预测结果有21类，则$(i \in 1,2,..,21)$ 还有对应的观测值 $y_{i}$(即像素点对应的颜色值)。以像素为节点，像素与像素间的关系作为边，构成了一个条件随机场(CRF)。通过观测变量$y_{i}$来推测像素位置$i$应的类别标签$x_{i}$.条件随机场示意图如下: 条件随机场符合吉布斯分布(x是上面的观测值，下面省略全局观测I):$$p(x|I)=\frac{1}{Z}exp(-E(x|I))$$ 全连接的CRF模型使用的能量函数$E(x)$为:$$E(x)=\sum_{i} \theta_{i}(x_{i})+\sum_{ij}\theta_{ij}(x_{i},x_{j})$$ 这分为一元势函数$\theta_{i}(x_{i})$和二元势函数$\theta_{ij}(x_{i},x_{j})$两部分 一元势函数是定义在观测序列位置i的状态特征函数，用于刻画观测序列对标记变量的影响（例如在城市道路任务中，观测到像素点为黑色，对应车子的可能比天空可能要大）。这里$P(x_{i})$是取DCNN计算关于像素i的输出的标签分配概率. $$\theta_{i}(x_{i})=-logP(x_{i})$$ 二元势函数是定义在不同观测位置上的转移特征函数，用于刻画变量之间的相关关系以及观测序列对其影响。如果比较相似，那可能是一类，否则就裂开，这可以细化边缘。一般的二元势函数只取像素点与周围像素之间的边，这里使用的是全连接，即像素点与其他所有像素之间的关系。 $$\theta_{ij}(x_{i},x_{j})=\mu(x_{i},x_{j})\sum_{m=1}^{K}w_{m}k^{m}(f_{i},f_{j})$$ DeepLab中高斯核采用双边位置和颜色的组合（第一核取决于像素位置(p)和像素颜色强度(I),第二核取决于像素位置(p)）:$$w_{1}exp(-\frac{\lVert p_{i}-p_{j} \rVert^{2}}{2\sigma_{\alpha}^{2}}-\frac{\lVert I_{i}-I_{j} \rVert^{2}}{2\sigma_{\beta}^{2}})+w_{2}exp(-\frac{\lVert p_{i}-p_{j} \rVert^{2}}{2\sigma_{\gamma}^{2}})$$ 实验 项目 设置 数据集 PASCAL VOC 2012 segmentation benchmark DCNN模型 权重采用预训练的VGG16 DCNN损失函数 交叉熵 训练器 SGD，batch=20 学习率 初始为0.001，最后的分类层是0.01。每2000次迭代乘0.1 权重 0.9的动量， 0.0005的衰减 DepLab V2论文地址 DeepLabv2 是相对于 DeepLabv1 基础上的优化。DeepLabv1 在三个方向努力解决，但是问题依然存在： 特征分辨率的降低 物体存在多尺度 DCNN 的平移不变性 针对这三个问题, DeepLabv2做出了3个主要贡献: 首先，强调使用空洞卷积，作为密集预测任务的强大工具。空洞卷积能够明确地控制DCNN内计算特征响应的分辨率，即可以有效的扩大感受野，在不增加参数量和计算量的同时获取更多的上下文。 其次，提出了空洞空间卷积池化金字塔(atrous spatial pyramid pooling (ASPP))，以多尺度的信息得到更强健的分割结果。ASPP并行的采用多个采样率的空洞卷积层来探测，以多个比例捕捉对象以及图像上下文。 最后，通过组合DCNN和概率图模型，改进分割边界结果。在DCNN中最大池化和下采样组合实现可平移不变性，但这对精度是有影响的。通过将最终的DCNN层响应与全连接的CRF结合来克服这个问题。 步骤 输入经过改进的DCNN(带空洞卷积和ASPP模块)得到粗略预测结果，即Aeroplane Coarse Score map 通过双线性插值扩大到原本大小，即Bi-linear Interpolation 再通过全连接的CRF细化预测结果，得到最终输出Final Output 方法 空洞卷积用于密集特征提取和扩大感受野 首先考虑一维信号，空洞卷积输出为$y[i]$, 输入为$x[i]$, 长度K的滤波器为$w[k]$, 则定义为：$$y[k]=\sum_{k=1}^{K}x[i+r\cdot k]w[k]$$输入采样的步幅为参数r, 标准采样率是$r=1$如图(a); 图(b)是采样率$r=2$的时候： 二维信号(图片)上使用空洞卷积的表现,给定一个图像： 上分支：首先下采样将分辨率降低2倍，做卷积。再上采样得到结果。本质上这只是在原图片的1/4内容上做卷积响应。 下分支：如果将全分辨率图像做空洞卷积(采样率为2，核大小与上面卷积核相同)，直接得到结果。这样可以计算出整张图像的响应，如下图所示，这样做效果更佳。 空洞卷积能够放大滤波器的感受野，速率r引入$r-1$个零，有效将感受野从$k\times k$扩展到$k_{e}=k+(k-1)(r-1)$而不增加参数和计算量。在DCNN中，常见的做法是混合使用空洞卷积以高的分辨率(理解为采样密度)计算最终的DCNN网络响应。 使用ASPP模块表示多尺度图像 DeepLabv2的做法与SPPNet类似，并行的采用多个采样率的空洞卷积提取特征，再将特征融合，类似于空间金字塔结构，形象的称为Atrous Spatial Pyramid Pooling (ASPP)。示意图如下： 在同一Input Feature Map的基础上，并行的使用4个空洞卷积，空洞卷积配置为$r=6,12,18,24$, 核大小为$3 \times 3$. 最终将不同卷积层得到的结果做像素加融合到一起. 使用全连接CRF做结构预测用于恢复边界精度 训练 项目 设置 DCNN模型 权重采用预训练的VGG16，ResNet101 DCNN损失函数 输出的结果与ground truth下采样8倍做像素交叉熵 训练器 SGD，batch=20 学习率 初始为0.001，最后的分类层是0.01。每2000次迭代乘0.1 权重 0.9的动量， 0.0005的衰减 DepLab V3论文地址 语义分割任务，在应用深度卷积神经网络中的有两个挑战： 第一个挑战：连续池化和下采样，让高层特征具有局部图像变换的内在不变性，这允许DCNN学习越来越抽象的特征表示。但同时引起的特征分辨率下降，会妨碍密集的定位预测任务，因为这需要详细的空间信息。 第二个挑战：多尺度目标的存在 DeepLabv3的主要贡献在于： 重新讨论了空洞卷积的使用，在级联模块和空间金字塔池化的框架下，能够获取更大的感受野从而获取多尺度信息。 改进了ASPP模块：由不同采样率的空洞卷积和BN层组成，尝试以级联或并行的方式布局模块。 讨论了一个重要问题：使用大采样率的空洞卷积，因为图像边界响应无法捕捉远距离信息，会退化为1×1的卷积, 因此建议将图像级特征融合到ASPP模块中。 阐述了训练细节并分享了训练经验，论文提出的”DeepLabv3”改进了以前的工作，获得了很好的结果 方法 空洞卷积应用于密集的特征提取 深层次的空洞卷积 将空洞卷积应用在级联模块, 取ResNet中最后一个block，在上图中为block4，并在其后面增加级联模块。图(a)所示，整体图片的信息总结到后面非常小的特征映射上，使用步幅越长的特征映射，得到的结果反倒会差，结果最好的out_stride = 8 需要占用较多的存储空间。因为连续的下采样会降低特征映射的分辨率，细节信息被抽取，这对语义分割是有害的。上图(b)所示，可使用不同采样率的空洞卷积保持输出步幅的为out_stride = 16.这样不增加参数量和计算量同时有效的缩小了步幅。 Atrous Spatial Pyramid Pooling 对于在DeepLabv2中提出的ASPP模块，其在特征顶部映射图并行使用了四种不同采样率的空洞卷积。这表明以不同尺度采样是有效的，在DeepLabv3中向ASPP中添加了BN层。不同采样率的空洞卷积可以有效的捕获多尺度信息，但是，随着采样率的增加，滤波器的有效权重(权重有效的应用在特征区域，而不是填充0)逐渐变小。如下图所示： 当不同采样率的$3 \times 3$卷积核应用在$65 \times 65$的特征映射上，采样率接近特征映射大小时，$3 \times 3$的滤波器不是捕捉全图像的上下文，而是退化为简单的$1 \times 1$滤波器，只有滤波器中心点的权重起了作用。 为了克服这个问题，改进了ASPP结构如上图： 一个$1 \times 1$卷积和三个$3 \times 3$卷积的采样率为$rate = (6,12,18)$的空洞卷积，滤波器数量为256，包含BN层。针对output_stride=16的情况。（当output_stride=8的时候，采样率会加倍，所有的特征会通过$1 \times 1$卷积级联到一起） 使用了图片级特征。具体来说，在模型最后的特征映射上应用全局平均，将结果经过$1 \times 1$的卷积，再双线性上采样得到所需的空间维度。 训练 部分 设置 数据集 PASCAL VOC 2012 工具 TensorFlow 裁剪尺寸 采样513大小的裁剪尺寸 学习率策略 采用poly策略， $learning rate = base_lr(1-\frac{iter}{max_iters})^{power}$ BN层策略 当output_stride=16时，我们采用batchsize=16，同时BN层的参数做参数衰减0.9997。在增强的数据集上，以初始学习率0.007训练30K后，冻结BN层参数。采用output_stride=8时，再使用初始学习率0.001训练30K。训练output_stride=16比output_stride=8要快很多，因为中间的特征映射在空间上小四倍。但因为output_stride=16在特征映射上粗糙的是牺牲了精度。 上采样策略 在先前的工作上,将最终的输出与GroundTruth下采样8倍做比较之后发现保持GroundTruth更重要，故将最终的输出上采样8倍与完整的GroundTruth比较。 DepLab-V$3^{+}$论文地址 因为深度网络存在pooling or convolutions with stride的层，会导致feature分辨率下降，从而导致预测精度降低，而造成的边界信息丢失问题. 这个问题可以通过使用空洞卷积替代更多的pooling层来获取分辨率更高的feature。但是feature分辨率更高会极大增加运算量。 方法 所以DeepLabV$3^{+}$中通过采用了encoder-decoder结构，在DeepLab V3中加入了一个简单有效的decoder模块来改善物体边缘的分割结果(图c)：先上采样4倍，在与encoder中的特征图concatenate，最后在上采样4倍恢复到原始图像大小。除此之外还尝试使用Xception作为encoder，在Atrous Spatial Pyramid Pooling和decoder中应用depth-wise separable convolution得到了更快精度更高的网络。 Encoder ResNet: encoder就是DeepLab V3，通过修改ResNet101最后两(一)个block的stride，使得output stride为8(16)。之后在block4后应用改进后的Atrous Spatial Pyramid Pooling，将所得的特征图concatenate用1×1的卷积得到256个通道的特征图。 Xecption: 采用的Xception模型为MSRA team提出的改进的Xception，叫做Aligned Xception，并做了几点修改： 网络深度与Aligned Xception相同，不同的地方在于为了快速计算和有效的使用内存而不修改entry flow network的结构。 所有的max pooling操作替换成带stride的separable convolution，这能使得对任意分辨率的图像应用atrous separable convolution提取特征。 在每个3×3的depath-wise convolution后增加BN层和ReLU。 Decoder 在decoder中，特征图首先上采样4倍，然后与encoder中对应分辨率低级特征concatenate。在concatenate之前，由于低级特征图的通道数通常太多(256或512)，而从encoder中得到的富含语义信息的特征图通道数只有256，这样会淡化语义信息，因此在concatenate之前，需要将低级特征图通过1×1的卷积减少通道数。在concatenate之后用3×3的卷积改善特征，最后上采样4倍恢复到原始图像大小。 设计： $1 \times 1$卷积的通道数采用48 用来获得更锋利的边界的3×3的卷积。最后采用了2个3×3的卷积 使用的encoder的低级特征（Conv2） 结果： Aligned Xception改 当train_stride=16和eval_stride=8的时候mIOU最好达到了84.56% 然而计算量比较高。使用train_stride和eval_stride都为16的时候，结果下降了1.53%但是计算量下降了60倍。]]></content>
      <categories>
        <category>学习</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
        <tag>Image Segmentation</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[目标检测]]></title>
    <url>%2FLearning-Object-Detection.html</url>
    <content type="text"><![CDATA[Object detection one-stage系 two-stage系 anchor-free系 Transform Weakly Supervised YOLO V1,V2,V3,V4,V5 FPN FCOS POTO Co-Mining SSD RFCN ATSS OneNet SFOD RetinalNet Lighthead GFocal Loss v1,v2 AutoAssign BorderDet TTFNet Anchor FreeFCOSPAPER ADDRESS 出发点解决 Anchor based 算法的缺点: 检测性能对于anchor的大小，数量，长宽比都非常敏感 为了去匹配GT，需要生成大量的anchor, 造成了样本间的不平衡 在训练中，需要计算所有anchor与真实框的IOU，这样就会消耗大量内存和时间 正负样本采样方法 FCOS直接对不同feature level限制bbox不同的范围（为了解决像素点目标重叠） 每个FPN层负责不同大小框的回归, m2, m2, m4, m5, m6, m7负责0, 64, 128, 256, 512, ∞尺寸的GT 如果一个location(x, y)落到了任何一个GT box中，那么它就为正样本 如果在同一图层仍然出现了重合问题，那么还是按照该点所在最小面积ground truth检测框进行分配 Center-ness FCOS存在大量的低质量的检测框。这是由于我们把中心点的区域扩大到整个物体的边框，经过模型优化后可能有很多中心离GT box中心很远的预测框，为了增加更强的约束，作者提出了Center-ness的方法来解决这个问题，使用BCE loss对center-ness进行优化当loss越小时，centerness就越接近1，也就是说回归框的中心越接近真实框 ATSSPAPER ADDRESS 出发点Anchor-based和anchor-free检测器主要的差异是如何定义正负样本 Anchor-based和anchor-free检测器有三点不同： 特征图上每个位置的anchor数量不同 RetinaNet每个位置多个预设的anchor，FCOS每个位置一个anchor point 定义正负样本的方式不同 RetinaNet通过IOU来选择正负样本，FCOS利用空间和尺度约束来选择正负样本 FCOS相比RetinaNet的AP更高，但如果选择了相似的正负样本采样方法，anchor-based和anchor-free的方法没有显著的差异 回归的开始状态不同 RetinaNet通过anchor来回归目标，FCOS通过anchor point来回归目标（回归的开始状态并不是造成结果差异的原因） Adaptive Training Sample Selection (ATSS) 基于anchor和GT之间的中心距离来选择候选框 使用anchor和GT之间的IoU的mean和std的和作为IoU阈值 一个目标（GT）的IOU mean用于衡量和这个目标关联的anchor对它的匹配度。mean越高，表示这个目标拥有很多高质量的候选框；一个目标的IOU std用于衡量哪个层适合检测这个目标。高std表示存在某个特征层特别适合这个目标，低std表示存在多个特征层适合检测这个目标。 将mean和std加在一起作为IOU阈值，可以自适应的从合适的特征层级为每个目标选择正样本 限制正样本的中心在GT内 如果anchor的中心在GT外，那么它会使用目标外的特征来预测，对于训练帮助不大 维持不同目标采样数量的公平性 常规的采样方法倾向于采样更多的正样本，而ATSS对于每个目标大约采样$0.2KL$个正样本 BorderDetPAPER ADDRESS 出发点 对于dense object detector(e.g. FCOS、FPN的RPN)，都是使用simple point特征去预测框的分类和回归，但是发现只用一个点的特征是不够的，很难去捕捉到物体边界的信息来精准定位。这些年有很多研究通过级联的方式，希望通过引入更强的特征来增加simple point特征，主要包括GA-RPN、RepPoints等。但这些工作可能存在两个问题。 一些操作(e.g. Deformable Conv)来增强特征，但这些操作可能是冗余的，甚至会引入“有害”的背景信息。 这些方法没有显示的提取边界特征，边界极限点特征对边界框的定位比较重要。 如下图，这个运动员中心的五角星位置即为anchor点，但是确定该物体边界框的主要是边界上的四个橘色圆点，这个运动员的边界框的位置主要由四个极限点来确定。用其他的方法可能会引入一些有害的信息，且不能直接有效的提取到真正有用的边界极限点。 BAM(Border Alignment Module) 对于一个特征图，通道个数为5xC，这是一个border-sensitive的特征图，分别对应物体4个边界特征和原始anchor点位置的特征。对于一个anchor点预测的一个框，我们把这个框的4个border对应在特征图上的特征分别做pooling操作。且由于框的位置是小数，所以该操作使用双线性插值取出每个border上的特征。如图所示，我们每条边会先选出5个待采样点，再对这5个待采样点取最大的值，作为该条边的特征，即每条边最后只会选出一个采样点作为输出。那么每个anchor点都会采样5个点的特征作为输出，即输出的通道数也为5xC个。 BAM模块需要将FCOS的框位置的输出作为输入，显示的提取该框边界上的特征。最终BAM会分别预测一个border score和border reg，和原始密集检测器的输出组合成为最后的输出。 AutoAssignPAPER ADDRESS 通过生成正负权重图来动态地修改每个位置的预测，从而自动地确定正负样本。具体来说，作者提出了一个中心加权模块来调整特定类别的先验分布，并用了一个置信度加权模块来适应每个实例特定的分配策略。整个标签分配过程不需要额外的修改即可在不同数据集和任务上使用 现有检测器对正负位置采样主要是根据的人工先验: 基于anchor的检测器如RetinaNet是在每个位置预置几个不同尺度和高宽比的anchor，根据IoU值在不同空间和尺度特征图进行正负样本采样 FCOS等anchor-free检测器是选取固定比例的中心区域作为每个目标的空间正位置，并根据预定义的尺度约束选取FPN的某一层级。这些检测器都是根据目标的先验分布来设计的分配策略。 但是在现实世界中，目标外观在不同类别和场景之间差异很大。固定的中心采样策略可能会导致目标外部位置分为正值，因为在目标上采样会比在背景采样更容易得到高分类置信度。另一方面，尽管CNN可以学习偏移，但是当将背景标为正样本时，特征移动带来的干扰可能会降低性能。 因此，固定的采样策略可能并不能在空间和尺度维度上选到最合适的位置。作者提出了一种新的标签分配策略。首先遵循FCOS等anchor free方法不使用人工设计的anchor，直接预测每个位置上的目标。为了保留足够多的位置用于进一步的优化，先处理所有尺度层级的边界框（正样本+负样本）中的所有位置。然后生成正负权重图来修正训练损失中的预测。为适应不同类别和域的分布，作者提出了一个类别加权模块，center weighting，用来学习数据中每个类别的分布。为适应每个实例的外观和比例，作者又提出一个置信度加权模块(Confidence weighting)，在空间和尺度维度上修改各个位置的正、负置信度。然后将两个模块结合起来，生成所有位置的正、负权重图进行加权，加权过程是可微的，可通过反向传播进行优化。 Center Weighting先验分布是标签分配的基本要素，尤其是在训练早期。通常目标的分布会倾向于中心先验，但不同类别的目标可能会有不同的分布。保持采样中心无法去更好地捕捉现实世界中不同实例的不同分布。对不同类别的目标，更需要一种自适应的中心分布。因此基于中心先验作者提出了一种带可学习参数的高斯形状的类别级加权函数G，每一类别都有其独有的参数(μ，σ)，相同类别的目标共享这一组参数 Confidence Weighting Classification confidence给定空间位置i，其分类置信度定义为Pi(cls|θ)，目标类别概率由网络直接预测，θ表示模型参数。为确保有考虑到所有合适的位置，作者先考虑了框内所有空间位置。由于一个目标很难完全占满预测框，所以初始正集中往往会包含一部分背景。如果一个位置实际是背景，那么该位置所有的类别预测都是不合理的，将这些背景位作为正样本会有损检测性能。为了抑制来自劣质位的false positives，作者引入了一个Implicit-Obiectness分支。它的工作原理类似于RPN 和YOLO中的Objectness，主要进行前景、背景的二分类任务，但是其存在缺少显式标签的问题。RPN和YOLO采用预定义分配方式，分配一致的正标签，而Autoassign需要动态地去找到并强调那些合适的positive。Implicit-Obiectness分支会和分类分支一起去优化Objectness，因此它不需要显式标签，其实也就是用一个隐式的前景背景二分类对分类预测做一个相乘，这个分支没有额外监督，就只是单纯地去放缩一下分类的预测。 Joint confidence modeling了生成每个位置的正负无偏估计，除了分类外，还应该考虑到定位置信度。定位分支的输出是框的偏移量，这很难直接用于度量回归置信度。因此作者将定位损失$L_{i}^{cls}(\theta)$转换为回归似然Pi(loc|θ)，然后将分类和回归似然结合起来得到联合置信度Pi(θ)，联合置信度可由损失转换得到。为了不失泛化性，作者使用二元交叉篇损失（BCE）用于分类，λ用来平衡两个损失。 Weighting function positive weights 对于一个目标i，应该只关注其边界框内合适的位置做出更精准的预测。但是在训练刚开始网络参数是随机初始化的，其预测的置信度值可能并不合理。因此来自先验的指导信息也很重要。对于位置i∈Sn，作者结合了置信度权重模块C(Pi)以及中心加权模块中特定类别的先验$G(di)$来生成positive weights wi+ 如果一开始质量较差的位置的分类和回归置信度预测出来都不错，那么它的w+就会很高，也就是它权重会较大，监督训练对它的关照会不断增大，这样会导致一些好的位置没有机会翻盘，让网络学成了一个过拟合的模样。所以作者引入了G(d)，由于大部分情况下质量较高的正样本都会在框的中心，作者为每个大类学习了一个公共的高斯先验，形状基本是从物体的大致中心区域向外渐渐变弱。引入这一项可学习先验后，那些更有潜力的位置就可能能翻盘。不过这个可学习的先验仅与类别有关，可能会造成对旋转目标的不匹配。为保证竞争和合理的数值范围，还有一个类似softmax的操作，因为本来一个gt框里面也只有一部分是真正地落在物体上的，这些位置应当对应那些较大的w+值。 negative weights边界框内通常会包含一定数量的背景位置，因此我们需要使用加权的negative loss来抑制这些位置，消除false positive。此外，由于边界框内的位置一般会预测得到较高的positive置信度，作者倾向于使用定位置信度来生成false positives的无偏指标。但是负分类并没有参与回归，也就是不应该对其定位置信度做进一步优化。因此作者使用每个位置预测的proposal和GT之间的IoUs来生成负权值wi-$iou_{i}$表示位置i的propos与所有GT的IOU最大值。为作为有效权重使用，作者通过函数f归一化1/(1-ioui)到0-1之间。这种转换锐化了权值分布，并确保了IoU最高值位置的负损失为0，边界框以外所有位置的wi-设置为1，因为是背景。 Loss function通过生成正负权重图，作者实现了为每个实例动态分配更合适的空间位置且自动选择合适的FPN层级。由于权重图会对训练损失做贡献，AutoAssign以可微的方式处理标签分配，损失函数为： GFocal Loss V1PAPER ADDRESS 出发点 classification score 和 IoU/centerness score 训练测试不一致 用法不一致训练的时候，分类和质量估计各自训记几个儿的，但测试的时候却又是乘在一起作为NMS score排序的依据，这个操作显然没有end-to-end，必然存在一定的gap。 对象不一致借助Focal Loss的力量，分类分支能够使得少量的正样本和大量的负样本一起成功训练，但是质量估计通常就只针对正样本训练。 那么，对于one-stage的检测器而言，在做NMS score排序的时候，所有的样本都会将分类score和质量预测score相乘用于排序，那么必然会存在一部分分数较低的“负样本”的质量预测是没有在训练过程中有监督信号的，有就是说对于大量可能的负样本，他们的质量预测是一个未定义行为。 这就很有可能引发这么一个情况：一个分类score相对低的真正的负样本，由于预测了一个不可信的极高的质量score，而导致它可能排到一个真正的正样本（分类score不够高且质量score相对低）的前面。 bbox regression 采用的表示不够灵活，没有办法建模复杂场景下的uncertainty在复杂场景中，边界框的表示具有很强的不确定性，而现有的框回归本质都是建模了非常单一的狄拉克分布，非常不flexible。我们希望用一种general的分布去建模边界框的表示。如图所示（比如被水模糊掉的滑板，以及严重遮挡的大象） Generalized Focal Loss为了保证training和test一致，同时还能够兼顾分类score和质量预测score都能够训练到所有的正负样本，那么一个方案呼之欲出：就是将两者的表示进行联合。这个合并也非常有意思，从物理上来讲，我们依然还是保留分类的向量，但是对应类别位置的置信度的物理含义不再是分类的score，而是改为质量预测的score。这样就做到了两者的联合表示 Focal Loss$FL(p)=-(1-p_{t})^{\gamma}log(p_{t}),\ p_{t}=\left{\begin{matrix}p, when \ y=1 &amp; \1-p, when \ y=0 &amp;\end{matrix}\right.$ Quality Focal Loss$QFL(\sigma)=-\left |y-\sigma \right |^{\beta}((1-y)log(1-\sigma)+ylog(\sigma))$ 对于框的表示我们选择直接回归一个任意分布来建模框的表示。当然，在连续域上回归是不可能的，所以可以用离散化的方式，通过softmax来实现即可。这里面涉及到如何从狄拉克分布的积分形式推导到一般分布的积分形式来表示框1234567891011121314151617181920212223class Project(nn.Module): """ A fixed project layer for distribution """ def __init__(self, reg_max=16): super(Project, self).__init__() self.reg_max = reg_max self.register_buffer("project", torch.linspace(0, self.reg_max, self.reg_max + 1)) def forward(self, x): """Forward feature from the regression head to get integral result of bounding box location. Args: x (Tensor): Features of the regression head, shape (N, 4*(n+1)), n is self.reg_max. Returns: x (Tensor): Integral result of box locations, i.e., distance offsets from the box center in four directions, shape (N, 4). """ x = F.softmax(x.reshape(-1, self.reg_max + 1), dim=1) x = F.linear(x, self.project.type_as(x)).reshape(-1, 4) return x 对于任意分布来建模框的表示，它可以用积分形式嵌入到任意已有的和框回归相关的损失函数上，例如最近比较流行的GIoU Loss。但是如果分布过于任意，网络学习的效率可能会不高，原因是一个积分目标可能对应了无穷多种分布模式。 考虑到真实的分布通常不会距离标注的位置太远，所以我们又额外加了个loss，希望网络能够快速地聚焦到标注位置附近的数值，使得他们概率尽可能大。 Distribution Focal Loss$DFL(S_{i}, S_{i+1})=-((y_{i+1}-y)log(S_{i})+(y-y_{i})log(S_{i+1}))$ 其形式上与QFL的右半部分很类似，含义是以类似交叉熵的形式去优化与标签y最接近的一左一右两个位置的概率，从而让网络快速地聚焦到目标位置的邻近区域的分布中去。 extra有一些分布式表示学到了多个峰，比如伞这个物体，它的伞柄被椅子严重遮挡。如果我们不看伞柄，那么可以按照白色框（gt）来定位伞，但如果我们算上伞柄，我们又可以用绿色框（预测）来定位伞。 在分布上，它也的确呈现一个双峰的模式（bottom），它的两个峰的概率会集中在底部的绿线和白线的两个位置。这个观察还是相当有趣的。 这可能带来一个妙用，就是我们可以通过分布shape的情况去找哪些图片可能有界定很模糊的边界，从而再进行一些标注的refine或一致性的检查等等。 GFocal Loss V2PAPER ADDRESS 用边界框的不确定性的统计量来高效地指导定位质量估计 a.point:单点特征做增强 b.region:用ROIAlign提取框内所有特征来增强 c.boder:使用边界上所有点的特征来增强 d.middle border:只用边界中心点来增强 e.border align:边界极限点特征对边界框的定位 f.regular sampling points: g.deformable sampling points: 使用可变性卷积 出发点在GFocalV1中，对边界框进行一个一般化的分布表示建模后基本上那些非常清晰明确的边界，它的分布都很尖锐；而模糊定义不清的边界，它们学习到的分布基本上会平下来，而且有的时候还经常出现双峰的情况。 既然分布的形状和真实的定位质量非常相关，因此用能够表达分布形状的统计量去指导最终定位质量的估计。 对GFLV1做了一些统计分析，具体把预测框的分布的top-1值和其真实的IoU定位质量做了一个散点图，可以看出，整个散点图还是有一个明显地倾向于y=x的趋势的，也就是说，在统计意义上，“分布的形状与真实的定位质量具有较强的相关性”这个假设是基本成立的 v2直接取学习到的分布（分布是用离散化的多个和为1的回归数值表示的Topk数值。因为所有数值和为1，如果分布非常尖锐的话，Topk这几个数通常就会很大；反之Topk就会比较小。选择Topk还有一个重要的原因就是它可以使得特征与对象的scale尽可能无关，如下图所示简单来说就是长得差不多形状的分布要出差不多结果的数值，不管它峰值时落在小scale还是大scale。把4条边的分布的Topk concat在一起形成一个维度非常低的输入特征向量（可能只有10+或20+），用这个向量再接一个非常小的fc层（通常维度为32、64），最后再变成一个Sigmoid之后的scalar乘到原来的分类表征中就可以了 其他算法里面也有非常准的预测框，但是它们的score通常都排到了第3第4的位置，而score排第一的框质量都比较欠佳。相反，GFLV2也有预测不太好的框，但是质量较高的框都排的非常靠前. TTFNetPAPER ADDRESS 出发点现有的目标检测很少能同时达到训练时间短、推理速度快、精度高等目的: 由于模型简化而难以训练，在很大程度上依赖于数据增强和较长的训练时间。例如centernet需要在公共数据集MSCOCO上进行140个epochs训练。 正负样本采样方法 从BBOX框中编码更多的训练样本，主要是增加高质量正样本数，与增加批量大小具有相似的作用，这有助于扩大学习速度并加快训练过程。主要是通过高斯核函数实现。其实很简单，我们知道centernet的回归分支仅仅在中心点位置计算loss，其余位置忽略，这种做法就是本文说的训练样本太少了，导致收敛很慢，需要特别长的训练时间，而本文采用高斯核编码形式，对中心点范围内满足高斯分布范围的点都当做正样本进行训练，可以极大的加速收敛。本文做法和FCOS非常类似，但是没有多尺度输出，也没有FPN等复杂结构，比较像简化版本的FCOS，属于实践性很强的算法。 将高斯分布概率作为样本权重来加权那些靠近目标中心的样本 结构 TTFNet使用ResNet和DarkNet作为主干网络。 主干网络提取的特征被采样到原始图像的1/4分辨率，这是通过Modulated Deformable Convolution(MDCN)和上采样层实现的。在MDCN层之后是批归一化(BN)和ReLU。上采样的特征然后分别通过两个头部为不同的目标。 检测头在物体中心附近的位置产生高激活，而回归头直接预测从这些位置到box四面的距离。由于目标中心对应于特征映射处的局部最大值，因此可以在2D最大池的帮助下安全地抑制非最大值。然后利用局部最大值的位置来收集回归结果。 最后，可以得到检测结果。 新提出的方法有效地使用了大中型目标中包含的注释信息，但对于包含很少信息的小目标，推广是有限的。为了在较短的训练计划中提高小目标的检测性能，添加了shortcut connections来引入高分辨率但低级别的特征。shortcut connections从主干的2级，3级和4级引入特征，每个连接由3×3卷积层实现。第二、第三和第四阶段的层数设置为3、2和1，除了shortcut connections中的最后一层外，ReLU遵循在每个层。 Weakly SupervisedCo-MiningSelf-Supervised Learning for Sparsely Annotated Object DetectionPAPER ADDRESS 在训练阶段使用共享参数的双检测器分别对原始图片以及增强图片做预测后使用各自预测的正样本(Co-Generation)交叉生成更复杂(完整)的GT来指导网络学习, 属于半监督中解决SAOD问题的。 SAOD: Sparsely Annotated Object DetectionSSOD: Semi-Supervised Object Detection 在coco数据集中进行了标注的随机擦除easy类标注随机删除一个, hard类随机删除一半, extrme类保留一个标注 在SAOD中的增强仅在色域上进行 SFODA Free Lunch for Unsupervised Domain Adaptive Object Detection without Source DataPAPER ADDRESS 本文首次提出了一种data-free 域自适应目标检测（SFOD）框架，方法是将其建模为带有噪声标签的学习问题。 方法是将其建模为带有噪声标签的学习问题。由于目标域中没有可用的标签，因此很难评估伪标签的质量。在本文中，自熵下降（SED）是一种度量标准，旨在在不使用任何手工标签的情况下搜索适当的置信度阈值以可靠地生成伪标签。尽管如此，仍然无法获得完全清洁的标签。经过全面的实验分析，发现false negatives在所产生的噪声标签中占主导地位。毫无疑问，挖掘FN有助于提高性能，通过像Mosaic这样的数据增强将简单TP其简化为FN Simulation。在四个有代表性的适应任务中进行的广泛实验表明，所提出的框架可以轻松实现最新性能。 SED噪声数据是难以拟合的，可以通过计算SED来判断当前的伪标签是否具有置信度以及搜索出一个最佳的confidence threshold. False Negatives Simulation在实验中发现占比做多的为FN， 且表现为小目标及遮挡严重的物体，因此使用增强(马赛克增强)来将容易识别的物体转化为FN的表现形式。 项目中如果有大量未标注数据，可以在标注数据预训练好检测模型后使用SFOD在大量的未标注数据中进行无监督的域自适应训练，或者用于快速拟合相似场景的模型 TransformerOneNetan effective baseline for endto-end one-stage object detectionPAPER ADDRESS OneNet将classification cost加入到location cost中，可以去除后续的NMS； 样本匹配策略是one-to-one，即一个gt一个正样本，其他都是负样本； Backbone: Backbone是先bottom-up再top-down的结构。其中，bottom-up结构是resnet，top-down结构是FPN。Head: Head是两个并行的conv，分类conv预测类别，回归conv预测到物体框的4个边界的距离。Output: 直接取top-k高分框，没有NMS，也没有类似CenterNet中max-pooling的操作 Label AssignmentLabel Assignment（样本选择策略）的cost定义为样本与gt的classification cost(loss)和location cost(loss)之和，即： POTOEnd-to-End Object Detection with Fully Convolutional NetworkPAPER ADDRESS 基于全卷积网络的主流目标检测器大多数仍然需要非最大抑制(NMS)后处理，在这篇文章中，我们不再使用NMS，为此，对于完全卷积检测器，我们引入了Prediction-aware One-To-One：POTO label assignment for classification，以实现端到端检测，获得与NMS相当的性能。此外，提出了一种简单的3D Max Filtering（3DMF），利用多尺度特征，提高局部区域卷积的可分辨性。 Prediction-aware One-to-one Label Assignment 使用固定的hand-designed one-to-one label assignment得到的location往往不是最优的，因此，这种强迫式地分配会使得网络收敛难度增加，同时造成更多的False-positive预测。作者这里根据Prediction的质量来进行label assignment。 目标检测的损失函数为: 其中，$\Psi$表示所有预测的索引集(the index set of all the predictions), $N$ 和 $G$分别表示Prediction bounding boxes的数量，Ground Truth bounding boxes 的数量, $L_{fg}$表示前景损失，$L_{bg}$表示背景损失。$c_{i}$, $b_{i}$分别是Ground Truth的类别标签以及回归坐标，与之对应着的$\hat{p}{\hat{\pi}(i)}$和$\hat{b}{\hat{\pi}(i)}$是预测类别分数以及预测的bounding boxes坐标。 作者这里选取label assignment的指标为： 前人的工作中通过使用foreground loss将其看成是一个biparticle matching problem(二分匹配问题)，使用Hungarian algorithm求解,但是foreground loss通常需要额外的权重来减轻优化问题 这里，作者采用的方式是(POTO）来获得一个更好的assignment 其中，$Q_{i, \pi(i)} \in [0,1]$表示第i个Ground-Truth 和我们选择的作为第i个label assignment $\pi(i)$之间的匹配质量。其中考虑了空间先验，分类的置信度以及回归的质量。$\Omega_{i}$表示第i个地面真值的候选预测集，即空间先验,同时对classification和regression利用$\alpha$进行了加权几何平均数 3D Max Filtering 作者发现重复预测主要来自最可靠预测的邻近空间区域 3D Max Filtering能够变换FPN多个尺度的特征，特征图中的每一个通道分别采用3D最大值滤波。 Auxiliary Loss使用了POTO以及3DMF得到的表现性能依旧不如FCOS baseline，这种现象可能是由于一对一的标签分配提供较少的监督，使得网络难以学习强有力的特征表示造成的.作者这里引入auxiliary loss来增强学习特征表示的能力。auxiliary loss采用Focal loss和改进的一对多标签分配，具体来说，根据之前公式(4)$Q_{i, \pi(i)} \in [0,1]$建议的匹配质量， one-to-many label assignment 首先选出前9个预测作为每个FPN阶段的候选，然后，它将候选对象指定为匹配质量超过统计阈值的前景样本，这个统计阈值是通过the summation of the mean and the standard deviation of all the candidate matching qualities来计算出来的. 从上图中可以看出，FCOS baseline有一对多的分配中心输出大量的重复预测，很多位置的置信度分数较高，这些重复的预测被评估为假阳性样本，并极大地影响性能。相反，通过使用所提出的POTO规则，重复样本的分数被显著抑制。在引入3DMF后，达到更好的效果，这是由于3DMF模块引入了多尺度竞争机制，检测器可以在不同的FPN阶段很好地执行独特的预测 One StageYOLO V1You only look once unified real-time object detection PAPER ADDRESS 作者在YOLO算法中把物体检测（object detection）问题处理成回归问题，用一个卷积神经网络结构就可以从输入图像直接预测bounding box和类别概率。 优点: YOLO的速度非常快。在Titan X GPU上的速度是45 fps, 加速版155 fps。 YOLO是基于图像的全局信息进行预测的。这一点和基于sliding window以及region proposal等检测算法不一样。与Fast R-CNN相比，YOLO在误检测（将背景检测为物体）方面的错误率能降低一半多。 泛化能力强。 缺点: accuracy 还落后于同期 state-of-the-art 目标检测方法。 难于检测小目标。 定位不够精准。 虽然降低了背景检测为物体的概率但同事导致了召回率较低。 流程 调整图像大小至$448\times448$. 运行卷积网络同时预测多目标的边界框和所属类的概率 NMX(非极大值抑制) Unified Detection 将图片分为$S\times S$格子。 对每个格子都预测B个边界框并且每个边界框包含5个预测值：x,t,w,h以及confidence。x,y就是bounding box的中心坐标，与grid cell对齐（即相对于当前grid cell的偏移值），使得范围变成0到1；w和h进行归一化（分别除以图像的w和h，这样最后的w和h就在0到1范围。 每个格子都预测C个假定类别的概率。 在Pascal VOC中， S=7,B=2,C=20. 所以有 $S\times S\times (B \times 5 + C)$ 即 $7\times 7\times 30$ 维张量。 Confidence计算：$Pr(Object) * IOU_{pred}^{turth} $ 每个bounding box都对应一个confidence score，如果grid cell里面没有object，confidence就是0，如果有，则confidence score等于预测的box和ground truth的IOU值，见上面公式。并且如果一个object的ground truth的中心点坐标在一个grid cell中，那么这个grid cell就是包含这个object，也就是说这个object的预测就由该grid cell负责。每个grid cell都预测C个类别概率，表示一个grid cell在包含object的条件下属于某个类别的概率：$Pr(Class_{i}|Object)$ 每个bounding box的confidence和每个类别的score相乘，得到每个bounding box属于哪一类的confidence score。 即得到每个bounding box属于哪一类的confidence score。也就是说最后会得到20*(7*7*2)的score矩阵，括号里面是bounding box的数量，20代表类别。接下来的操作都是20个类别轮流进行：在某个类别中（即矩阵的某一行），将得分少于阈值（0.2）的设置为0，然后再按得分从高到低排序。最后再用NMS算法去掉重复率较大的bounding box（NMS:针对某一类别，选择得分最大的bounding box，然后计算它和其它bounding box的IOU值，如果IOU大于0.5，说明重复率较大，该得分设为0，如果不大于0.5，则不改；这样一轮后，再选择剩下的score里面最大的那个bounding box，然后计算该bounding box和其它bounding box的IOU，重复以上过程直到最后）。最后每个bounding box的20个score取最大的score，如果这个score大于0，那么这个bounding box就是这个socre对应的类别（矩阵的行），如果小于0，说明这个bounding box里面没有物体，跳过即可。 网络设计灵感来源于GoogLeNet,如下图：训练过程中： 作者先在ImageNet数据集上预训练网络，而且网络只采用图中的前面20个卷积层，输入是224*224大小的图像。然后在检测的时候再加上随机初始化的4个卷积层和2个全连接层，同时输入改为更高分辨率的448*448。 Relu层改为leaky Relu，即当x&lt;0时，激活值是0.1*x，而不是传统的0。 作者采用sum-squared error的方式把localization error（bounding box的坐标误差）和classificaton error整合在一起。但是如果二者的权值一致，容易导致模型不稳定，训练发散。因为很多grid cell是不包含物体的，这样的话很多grid cell的confidence score为0。所以采用设置不同权重方式来解决，一方面提高localization error的权重，另一方面降低没有object的box的confidence loss权值，loss权重分别是5和0.5。而对于包含object的box的confidence loss权值还是原来的1。 用宽和高的开根号代替原来的宽和高，这样做主要是因为相同的宽和高误差对于小的目标精度影响比大的目标要大. Loss Function如下： 训练的时候：输入N个图像，每个图像包含M个objec，每个object包含4个坐标（x，y，w，h）和1个label。然后通过网络得到7*7*30大小的三维矩阵。每个1*30的向量前5个元素表示第一个bounding box的4个坐标和1个confidence，第6到10元素表示第二个bounding box的4个坐标和1个confidence。最后20个表示这个grid cell所属类别。注意这30个都是预测的结果。然后就可以计算损失函数的第一、二 、五行。至于第二三行，confidence可以根据ground truth和预测的bounding box计算出的IOU和是否有object的0,1值相乘得到。真实的confidence是0或1值，即有object则为1，没有object则为0。 这样就能计算出loss function的值了。 测试的时候：输入一张图像，跑到网络的末端得到7*7*30的三维矩阵，这里虽然没有计算IOU，但是由训练好的权重已经直接计算出了bounding box的confidence。然后再跟预测的类别概率相乘就得到每个bounding box属于哪一类的概率。 YOLO效果由于yolo更少的识别背景为物体对比Faster RCNN,因此结合YOLO作为背景检测器与Faster RCNN可以带来更大的提升，不过速度方面就没有优势了。 YOLO V2 and YOLO 9000论文地址 与分类和标记等其他任务的数据集相比，目前目标检测数据集是有限的。最常见的检测数据集包含成千上万到数十万张具有成百上千个标签的图像。分类数据集有数以百万计的图像，数十或数十万个类别。为了扩大当前检测系统的范围。我们的方法使用目标分类的分层视图，允许我们将不同的数据集组合在一起。此外联合训练算法，使我们能够在检测和分类数据上训练目标检测器。我们的方法利用标记的检测图像来学习精确定位物体，同时使用分类图像来增加词表和鲁棒性。 对比YOLO V1的改进 YOLO有许多缺点。YOLO与Fast R-CNN相比的误差分析表明，YOLO造成了大量的定位误差。此外，与基于区域提出的方法相比，YOLO召回率相对较低。因此，我们主要侧重于提高召回率和改进定位，同时保持分类准确性。 在YOLOv2中，一个更精确的检测器被设计出来，它仍然很快。但是不是通过扩大网络，而是简化网络，然后让其更容易学习。结合了以往的一些新方法，以提高YOLO的性能： Batch Normalization: map提升2% High Resolution Classifier: 先在ImageNet上以448×448的分辨率对分类网络进行10个迭代周期的微调。这给了网络时间来调整其滤波器以便更好地处理更高分辨率的输入。map提升4%, Convolutional With Anchor Boxes: 从YOLO中移除全连接层，并使用锚盒来预测边界框。首先，我们消除了一个池化层，使网络卷积层输出具有更高的分辨率。我们还缩小了网络，操作416×416的输入图像而不是448×448。我们这样做是因为我们要在我们的特征映射中有奇数个位置，所以只有一个中心单元。目标，特别是大目标，往往占据图像的中心，所以在中心有一个单独的位置来预测这些目标，而不是四个都在附近的位置是很好的。YOLO的卷积层将图像下采样32倍，所以通过使用416的输入图像，我们得到了13×13的输出特征映射。map有所下降但是召回率达到了88%. Dimension Clusters: Anchors 尺寸的选择用k-means聚类来挑选合适的锚盒尺寸。如果我们使用具有欧几里得距离的标准k-means，那么较大的边界框比较小的边界框产生更多的误差。然而，我们真正想要的是导致好的IOU分数的先验，这是独立于边界框大小的。因此，对于我们的距离度量，我们使用：$d(box,centroid)=1-IOU(box,centroid)$. 在voc和coco的测试中，更薄更高的边界框会带来更好的结果在k=5的时候，如图所示： Direct location prediction: 传统的RPN中，特别是在早期的迭代过程中。大部分的不稳定来自预测边界框的(x,y)位置，他的位置修正方法是不受限制的，所以任何锚盒都可以在图像任一点结束，而不管在哪个位置预测该边界框。随机初始化模型需要很长时间才能稳定以预测合理的偏移量。所以这一步就优化为直接预测相对于网格单元位置的位置坐标。逻辑激活备用来限制网络的预测落在这个范围内。Sigmoid使输出在0~1之间这样映射到原图中时候不会位于其他的网格（在中心目标处）。网络预测输出特征映射中每个单元的5个边界框。网络预测每个边界框的5个坐标，$t_{x}$,$t_{y}$,$t_{w}$,$t_{h}$,$t_{o}$,如果单元从图像的左上角偏移了$(c_{x},c_{y})$,并且边界框先验的宽度和高度为$p_{w}$,$p_{h}$. 预测就可以对应如图公式计算。$Pr(object)\times IOU(b,object)=\sigma(t_{o}) $ 该方法结合维度聚类，map 提升了5%对比与其他的锚盒方法。 Fine-Grained Features(细粒度特征): 对于小目标物体，更细的力度特种可以带来更好的效果，因此直通层通过将相邻特征堆叠到不同的通道而不是空间位置来连接较高分辨率特征和较低分辨率特征，类似于ResNet中的恒等映射。这将26×26×512特征映射变成13×13×2048特征映射，其可以与原始特征连接。我们的检测器运行在这个扩展的特征映射的顶部，以便它可以访问细粒度的特征。这会使性能提高1%。 Multi-Scale Training: 由于模型只使用卷积层和池化层，因此它可以实时调整大小。所以每隔10个批次会随机选择一个新的图像尺寸大小（330到608，从32的倍数中选择因为模型缩减了32倍），强迫网络学习在不同维度上预测，并且小尺度的网络运行更快。 Darknet-19， 它有19个卷积层和5个最大池化层并且只需要55.8亿次运算处理图像获得了比复杂运算VGG和前一代YOLO更高的top-5精度在ImageNet上，结构如下： 分类训练：使用Darknet神经网络结构，使用随机梯度下降，初始学习率为0.1，学习率多项式衰减系数为4，权重衰减为0.0005，动量为0.9，在标准ImageNet 1000类分类数据集上训练网络160个迭代周期。在训练过程中，标准的数据增强技巧，包括随机裁剪，旋转，色调，饱和度和曝光偏移被使用来防止over-fitting. 在在对224×224的图像进行初始训练之后，对网络在更大的尺寸448上进行了微调。 检测训练：删除了最后一个卷积层，加上了三个具有1024个滤波器的3×3卷积层，其后是最后的1×1卷积层与我们检测需要的输出数量。对于VOC，我们预测5个边界框，每个边界框有5个坐标和20个类别，所以有125个滤波器。还添加了从最后的3×3×512层到倒数第二层卷积层的直通层，以便模型可以使用细粒度特征。 联合训练分类和检测数据： 网络看到标记为检测的图像时，可以基于完整的YOLOv2损失函数进行反向传播。当它看到一个分类图像时，只能从该架构的分类特定部分反向传播损失。 Hierarchical classification(分层分类)：ImageNet标签是从WordNet中提取的，这是一个构建概念及其相互关系的语言数据库，在这里通过构建简单的分层树简化问题。最终的结果是WordTree，一个视觉概念的分层模型。为了使用WordTree进行分类，我们预测每个节点的条件概率，以得到同义词集合中每个同义词下义词的概率。如果想要计算一个特定节点的绝对概率，只需沿着通过树到达根节点的路径，再乘以条件概率。可以使用WordTree以合理的方式将多个数据集组合在一起。只需将数据集中的类别映射到树中的synsets即可。 YOLO 9000（anchors尺寸3个限制输出大小）: 使用COCO检测数据集和完整的ImageNet版本中的前9000个类来创建的组合数据集。该数据集的相应WordTree有9418个类别。ImageNet是一个更大的数据集，所以通过对COCO进行过采样来平衡数据集，使得ImageNet仅仅大于4:1的比例。当分析YOLO9000在ImageNet上的表现时，发现它很好地学习了新的动物种类，但是却在像服装和设备这样的学习类别中挣扎。新动物更容易学习，因为目标预测可以从COCO中的动物泛化的很好。相反，COCO没有任何类型的衣服的边界框标签，只针对人，因此效果不好3 YOLO V3论文地址模型: 改进： 将YOLO V3替换了V2中的Softmax loss变成Logistic loss(每个类一个logistic)，而且每个GT只匹配一个先验框. Anchor bbox prior不同：V2用了5个anchor，V3用了9个anchor，提高了IOU. Detection的策略不同：V2只有一个detection，V3设置有3个，分别是一个下采样的，Feature map为13*13，还有2个上采样的eltwise sum(feature pyramid networks)，Feature map分别为26\26和52\52，也就是说，V3的416版本已经用到了52的Feature map，而V2把多尺度考虑到训练的data采样上，最后也只是用到了13的Feature map，这应该是对小目标影响最大的地方。总结： 网络改进 DarkNet-53: 融合了YOLOv2、Darknet-19以及其他新型残差网络，由连续的3×3和1×1卷积层组合而成，当然，其中也添加了一些shortcut connection，整体体量也更大。因为一共有53个卷积层。 LOSS: 除了w, h的损失函数依然采用总方误差之外，其他部分的损失函数用的是二值交叉熵。最后加到一起12345678910xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[..., 0:2], from_logits=True)wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh - raw_pred[..., 2:4])confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) + (1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) * ignore_maskclass_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[..., 5:], from_logits=True)xy_loss = K.sum(xy_loss) / mfwh_loss = K.sum(wh_loss) / mfconfidence_loss = K.sum(confidence_loss) / mfclass_loss = K.sum(class_loss) / mfloss += xy_loss + wh_loss + confidence_loss + class_loss 结果: YOLO V3在Pascal Titan X上处理608x608图像速度达到20FPS，在 COCO test-dev 上 mAP@0.5 达到 57.9%，与RetinaNet的结果相近，并且速度快了4倍。 YOLO V3的模型比之前的模型复杂了不少，可以通过改变模型结构的大小来权衡速度与精度。 速度对比如下： 失败的尝试： Anchor box坐标的偏移预测 用线性方法预测x,y，而不是使用逻辑方法 focal loss 双IOU阈值和真值分配 YOLO V4论文地址模型: CBM: Yolov4网络结构中的最小组件，由Conv+Bn+Mish激活函数三者组成。 CBL: 由Conv+Bn+Leaky_relu激活函数三者组成。 Res unit: 借鉴Resnet网络中的残差结构，让网络可以构建的更深。 CSPX: 借鉴CSPNet网络结构，由卷积层和X个Res unint模块Concate组成。 SPP: 采用1×1，5×5，9×9，13×13的最大池化的方式，进行多尺度融合。 每个CSPX中包含3+2X个卷积层，因此整个主干网络Backbone中一共包含$2+（3+21）+2+（3+22）+2+（3+28）+2+（3+28）+2+（3+24）+1=72$。 评估过的技术 改进: 输入端: Mosaic数据增强、cmBN、SAT自对抗训练、类别标签平滑化 Backbone: CSPDarknet53、Mish激活函数、Dropblock Nect: SPP模块、PAN结构 Prediction: CmBN、最优超参数、CIOU_Loss，DIOU_nms mosaic:pro: 丰富数据集：随机使用4张图片，随机缩放，再随机分布进行拼接，大大丰富了检测数据集，特别是随机缩放增加了很多小目标，让网络的鲁棒性更好。 CSPNet:CSPNet的作者认为推理计算过高的问题是由于网络优化中的梯度信息重复导致的。 因此采用CSP模块先将基础层的特征映射划分为两部分，然后通过跨阶段层次结构将它们合并，在减少了计算量的同时可以保证准确率。 因此Yolov4在主干网络Backbone采用CSPDarknet53网络结构，主要有三个方面的优点： 增强CNN的学习能力，使得在轻量化的同时保持准确性。 降低计算瓶颈 降低内存成本 Mish:正值可以达到任何高度, 避免了由于封顶而导致的饱和。理论上对负值的轻微允许更好的梯度流，而不是像ReLU中那样的硬零边界。 Dropblock:卷积层对于dropout丢弃并不敏感，因为卷积层通常是三层连用：卷积+激活+池化层，池化层本身就是对相邻单元起作用。而且即使随机丢弃，卷积层仍然可以从相邻的激活单元学习到相同的信息。 SPP:采用SPP模块的方式，比单纯的使用k*k最大池化的方式，更有效的增加主干特征的接收范围，显著的分离了最重要的上下文特征。在SPP模块中，使用k=[1x1, 5x5, 9x9, 13x13]的最大池化的方式，再将不同尺度的特征图进行Concat操作。 PAN:PAN模型也叫金字塔注意力模型，主要由FPA(特征金字塔注意力模块)和GAU两个模型组成 FPA该模块能够融合来自 U 型网络 (如特征金字塔网络 FPN) 所提取的三种不同尺度的金字塔特征 GAUGAU是用在decode时候的单元，并且引入注意力机制 yolov4包含两个PAN结构，并且只取最后一个特征图，FPN层自顶向下传达强语义特征，而特征金字塔则自底向上传达强定位特征，两两联手，从不同的主干层对不同的检测层进行参数聚合。 IOU LOSS:Bounding Box Regeression的Loss近些年的发展过程是：Smooth L1 Loss-&gt; IoU Loss（2016）-&gt; GIoU Loss（2019）-&gt; DIoU Loss（2020）-&gt;CIoU Loss（2020） IOU loss pro: IoU损失将位置信息作为一个整体进行训练, 对比L2损失对4个独立变量进行训练能得到更为准确的效果. con: 当预测框和目标框不相交时，IoU(A,B)=0时，不能反映A,B距离的远近，此时损失函数不可导，IoU Loss 无法优化两个框不相交的情况。 假设预测框和目标框的大小都确定，只要两个框的相交值是确定的，其IoU值是相同时，IoU值不能反映两个框是如何相交的。 GIOU loss找到一个最小的外接矩形C，让C可以将A和B包围在里面，然后我们计算C中没有覆盖A和B的面积占C总面积的比例，然后用A和B的IOU值减去这个比值 pro: 预测框和目标不相交的情况下也可以进行优化，且对物体的尺度大小不敏感(因为比值的原因) con: 当目标框完全包裹预测框的时候，IoU和GIoU的值都一样，此时GIoU退化为IoU, 无法区分其相对位置关系 DIOU loss通常基于IoU-based的loss可以定义为$L=1-IOU+R(B,B^{gt})$其中$R(B,B^{gt})$)定义为预测框$B$和目标框$B^{gt}$的惩罚项。$DIoU=IoU - \frac{p^{2}(b,b^{gt})}{c^{2}}$其中，$b$和$b^{gt}$分别代表了预测框和真实框的中心点，且代表的是计算两个中心点间的欧式距离。$c$代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。 pro: 即使目标框完全包裹预测框的时候也可以进行优化 将目标与anchor之间的距离，重叠率以及尺度都考虑进去，使得目标框回归变得更加稳定，不会像IoU和GIoU一样出现训练过程中发散等问题 DIoU还可以替换普通的IoU评价策略，应用于NMS中，使得NMS得到的结果更加合理和有效。 con: 没有考虑预测框的长宽比，当预测框与目标框中心点距离相同时无法进行优化 CIOU loss$CIoU=IoU - \frac{p^{2}(b,b^{gt})}{c^{2}} - \alpha v$$v=\frac{4}{\pi^{2}(arctan(w^{gt}/h^{gt})-arctan(w/h))^{2}}$$\alpha = v / ((1-IoU)+v)$其中，$\alpha$是用于做trade-off的权重函数，$v$是用来衡量长宽比一致性的参数 pro: 目标框回归函数应该考虑三个重要几何因素：重叠面积、中心点距离，长宽比全都考虑进去了 结果: YOLO V5代码地址模型: Backbone Focus结构这个其实就是yolov2里面的ReOrg+Conv操作，也是亚像素卷积的反向操作版本，简单来说就是把数据切分为4份，每份数据都是相当于2倍下采样得到的，然后在channel维度进行拼接，最后进行卷积操作。以Yolov5s的结构为例，原始$6086083$的图像输入Focus结构，采用切片操作，先变成$30430412$的特征图，再经过一次32个卷积核的卷积操作，最终变成$30430432$的特征图 CSP结构Yolov5与Yolov4不同点在于，Yolov4中只有主干网络使用了CSP结构。而Yolov5中设计了两种CSP结构，以Yolov5s网络为例，CSP1_X结构应用于Backbone主干网络，另一种CSP2_X结构则应用于Neck中。 Neck结构Yolov5和Yolov4的不同点在于，Yolov4的Neck结构中，采用的都是普通的卷积操作。而Yolov5的Neck结构中，采用借鉴CSPnet设计的CSP2结构，加强网络特征融合的能力。 结构参数yolov5通过灵活的配置参数，可以得到不同复杂度的模型 Yolov5sdepth_multiple: 0.33width_multiple: 0.50 Yolov5mdepth_multiple: 0.67width_multiple: 0.75 Yolov5ldepth_multiple: 1.0width_multiple: 1.0 Yolov5xdepth_multiple: 1.33width_multiple: 1.25 Anchor匹配策略在诸多论文研究中表明，例如FCOS和ATSS：增加高质量正样本anchor可以显著加速收敛。yolov5也采用了增加正样本anchor数目的做法来加速收敛，这其实也是yolov5在实践中表明收敛速度非常快的原因。其核心匹配规则为： 对于任何一个输出层，抛弃了基于max iou匹配的规则，而是直接采用shape规则匹配，也就是该bbox和当前层的anchor计算宽高比，如果宽高比例大于设定阈值，则说明该bbox和anchor匹配度不够，将该bbox过滤暂时丢掉，在该层预测中认为是背景 对于剩下的bbox，计算其落在哪个网格内，同时利用四舍五入规则，找出最近的两个网格，将这三个网格都认为是负责预测该bbox的，可以发现粗略估计正样本数相比前yolo系列，至少增加了三倍 因此不同于yolov3和v4， 其gt bbox可以跨层预测即有些bbox在多个预测层都算正样本 其gt bbox的匹配数范围从3-9个,明显增加了很多正样本 *有些gt bbox由于和anchor匹配度不高，而变成背景虽然可以加速收敛，但是由于引入了很多低质量anchor，对最终结果还是有影响的 自适应anchor计算在Yolo算法中，针对不同的数据集，都会有初始设定长宽的锚框。在网络训练中，网络在初始锚框的基础上输出预测框，进而和真实框groundtruth进行比对，计算两者差距，再反向更新，迭代网络参数。 结果: SSD: Single Shot MultiBox Detector论文地址 SSD将边界框的输出空间离散化为不同长宽比的一组默认框和并缩放每个特征映射的位置。在预测时，网络会在每个默认框中为每个目标类别的出现生成分数，并对框进行调整以更好地匹配目标形状。此外，网络还结合了不同分辨率的多个特征映射的预测，自然地处理各种尺寸的目标。 改进: 针对多个类别的单次检测器 预测固定的一系列默认边界框的类别分数和边界框偏移，使用更小的卷积滤波器应用到特征映射上 根据不同尺度的特征映射生成不同尺度的预测，并通过纵横比明确分开预测 在低分辨率输入图像上也能实现简单的端到端训练和高精度，从而进一步提高速度与精度之间的权衡。 模型:SSD方法基于前馈卷积网络，该网络产生固定大小的边界框集合，并对这些边界框中存在的目标类别实例进行评分，然后进行非极大值抑制步骤来产生最终的检测结果。早期的网络层基于用于高质量图像分类的标准架构将其称为基础网络。然后，将辅助结构添加到网络中以产生具有以下关键特征的检测： 用于检测的多尺度特征映射。我们将卷积特征层添加到截取的基础网络的末端。这些层在尺寸上逐渐减小，并允许在多个尺度上对检测结果进行预测。用于预测检测的卷积模型对于每个特征层都是不同的 用于检测的卷积预测器。每个添加的特征层（或者任选的来自基础网络的现有特征层）可以使用一组卷积滤波器产生固定的检测预测集合。 默认边界框和长宽比。默认边界框与Faster R-CNN[2]中使用的锚边界框相似，但是我们将它们应用到不同分辨率的几个特征映射上。在几个特征映射中允许不同的默认边界框形状可以有效地离散可能的输出框形状的空间。 总结： 末尾添加的特征层预测不同尺度的长宽比的默认边界框的偏移量以及相关的置信度。PS: 空洞版本VGG更快。 训练： 匹配策略：默认边界框匹配到IOU重叠高于阈值（0.5）的任何实际边界框。这简化了学习问题，允许网络为多个重叠的默认边界框预测高分，而不是要求它只挑选具有最大重叠的一个边界框。 训练目标函数：定位损失加上置信度损失 为默认边界框选择尺度和长宽比: 难例挖掘: 在匹配步骤之后，大多数默认边界框为负例，尤其是当可能的默认边界框数量较多时。这在正的训练实例和负的训练实例之间引入了显著的不平衡。不使用所有负例，而是使用每个默认边界框的最高置信度损失来排序它们，并挑选最高的置信度，以便负例和正例之间的比例至多为3:1。 数据增强 小目标数据增强： 将图像随机放置在填充了平均值的原始图像大小为16x的画布上，然后再进行任意的随机裁剪操作。因为通过引入这个新的“扩展”数据增强技巧，有更多的训练图像，所以必须将训练迭代次数加倍。 优劣:SSD对类似的目标类别（特别是对于动物）有更多的混淆，部分原因是共享多个类别的位置。SSD对边界框大小非常敏感。换句话说，它在较小目标上比在较大目标上的性能要差得多。这并不奇怪，因为这些小目标甚至可能在顶层没有任何信息。增加输入尺寸（例如从300×300到512×512）可以帮助改进检测小目标，但仍然有很大的改进空间。积极的一面，SSD在大型目标上的表现非常好。而且对于不同长宽比的目标，它是非常鲁棒的，因为使用每个特征映射位置的各种长宽比的默认框。 RetinaNet Focal Loss for Dense Object Detection论文地址 稠密分类的后者精度不够高，核心问题（central issus）是稠密proposal中前景和背景的极度不平衡。以我更熟悉的YOLO举例子，比如在PASCAL VOC数据集中，每张图片上标注的目标可能也就几个。但是YOLO V2最后一层的输出是13×13×5，也就是845个候选目标！大量（简单易区分）的负样本在loss中占据了很大比重，使得有用的loss不能回传回来。基于此，作者将经典的交叉熵损失做了变形（见下），给那些易于被分类的简单例子小的权重，给不易区分的难例更大的权重。同时，作者提出了一个新的one-stage的检测器RetinaNet，达到了速度和精度很好地trade-off。 $FL(p_{t}=-(1-p_{t})^{\gamma}log(p_{t})$ Focal LossFocal Loss从交叉熵损失而来。二分类的交叉熵损失如下： 对应的，多分类的交叉熵损失是这样的： $CE(p,y)=-log(p_{y})$ 因此可以使用添加权重的交叉熵损失：$CE(p)=-\alpha_{t}log(p_{t})$ 而作者提出的是一个自适应调节的权重：(可加入权重$\alpha$平衡) $FL(p_{t}=-(1-p_{t})^{\gamma}log(p_{t})$ Pytorch实现： $L=-\sum_{i}^{C}onehot\odot(1-p_{t})^{\gamma}log(p_{t})$ 1234567891011121314151617181920212223242526272829import torchimport torch.nn as nnimport torch.nn.functional as Ffrom torch.autograd import Variabledef one_hot(index, classes): size = index.size() + (classes,) view = index.size() + (1,) mask = torch.Tensor(*size).fill_(0) index = index.view(*view) ones = 1. if isinstance(index, Variable): ones = Variable(torch.Tensor(index.size()).fill_(1)) mask = Variable(mask, volatile=index.volatile) return mask.scatter_(1, index, ones) class FocalLoss(nn.Module): def __init__(self, gamma=0, eps=1e-7): super(FocalLoss, self).__init__() self.gamma = gamma self.eps = eps def forward(self, input, target): y = one_hot(target, input.size(-1)) logit = F.softmax(input) logit = logit.clamp(self.eps, 1. - self.eps) loss = -1 * y * torch.log(logit) # cross entropy loss = loss * (1 - logit) ** self.gamma # focal loss return loss.sum() 模型: 模型初始化： 对于一般的分类网络，初始化之后，往往其输出的预测结果是均等的（随机猜测）。然而作者认为，这种初始化方式在类别极度不均衡的时候是有害的。作者提出，应该初始化模型参数，使得初始化之后，模型输出稀有类别的概率变小（如0.01），作者发现这种初始化方法对于交叉熵损失和Focal Loss的性能提升都有帮助。首先，从imagenet预训练得到的base net不做调整，新加入的卷积层权重均初始化为$\sigma$=0.01的高斯分布，偏置项为0.对于分类网络的最后一个卷积层，偏置项为$b=-log(\frac{(1-\pi)}{\pi})$, $\pi$是一个超参数，其意义是在训练的初始阶段，每个anchor被分类为前景的概率。 作者利用前面介绍的发现和结论，基于ResNet和Feature Pyramid Net（FPN）设计了一种新的one-stage检测框架.RetinaNet 是由一个骨干网络和两个特定任务子网组成的单一网络。骨感网络负责在整个输入图像上计算卷积特征图，并且是一个现成的卷积网络。 第一个子网在骨干网络的输出上执行卷积对象分类(small FCN attached to each FPN level)子网的参数在所有金字塔级别共享 第二个子网执行卷积边界框回归(attach another samll FCN to each pyramid level) 对象分类子网和框回归子网，尽管共享一个共同的结构，使用单独的参数。 Anchors:在金字塔等级P3到P7上，锚点的面积分别为$32^{2}$到$512^{2}$, 使用的长宽比为[1:2,1:1,2:1]. 对于更密集的比例覆盖，每个级别添加锚点的尺寸$[2^{0},2^{\frac{1}{3}},2^{\frac{1}{2}}]$ IOU:[0,0.4)的为背景，[0.4,0.5)的忽略，大于0.5的为前景 Two StageR-FCN Object Detection via Region-based Fully Convolutional Networks论文地址 R-FCN 通过添加 Position-sensitive score map 解决了把 ROI pooling 放到网络最后一层降低平移可变性的问题，以此改进了 Faster R-CNN 中检测速度慢的问题。 PS: 分类需要特征具有平移不变性，检测则要求对目标的平移做出准确响应。论文中作者给了测试的数据：ROI放在ResNet-101的conv5后，mAP是68.9%；ROI放到conv5前（就是标准的Faster R-CNN结构）的mAP是76.4%，差距是巨大的，这能证明平移可变性对目标检测的重要性。 Faster R-CNN检测速度慢的问题，速度慢是因为ROI层后的结构对不同的proposal是不共享的，试想下如果有300个proposal，ROI后的全连接网络就要计算300次, 非常耗时。 模型： Backbone architecture: ResNet-101有100个卷积层，后面是全局平均池化和1000类的全连接层。删除了平均池化层和全连接层，只使用卷积层来计算特征映射。最后一个卷积块是2048维，附加一个随机初始化的1024维的1×1卷积层来降维 $k^{2}(C+1)$Conv: ResNet101的输出是W*H*1024，用$k^{2}(C+1)$个1024*1*1的卷积核去卷积即可得到$k^{2}(C+1)$个大小为W*H的position sensitive的score map。这步的卷积操作就是在做prediction。k = 3，表示把一个ROI划分成3*3，对应的9个位置 ROI pooling: 一层的SPP结构。主要用来将不同大小的ROI对应的feature map映射成同样维度的特征 Vote:k*k个bin直接进行求和（每个类单独做）得到每一类的score，并进行softmax得到每类的最终得分，并用于计算损失 训练: 损失函数： $L(s,t_{x,y,w,h})=L_{cls}(s_{c^{\star}}+ \lambda [c^{\star}&gt;0]L_{reg}(t,t^{\star})$将正样本定义为与真实边界框IOU至少为0.5的ROI，否则为负样本 在线难例挖掘（OHEM): 其主要考虑训练样本集总是包含较多easy examples而相对较少hard examples，而自动选择困难样本能够使得训练更为有效，此外还有：S-OHEM: Stratified Online Hard Example Mining for Object Detection. S-OHEM 利用OHEM和stratified sampling技术。其主要考虑OHEM训练过程忽略了不同损失分布的影响，因此S-OHEM根据分布抽样训练样本。A-Fast-RCNN: Hard positive generation via adversary for object detection从更好的利用数据的角度出发，OHEM和S-OHEM都是发现困难样本，而A-Fast-RCNN的方法则是通过GAN的方式在特征空间产生具有部分遮挡和形变的困难样本。 空洞和步长:我们的全卷积架构享有FCN广泛使用的语义分割的网络修改的好处。特别的是，将ResNet-101的有效步长从32像素降低到了16像素，增加了分数图的分辨率。第一个conv5块中的stride=2操作被修改为stride=1，并且conv5阶段的所有卷积滤波器都被“hole algorithm” 修改来弥补减少的步幅. 位置敏感分数图：我们可以想想一下这种情况，M 是一个 5*5 大小，有一个蓝色的正方形物体在其中的特征图，我们将方形物体平均分割成 3*3 的区域。现在我们从 M 中创建一个新的特征图并只用其来检测方形区域的左上角。这个新的特征图如下右图，只有黄色网格单元被激活 因为我们将方形分为了 9 个部分，我们可以创建 9 张特征图分别来检测对应的物体区域。因为每张图检测的是目标物体的子区域，所以这些特征图被称为位置敏感分数图（position-sensitive score maps）。比如，我们可以说，下图由虚线所画的红色矩形是被提议的 ROIs 。我们将其分为 3*3 区域并得出每个区域可能包含其对应的物体部分的可能性。我们将此结果储存在 3*3 的投票阵列（如下右图）中。比如，投票阵列 [0][0] 中数值的意义是在此找到方形目标左上区域的可能性。将分数图和 ROIs 映射到投票阵列的过程叫做位置敏感 ROI 池化（position-sensitive ROI-pool）。在计算完位置敏感 ROI 池化所有的值之后，分类的得分就是所有它元素的平均值如果说我们有 C 类物体需要检测。我们将使用 C+1个类，因为其中多包括了一个背景（无目标物体）类。每类都分别有一个 3×3 分数图，因此一共有 (C+1)×3×3 张分数图。通过使用自己类别的那组分数图，我们可以预测出每一类的分数。然后我们使用 softmax 来操作这些分数从而计算出每一类的概率。 FPN Feature Pyramid Networks for Object Detection论文地址 a. 通过缩放图片获取不同尺度的特征图 b. ConvNET c. 通过不同特征分层 d. FPN 特征金字塔网络FPN，网络直接在原来的单网络上做修改，每个分辨率的 feature map 引入后一分辨率缩放两倍的 feature map 做 element-wise 相加的操作。通过这样的连接，每一层预测所用的 feature map 都融合了不同分辨率、不同语义强度的特征，融合的不同分辨率的 feature map 分别做对应分辨率大小的物体检测。这样保证了每一层都有合适的分辨率以及强语义特征。同时，由于此方法只是在原网络基础上加上了额外的跨层连接，在实际应用中几乎不增加额外的时间和计算量。 FPN: 图中feature map用蓝色轮廓表示，较粗的表示语义上较强特征 采用的bottom-up 和 top-down 的方法， bottom-up这条含有较低级别的语义但其激活可以更精确的定位因为下采样的次数更少。 Top-down的这条路更粗糙但是语义更强。 top-down的特征随后通过bottom-up的特征经由横向连接进行增强如图，使用较粗糙分辨率的特征映射时候将空间分辨率上采样x2倍。bottom-up的特征要经过1x1卷积层来生成最粗糙分辨率映射。 每个横向连接合并来自自下而上路径和自顶向下路径的具有相同空间大小的特征映射。 RPN结合FPN 通过用FPN替换单尺度特征映射来适应RPN。在特征金字塔的每个层级上附加一个相同设计的头部（3x3 conv（目标/非目标二分类和边界框回归）和 两个1x1convs（分类和回归））由于头部在所有金字塔等级上的所有位置密集滑动，所以不需要在特定层级上具有多尺度锚点。相反，为每个层级分配单尺度的锚点。定义锚点$[P_{2},P_{3},P_{4},P_{5},P_{6}]$分别具有$[32^{2},64^{2},128^{2},256^{2},512^{2}]$个像素 面积，以及多个长宽比{1:2,1:1,2:1}所以总共15个锚点在金字塔上。 其余同RPN网络 不同的尺度ROI用不同层的特征，每个box根据公式计算后提取其中某一层特征图对应的特征ROI，大尺度就用后面一些的金字塔层（P5），小尺度就用前面一点的层（P4）可根据公式：$k=[k_{0}+log_{2}(\frac{\sqrt{wh}}{224}]$ 计算需要哪一层的特征 Light-Head R-CNN: In Defense of Two-Stage Object Detector论文地址 two-stage 的方法在本身的一个基础网络上都会附加计算量很大的一个用于 classification+regression 的网络，导致速度变慢 Faster R-CNN: two fully connected layers for RoI recognition R-FCN: produces a large score maps. 因此，作者为了解决 detection 的速度问题，提出了一种新的 two-stage detector，就是Light-Head R-CNN。速度和准确率都有提升。Light-Head R-CNN 重点是 head 的结构设计。包括两部分： R-CNN subnet（ROI pooling 之后的network） 和ROI warping。 方法 Thin feature map: 降低进入head部分feature map的channel数也就是将r-fcn的score map从$P\times P(C+1)$减小到$P\times P\times \alpha$，$\alpha$是一个与类别数无关且较小的值，比如10。这样，score map的channel数与类别数无关，使得后面的分类不能像r-fcn那样vote，于是在roi pooling之后添加了一个fc层进行预测类别和位置。 Large separable convolution: Cheap R-CNN: 模型： Large: (1) ResNEt-101; (2) atrous algorithm; (3) RPN chanels 512; (4) Large separable convolution with $C_{mid}=256$ Small: (1) Xception like; (2) abbandon atrous algorithm; (3) RPN convolution to 256; (4) Large separable convolution with $C_{mid}=64$; (5) Apply PSPooling with alignment techniques as RoI warping(better results involve RoI-align).]]></content>
      <categories>
        <category>学习</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
        <tag>Object Detection</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[Ubuntu深度学习服务器配置]]></title>
    <url>%2FLinux.html</url>
    <content type="text"><![CDATA[Ubuntu 配置NVIDIA驱动 驱动删除 123sudo apt --purge autoremove nvidia*orsudo /usr/bin/nvidia-uninstall 驱动安装 12345sudo add-apt-repository ppa:graphics-drivers/ppasudo apt updatesudo apt upgradeubuntu-drivers listsudo apt install nvidia-driver-VERSION_NUMBER_HERE Reboot your computer so that the new driver is loaded. CUDA+Cudnn 下载对应驱动版本的cuda以及cudnn 12chmod 755 cuda_%version%_linux.runsudo sh cuda_%version%_linux.run 安装cuda后配置环境变量 123export CUDA_HOME=/usr/local/cuda export PATH=$PATH:$CUDA_HOME/bin export LD_LIBRARY_PATH=/usr/local/cuda/lib64$&#123;LD_LIBRARY_PATH:+:$&#123;LD_LIBRARY_PATH&#125;&#125; 查看cuda版本 1nvcc --version 把cudnn对应文件移入 /usr/local/cuda/ 中 cudnn7.6.3 1234567sudo cp cuda/include/cudnn.h /usr/local/cuda/include/sudo cp cuda/lib64/libcudnn.so.7.6.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_static.a /usr/local/cuda/lib64/sudo chmod a+r /usr/local/cuda/include/cudnn.hsudo chmod a+r /usr/local/cuda/lib64/libcudnn*sudo ln -s /usr/local/cuda/lib64/libcudnn.so.7.6.5 /usr/local/cuda/lib64/libcudnn.so.7sudo ln -s /usr/local/cuda/lib64/libcudnn.so.7 /usr/local/cuda/lib64/libcudnn.so cudnn8.0.5 12345678910111213141516171819202122232425sudo cp cuda/include/cudnn* /usr/local/cuda/include/sudo cp cuda/lib64/libcudnn.so.8.0.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_adv_infer.so.8.0.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_adv_train.so.8.0.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_cnn_infer.so.8.0.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_cnn_train.so.8.0.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_ops_infer.so.8.0.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_ops_train.so.8.0.5 /usr/local/cuda/lib64/sudo cp cuda/lib64/libcudnn_static.a /usr/local/cuda/lib64/sudo chmod a+r /usr/local/cuda/include/cudnn*sudo chmod a+r /usr/local/cuda/lib64/libcudnn*sudo ln -s /usr/local/cuda/lib64/libcudnn.so.8.0.5 /usr/local/cuda/lib64/libcudnn.so.8sudo ln -s /usr/local/cuda/lib64/libcudnn.so.8 /usr/local/cuda/lib64/libcudnn.sosudo ln -s /usr/local/cuda/lib64/libcudnn_adv_infer.so.8.0.5 /usr/local/cuda/lib64/libcudnn_adv_infer.so.8sudo ln -s /usr/local/cuda/lib64/libcudnn_adv_infer.so.8 /usr/local/cuda/lib64/libcudnn_adv_infer.sosudo ln -s /usr/local/cuda/lib64/libcudnn_adv_train.so.8.0.5 /usr/local/cuda/lib64/libcudnn_adv_train.so.8sudo ln -s /usr/local/cuda/lib64/libcudnn_adv_train.so.8 /usr/local/cuda/lib64/libcudnn_adv_train.sosudo ln -s /usr/local/cuda/lib64/libcudnn_cnn_infer.so.8.0.5 /usr/local/cuda/lib64/libcudnn_cnn_infer.so.8sudo ln -s /usr/local/cuda/lib64/libcudnn_cnn_infer.so.8 /usr/local/cuda/lib64/libcudnn_cnn_infer.sosudo ln -s /usr/local/cuda/lib64/libcudnn_cnn_train.so.8.0.5 /usr/local/cuda/lib64/libcudnn_cnn_train.so.8sudo ln -s /usr/local/cuda/lib64/libcudnn_cnn_train.so.8 /usr/local/cuda/lib64/libcudnn_cnn_train.sosudo ln -s /usr/local/cuda/lib64/libcudnn_ops_infer.so.8.0.5 /usr/local/cuda/lib64/libcudnn_ops_infer.so.8sudo ln -s /usr/local/cuda/lib64/libcudnn_ops_infer.so.8 /usr/local/cuda/lib64/libcudnn_ops_infer.sosudo ln -s /usr/local/cuda/lib64/libcudnn_ops_train.so.8.0.5 /usr/local/cuda/lib64/libcudnn_ops_train.so.8sudo ln -s /usr/local/cuda/lib64/libcudnn_ops_train.so.8 /usr/local/cuda/lib64/libcudnn_ops_train.so 查看cudnn安装1cat /usr/local/cuda/include/cudnn_version.h | grep CUDNN_MAJOR -A 2 Vim 升级vim到8.0以上 使用 Sans Mono Nerd Font``` 作为终端字体: [download](https://drive.google.com/file/d/1aWA6edSaMiG6cGV0tyQnt012Ub-CoUCO/view?usp1234563. 安装```shgit clone https://github.com/chxuan/vimplus.git ~/.vimpluscd ~/.vimplus./install.sh //不加sudo 更新 12cd ~/.vimplus./update.sh Tmux 安装 1sudo apt-get install tmux tmux 内无法连接X 1set-option -g update-environment &quot;SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY&quot; 上面命令放到 tmux.conf 中1tmux source-file ~/.tmux.conf Anaconda3官网下载安装包For Linux Installer 打开命令行 /path/filename 替换为安装包路径 1sha256sum /path/filename 安装 1bash ~/path/filename 安装过程中出现说明以及选择的地方选择YES 修改环境变量 1vim ~/.bashrc 按”i”进入编辑模式，在最后一行添加1export PATH=~/anaconda3/bin:$PATH 然后重启环境变量1source ~/.bashrc 配置完成，命令行输入 1anaconda-navigator 启动 Anaconda环境管理断开VPN!!! 创建新环境(自定义python版本) 1conda create -n pytorch python=3.7 启动环境 1source activate pytorch 关联环境到Jupyter-Notebook 1conda install ipykernel 切换国内源 升级pip&gt;10.0 1pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pip -U 设置清华源作为镜像 1pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple Anaconda 镜像 12conda config --add channels &apos;https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/&apos;conda config --set show_channel_urls yes Pytorch 各个pytorch以及torchvision版本地址 here TensorRT 下载对应版本tensorrt(cuda, cudnn, linux) 进入conda虚拟环境 解压 1tar xzvf TensorRT-$&#123;version&#125;.$&#123;os&#125;.$&#123;arch&#125;-gnu.$&#123;cuda&#125;.$&#123;cudnn&#125;.tar.gz 添加环境变量 12export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:&lt;TensorRT-$&#123;version&#125;/lib&gt;export LIBRARY_PATH=$LIBRARY_PATH:&lt;TensorRT-$&#123;version&#125;/lib&gt; 安装 123456cd TensorRT-$&#123;version&#125;/pythonsudo pip install tensorrt-*-cp3x-none-linux_x86_64.whlcd TensorRT-$&#123;version&#125;/graphsurgeonsudo pip install graphsurgeon-0.4.4-py2.py3-none-any.whlcd TensorRT-$&#123;version&#125;/onnx_graphsurgeonsudo pip install onnx_graphsurgeon-0.2.6-py2.py3-none-any.whl 重启虚拟环境 1source ~/.bashrc tensorrt bug记录 12Assertion failed: !_importer_ctx.network()-&gt;hasImplicitBatchDimension() &amp;&amp; &quot;This version of the ONNX parser only supports TensorRT INetworkDefinitions with an explicit batch dimension. Please ensure the network was created using the EXPLICIT_BATCH NetworkDefinitionCreationFlag.&quot; build trt enginn时候设定 EXPLICIT_BATCH123EXPLICIT_BATCH = 1 &lt;&lt; (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)builder = trt.Builder()network = builder.create_network(EXPLICIT_BATCH) Docker使用脚本自动安装12345678curl -fsSL get.docker.com -o get-docker.shsudo sh get-docker.sh --mirror Aliyunsudo systemctl enable dockersudo systemctl start dockersudo groupadd dockersudo usermod -aG docker $USERsudo gpasswd -a $USER dockernewgrp docker 退出当前终端并重新登录，进行如下测试1docker run hello-world Cmake官网下载Cmake压缩包，解压后12345sudo apt-get install libssl-dev./bootstrap --prefix=/usrmakesudo make installcmake --version OpenCV 安装123456789git clone https://github.com/opencv/opencv.gitcd opencvmkdir releasecd releasecmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..sudo makesudo make installsudo /bin/bash -c &apos;echo &quot;/usr/local/lib&quot; &gt; /etc/ld.so.conf.d/opencv.conf&apos; sudo ldconfig]]></content>
      <categories>
        <category>System</category>
      </categories>
      <tags>
        <tag>Linux</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[(Logbook) -- Object Detection System Based on CNN and Capsule Network]]></title>
    <url>%2FLogBook.html</url>
    <content type="text"><![CDATA[Gantt chartCheck list 1) preparation 1.1) Familiarization with develop tools 1.1.1) Keras 1.1.2) Pythrch 1.2) Presentation 1.2.1) Poster conference 2) Create image database 2.1) Confirmation of detected objects 2.2) Collect and generate the dataset 3) Familiarization with CNN based object detection methods 3.1) R-CNN 3.2) SPP-net 3.3) Fast R-CNN 3.4) Faster R-CNN 4) Implement object detection system based on one chosen CNN method 4.1) Pre-processing of images 4.2) Extracting features 4.3) Mode architecture 4.4) Train model and optimization 4.5) Models ensemble 5) Analysis work 5.1) Evaluation of detection result. 6) Paperwork and bench inspection 6.1) Logbook 6.2) Write the thesis 6.3) Project video 6.4) Speech and ppt of bench inspection 7) Documents 7.1) Project Brief May【28/05/2018】Keras is a high-level neural networks API, written in Python and capable of running on top of TensorFlow, CNTK, or Theano. Keras document Keras 文档 Installation TensorFlowMicrosoft Visual Studio 2015CUDA 9.0cuDNN7Anaconda Step 1: Install VS2015 Step 2: Install CUDA 9.0 并添加环境变量 Step 3: Install cuDNN7 解压后把cudnn目录下的bin目录加到PATH环境变量里 Step 4: Install Anaconda 把安装路径添加到PATH里去, 在这里我用了 Python 3.5 Step 5: 使用Anaconda的命令行 新建一个虚拟环境,激活并且关联到jupyterbook123conda create --name tensorflow python=3.5activate tensorflowconda install nb_conda Step 6: Install GPU version TensorFlow.1pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --ignore-installed --upgrade tensorflow-gpu Keras Step 1: 启动之前的 虚拟环境， 并且安装Keras GPU 版本12activate tensorflowpip install keras -U --pre 在硕士学习过程中，使用Keras的项目** NBA with Machine Learning Kaggle- Job salary prediction TensorFlow CPU 切换1234567891011import tensorflow as tf import osimport keras.backend.tensorflow_backend as KTF os.environ["CUDA_VISIBLE_DEVICES"] = "0" #设置需要使用的GPU的编号config = tf.ConfigProto()config.gpu_options.per_process_gpu_memory_fraction = 0.4 #设置使用GPU容量占GPU总容量的比例sess = tf.Session(config=config)KTF.set_session(sess)with tf.device('/cpu:0'): 这样可以在GPU版本的虚拟环境里面使用CPU计算 Jupyter Notebook 工作目录设置启动命令行，切换至预设的工作目录， 运行：1jupyter notebook --generate-config June【01/06/2018】PyTorch is a python package that provides two high-level features: Tensor computation (like numpy) with strong GPU acceleration Deep Neural Networks built on a tape-based autodiff system Package Description torch a Tensor library like NumPy, with strong GPU support torch.autograd a tape based automatic differentiation library that supports all differentiable Tensor operations in torch torch.nn a neural networks library deeply integrated with autograd designed for maximum flexibility torch.optim an optimization package to be used with torch.nn with standard optimization methods such as SGD, RMSProp, LBFGS, Adam etc. torch.multiprocessing python multiprocessing, but with magical memory sharing of torch Tensors across processes. Useful for data loading and hogwild training. torch.utils DataLoader, Trainer and other utility functions for convenience torch.legacy(.nn/.optim) legacy code that has been ported over from torch for backward compatibility reasons Installation Step 1: 使用Anaconda的命令行 新建一个虚拟环境,激活并且关联到jupyterbook123conda create --name pytorch python=3.5activate pytorchconda install nb_conda Step 2: Install GPU version PyTorch.12conda install pytorch cuda90 -c pytorch pip install torchvision Understanding of PyTorch TensorsTensors和numpy中的ndarrays较为相似, 与此同时Tensor也能够使用GPU来加速运算 123456789101112131415161718192021222324from __future__ import print_functionimport torchx = torch.Tensor(5, 3) # 构造一个未初始化的5*3的矩阵x = torch.rand(5, 3) # 构造一个随机初始化的矩阵x # 此处在notebook中输出x的值来查看具体的x内容x.size()#NOTE: torch.Size 事实上是一个tuple, 所以其支持相关的操作*y = torch.rand(5, 3)#此处 将两个同形矩阵相加有两种语法结构x + y # 语法一torch.add(x, y) # 语法二# 另外输出tensor也有两种写法result = torch.Tensor(5, 3) # 语法一torch.add(x, y, out=result) # 语法二y.add_(x) # 将y与x相加# 特别注明：任何可以改变tensor内容的操作都会在方法名后加一个下划线'_'# 例如：x.copy_(y), x.t_(), 这俩都会改变x的值。#另外python中的切片操作也是资次的。x[:,1] #这一操作会输出x矩阵的第二列的所有值 Numpy桥将Torch的Tensor和numpy的array相互转换简，注意Torch的Tensor和numpy的array会共享他们的存储空间，修改一个会导致另外的一个也被修改。 1234567891011121314151617181920212223# 此处演示tensor和numpy数据结构的相互转换a = torch.ones(5)b = a.numpy()# 此处演示当修改numpy数组之后,与之相关联的tensor也会相应的被修改a.add_(1)print(a)print(b)# 将numpy的Array转换为torch的Tensorimport numpy as npa = np.ones(5)b = torch.from_numpy(a)np.add(a, 1, out=a)print(a)print(b)# 另外除了CharTensor之外，所有的tensor都可以在CPU运算和GPU预算之间相互转换# 使用CUDA函数来将Tensor移动到GPU上# 当CUDA可用时会进行GPU的运算if torch.cuda.is_available(): x = x.cuda() y = y.cuda() 使用PyTorch设计一个CIFAR10数据集的分类模型code MMdnnMMdnn is a set of tools to help users inter-operate among different deep learning frameworks. E.g. model conversion and visualization. Convert models between Caffe, Keras, MXNet, Tensorflow, CNTK, PyTorch Onnx and CoreML. MMdnn主要有以下特征： 模型文件转换器，不同的框架间转换DNN模型 模型代码片段生成器，生成适合不同框架的代码 模型可视化，DNN网络结构和框架参数可视化 模型兼容性测试（正在进行中） Github 【04/06/2018】Dataset: VOC 2012 Dataset Introduce: Visual Object Classes Challenge 2012 (VOC2012)PASCAL‘s full name is Pattern Analysis, Statistical Modelling and Computational Learning.VOC’s full name is Visual OBject Classes.The first competition was held in 2005 and terminated in 2012. I will use the last updated dataset which is VOC2012 dataset. The main aim of this competition is object detection, there are 20 classes objects in the dataset: person bird, cat, cow, dog, horse, sheep aeroplane, bicycle, boat, bus, car, motorbike, train bottle, chair, dining table, potted plant, sofa, tv/monitor Detection TaskReferenced:The PASCAL Visual Object Classes Challenge 2012 (VOC2012) Development KitMark Everingham - John Winnhttp://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/index.html Task:For each of the twenty classes predict the bounding boxes of each object of that class in a test image (if any). Each bounding box should be output with an associated real-valued confidence of the detection so that a precision/recall curve can be drawn. Participants may choose to tackle all, or any subset of object classes, for example ‘cars only’ or ‘motorbikes and cars’. Competitions:Two competitions are defined according to the choice of training data: taken from the $VOC_{trainval}$ data provided. from any source excluding the $VOC_{test}$ data provided. Submission of Results:A separate text file of results should be generated for each competition and each class e.g. `car’. Each line should be a detection output by the detector in the following format: 1&lt;image identifier&gt; &lt;confidence&gt; &lt;left&gt; &lt;top&gt; &lt;right&gt; &lt;bottom&gt; where (left,top)-(right,bottom) defines the bounding box of the detected object. The top-left pixel in the image has coordinates $(1,1)$. Greater confidence values signify greater confidence that the detection is correct. An example file excerpt is shown below. Note that for the image 2009_000032, multiple objects are detected:1234567comp3_det_test_car.txt: ... 2009_000026 0.949297 172.000000 233.000000 191.000000 248.000000 2009_000032 0.013737 1.000000 147.000000 114.000000 242.000000 2009_000032 0.013737 1.000000 134.000000 94.000000 168.000000 2009_000035 0.063948 455.000000 229.000000 491.000000 243.000000 ... Evaluation:The detection task will be judged by the precision/recall curve. The principal quantitative measure used will be the average precision (AP). Detections are considered true or false positives based on the area of overlap with ground truth bounding boxes. To be considered a correct detection, the area of overlap $a_o$ between the predicted bounding box $B_p$ and ground truth bounding box $B_{gt}$ must exceed $50\%$ by the formula: $a_o = \frac{area(B_p \cap B_{gt})}{area(B_p \cup B_{gt})}$ XML标注格式 对于目标检测来说，每一张图片对应一个xml格式的标注文件。所以你会猜到，就像gemfield准备的训练集有8万张照片一样，在存放xml文件的目录里，这里也将会有8万个xml文件。下面是其中一个xml文件的示例： 123456789101112131415161718192021222324252627282930313233 &lt;?xml version="1.0" encoding="utf-8"?&gt;&lt;annotation&gt; &lt;folder&gt;VOC2007&lt;/folder&gt; &lt;filename&gt;test100.mp4_3380.jpeg&lt;/filename&gt; &lt;size&gt; &lt;width&gt;1280&lt;/width&gt; &lt;height&gt;720&lt;/height&gt; &lt;depth&gt;3&lt;/depth&gt; &lt;/size&gt; &lt;object&gt; &lt;name&gt;gemfield&lt;/name&gt; &lt;bndbox&gt; &lt;xmin&gt;549&lt;/xmin&gt; &lt;xmax&gt;715&lt;/xmax&gt; &lt;ymin&gt;257&lt;/ymin&gt; &lt;ymax&gt;289&lt;/ymax&gt; &lt;/bndbox&gt; &lt;truncated&gt;0&lt;/truncated&gt; &lt;difficult&gt;0&lt;/difficult&gt; &lt;/object&gt; &lt;object&gt; &lt;name&gt;civilnet&lt;/name&gt; &lt;bndbox&gt; &lt;xmin&gt;842&lt;/xmin&gt; &lt;xmax&gt;1009&lt;/xmax&gt; &lt;ymin&gt;138&lt;/ymin&gt; &lt;ymax&gt;171&lt;/ymax&gt; &lt;/bndbox&gt; &lt;truncated&gt;0&lt;/truncated&gt; &lt;difficult&gt;0&lt;/difficult&gt; &lt;/object&gt; &lt;segmented&gt;0&lt;/segmented&gt;&lt;/annotation&gt; 在这个测试图片上，我们标注了2个object，一个是gemfield，另一个是civilnet。 在这个xml例子中： bndbox是一个轴对齐的矩形，它框住的是目标在照片中的可见部分； truncated表明这个目标因为各种原因没有被框完整（被截断了），比如说一辆车有一部分在画面外； occluded是说一个目标的重要部分被遮挡了（不管是被背景的什么东西，还是被另一个待检测目标遮挡）； difficult表明这个待检测目标很难识别，有可能是虽然视觉上很清楚，但是没有上下文的话还是很难确认它属于哪个分类；标为difficult的目标在测试成绩的评估中一般会被忽略。 注意：在一个object中，name 标签要放在前面，否则的话，目标检测的一个重要工程实现SSD会出现解析数据集错误（另一个重要工程实现py-faster-rcnn则不会）。 【07/06/2018】Poster conference 5 People in one group to present their object.I present this object to my supervisor in this conference. 【11/06/2018】R-CNNPaper: Rich feature hierarchies for accurate object detection and semantic segmentation 【论文主要特点】（相对传统方法的改进） 速度： 经典的目标检测算法使用滑动窗法依次判断所有可能的区域。本文则(采用Selective Search方法)预先提取一系列较可能是物体的候选区域，之后仅在这些候选区域上(采用CNN)提取特征，进行判断。 训练集： 经典的目标检测算法在区域中提取人工设定的特征。本文则采用深度网络进行特征提取。使用两个数据库： 一个较大的识别库 （ImageNet ILSVC 2012）：标定每张图片中物体的类别。一千万图像，1000类。 一个较小的检测库（PASCAL VOC 2007）：标定每张 图片中，物体的类别和位置，一万图像，20类。 本文使用识别库进行预训练得到CNN（有监督预训练），而后用检测库调优参数，最后在 检测库上评测。 【流程】 候选区域生成： 一张图像生成1K~2K个候选区域 （采用Selective Search 方法） 特征提取： 对每个候选区域，使用深度卷积网络提取特征 （CNN） 类别判断： 特征送入每一类的SVM 分类器，判别是否属于该类 位置精修： 使用回归器精细修正候选框位置 【Selective Search】 使用一种过分割手段，将图像分割成小区域 (1k~2k 个) 查看现有小区域，按照合并规则合并可能性最高的相邻两个区域。重复直到整张图像合并成一个区域位置 输出所有曾经存在过的区域，所谓候选区域其中合并规则如下： 优先合并以下四种区域： 颜色（颜色直方图）相近的 纹理（梯度直方图）相近的 合并后总面积小的： 保证合并操作的尺度较为均匀，避免一个大区域陆续“吃掉”其他小区域 （例：设有区域a-b-c-d-e-f-g-h。较好的合并方式是：ab-cd-ef-gh -&gt; abcd-efgh -&gt; abcdefgh。 不好的合并方法是：ab-c-d-e-f-g-h -&gt;abcd-e-f-g-h -&gt;abcdef-gh -&gt; abcdefgh） 合并后，总面积在其BBOX中所占比例大的： 保证合并后形状规则。 【12/06/2018】SPP-CNNPaper: Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition 【论文主要特点】（相对传统方法的改进） RCNN使用CNN作为特征提取器，首次使得目标检测跨入深度学习的阶段。但是RCNN对于每一个区域候选都需要首先将图片放缩到固定的尺寸（224*224），然后为每个区域候选提取CNN特征。容易看出这里面存在的一些性能瓶颈： 速度瓶颈：重复为每个region proposal提取特征是极其费时的，Selective Search对于每幅图片产生2K左右个region proposal，也就是意味着一幅图片需要经过2K次的完整的CNN计算得到最终的结果。 性能瓶颈：对于所有的region proposal防缩到固定的尺寸会导致我们不期望看到的几何形变，而且由于速度瓶颈的存在，不可能采用多尺度或者是大量的数据增强去训练模型。 【流程】 首先通过selective search产生一系列的region proposal 然后训练多尺寸识别网络用以提取区域特征，其中处理方法是每个尺寸的最短边大小在尺寸集合中：$s \in S = {480,576,688,864,1200}$训练的时候通过上面提到的多尺寸训练方法，也就是在每个epoch中首先训练一个尺寸产生一个model，然后加载这个model并训练第二个尺寸，直到训练完所有的尺寸。空间金字塔池化使用的尺度为：1*1，2*2，3*3，6*6，一共是50个bins。 在测试时，每个region proposal选择能使其包含的像素个数最接近224*224的尺寸，提取相 应特征。 训练SVM，BoundingBox回归. 【13/06/2018】FAST R-CNNPaper: Fast R-CNN 【论文主要特点】（相对传统方法的改进） 测试时速度慢：RCNN一张图像内候选框之间大量重叠，提取特征操作冗余。本文将整张图像归一化后直接送入深度网络。在邻接时，才加入候选框信息，在末尾的少数几层处理每个候选框。 训练时速度慢 ：原因同上。在训练时，本文先一张图像送入网络，紧接着送入从这幅图像上提取出的候选区域。这些候选区域的前几层特征不需要再重复计算。 训练所需空间大: RCNN中独立的分类器和回归器需要大量特征作为训练样本。本文把类别判断和位置精调统一用深度网络实现，不再需要额外存储。 【流程】 网络首先用几个卷积层（conv）和最大池化层处理整个图像以产生conv特征图。 然后，对于每个对象建议框（object proposals ），感兴趣区域（region of interest——RoI）池层从特征图提取固定长度的特征向量。 每个特征向量被输送到分支成两个同级输出层的全连接（fc）层序列中：其中一层进行分类，对 目标关于K个对象类（包括全部“背景background”类）产生softmax概率估计，即输出每一个RoI的概率分布；另一层进行bbox regression，输出K个对象类中每一个类的四个实数值。每4个值编码K个类中的每个类的精确边界盒（bounding-box）位置，即输出每一个种类的的边界盒回归偏差。整个结构是使用多任务损失的端到端训练（trained end-to-end with a multi-task loss）。 【14~18/06/2018】FASTER R-CNNI want to use Faster R-cnn as the first method to implement object detection system. Paper: Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks 在结构上，Faster RCNN已经将特征抽取(feature extraction)，proposal提取，bounding box regression(rect refine)，classification都整合在了一个网络中，使得综合性能有较大提高，在检测速度方面尤为明显。 流程 Conv layers：作为一种CNN网络目标检测方法，Faster R-CNN首先使用一组基础的conv+relu+pooling层提取image的feature maps。该feature maps被共享用于后续RPN层和全连接层。 Region Proposal Networks：RPN网络用于生成region proposals。该层通过softmax判断anchors属于foreground或者background，再利用bounding box regression修正anchors获得精确的proposals。 Roi Pooling：该层收集输入的feature maps和proposals，综合这些信息后提取proposal feature maps，送入后续全连接层判定目标类别。 Classification：利用proposal feature maps计算proposal的类别，同时再次bounding box regression获得检测框最终的精确位置。 解释[1. Conv layers] Conv layers包含了conv，pooling，relu三种层。以python版本中的VGG16模型中的faster_rcnn_test.pt的网络结构为例，如图, Conv layers部分共有13个conv层，13个relu层，4个pooling层。这里有一个非常容易被忽略但是又无比重要的信息，在Conv layers中： 所有的conv层都是： $kernel_size=3$ ， $pad=1$ ， $stride=1$ 所有的pooling层都是： $kernel_size=2$ ， $pad=0$ ， $stride=2$ 为何重要？在Faster RCNN Conv layers中对所有的卷积都做了扩边处理（ $pad=1$ ，即填充一圈0），导致原图变为 $(M+2)\times (N+2)$ 大小，再做3x3卷积后输出 $M\times N$ 。正是这种设置，导致Conv layers中的conv层不改变输入和输出 矩阵大小。如下图：类似的是，Conv layers中的pooling层 $kernel_size=2$ ， $stride=2$ 。这样每个经过pooling层的 $M\times N$ 矩阵，都会变为 $(M/2) \times(N/2)$ 大小。综上所述，在整个Conv layers中，conv和relu层不改变输入输出大小，只有pooling层使输出长宽都变为输入的1/2。那么，一个 $M\times N$ 大小的矩阵经过Conv layers固定变为 $(M/16)\times (N/16)$ ！这样Conv layers生成的featuure map中都可以和原图对应起来。 [2. Region Proposal Networks(RPN)] 经典的检测方法生成检测框都非常耗时，如OpenCV adaboost使用滑动窗口+图像金字塔生成检测框；或如R-CNN使用SS(Selective Search)方法生成检测框。而Faster RCNN则抛弃了传统的滑动窗口和SS方法，直接使用RPN生成检测框，这也是Faster R-CNN的巨大 优势，能极大提升检测框的生成速度。 上图展示了RPN网络的具体结构。可以看到RPN网络实际分为2条线，上面一条通过softmax分类anchors获得foreground和 background（检测目标是foreground），下面一条用于计算对于anchors的bounding box regression偏移量，以获得精确的 proposal。而最后的Proposal层则负责综合foreground anchors和bounding box regression偏移量获取proposals，同时剔除太 小和超出边界的proposals。其实整个网络到了Proposal Layer这里，就完成了相当于目标定位的功能。 2.1 多通道图像卷积基础知识介绍 对于单通道图像+单卷积核做卷积，之前展示了； 对于多通道图像+多卷积核做卷积，计算方式如下： 输入有3个通道，同时有2个卷积核。对于每个卷积核，先在输入3个通道分别作卷积，再将3个通道结果加起来得到卷积输出。所以对 于某个卷积层，无论输入图像有多少个通道，输出图像通道数总是等于卷积核数量！对多通道图像做 $1\times1$ 卷积，其实就是将输入图像于每个通道乘以卷积系数后加在一起，即相当于把原图像中本来各个独立的 通道“联通”在了一起。2.2 Anchors提到RPN网络，就不能不说anchors。所谓anchors，实际上就是一组由rpn/generate_anchors.py生成的矩形。直接运行作者demo中 的generate_anchors.py可以得到以下输出：[[ -84. -40. 99. 55.][-176. -88. 191. 103.][-360. -184. 375. 199.][ -56. -56. 71. 71.][-120. -120. 135. 135.][-248. -248. 263. 263.][ -36. -80. 51. 95.][ -80. -168. 95. 183.][-168. -344. 183. 359.]]其中每行的4个值 $(x1,y1,x2,y2)$ 代表矩形左上和右下角点坐标。9个矩形共有3种形状，长宽比为大约为 $width:height = [1:1, 1:2, 2:1]$ 三种，如下图。实际上通过anchors就引入了检测中常用到的多尺度方法。注：关于上面的anchors size，其实是根据检测图像设置的。在python demo中，会把任意大小的输入图像reshape成 $800\times600$。再回头来看anchors的大小，anchors中长宽 1:2 中最大为 $352\times704$ ，长宽 2:1 中最大 $736\times384$ ，基本是cover了 $800\times600$ 的各个尺度和形状。那么这9个anchors是做什么的呢？借用Faster RCNN论文中的原图，如下图，遍历Conv layers计算获得的feature maps，为每一个点都配备这9种anchors作为初始的检测框。这样做获得检测框很不准确，不用担心，后面还有2次bounding box regression可以修正检测框位置。 解释一下上面这张图的数字。 在原文中使用的是ZF model中，其Conv Layers中最后的conv5层num_output=256，对应生成256张特征图，所以相当于feature map每个点都是256-dimensions 在conv5之后，做了rpn_conv/3x3卷积且num_output=256，相当于每个点又融合了周围3x3的空间信息（猜测这样做也许更鲁棒？反正我没测试），同时256-d不变（如图4和图7中的红框） 假设在conv5 feature map中每个点上有k个anchor（默认k=9），而每个anhcor要分foreground和background，所以每个点由256d feature转化为cls=2k scores；而每个anchor都有[x, y, w, h]对应4个偏移量，所以reg=4k coordinates 补充一点，全部anchors拿去训练太多了，训练程序会在合适的anchors中随机选取128个postive anchors+128个negative anchors进行训练（什么是合适的anchors下文5.1有解释） 2.3 softmax判定foreground与background 一副MxN大小的矩阵送入Faster RCNN网络后，到RPN网络变为(M/16)x(N/16)，不妨设 W=M/16 ， H=N/16 。在进入reshape与softmax之前，先做了1x1卷积，如下图： 该1x1卷积的caffe prototxt定义如下： 可以看到其num_output=18，也就是经过该卷积的输出图像为 $W\times H \times 18$ 大小（注意第二章开头提到的卷积计算方式）。这也就刚好对应了feature maps每一个点都有9个anchors，同时每个anchors又有可能是foreground和background，所有这些信息都保存 $W\times H\times (9\cdot2)$ 大小的矩阵。为何这样做？后面接softmax分类获得foreground anchors，也就相当于初步提取了检测目标候选区域box（一般认为目标在foreground anchors中）。综上所述，RPN网络中利用anchors和softmax初步提取出foreground anchors作为候选区域。 2.4 bounding box regression原理如图所示绿色框为飞机的Ground Truth(GT)，红色为提取的foreground anchors，即便红色的框被分类器识别为飞机，但是由于红色的框定位不准，这张图相当于没有正确的检测出飞机。所以我们希望采用一种方法对红色的框进行微调，使得foreground anchors和GT更加接近。对于窗口一般使用四维向量 (x, y, w, h) 表示，分别表示窗口的中心点坐标和宽高。对于下图，红色的框A代表原始的Foreground Anchors，绿色的框G代表目标的GT，我们的目标是寻找一种关系，使得输入原始的anchor A经过映射得到一个跟真实窗口G更接近的回归窗口G’，即： 给定：$anchor A=(A_{x}, A_{y}, A_{w}, A_{h})$ 和 $GT=[G_{x}, G_{y}, G_{w}, G_{h}]$ 寻找一种变换F，使得：$F(A_{x}, A_{y}, A_{w}, A_{h})=(G_{x}^{‘}, G_{y}^{‘}, G_{w}^{‘}, G_{h}^{‘})$，其中 $(G_{x}^{‘}, G_{y}^{‘}, G_{w}^{‘}, G_{h}^{‘}) \approx (G_{x}, G_{y}, G_{w}, G_{h})$那么经过何种变换F才能从图10中的anchor A变为G’呢？ 比较简单的思路就是: 先做平移 $G^{‘}{x} = A{w} \cdot d_{x}(A) + A_{x} $$G^{‘}{y} = A{y} \cdot d_{y}(A) + A_{y} $ 再做缩放$G^{‘}{w} = A{w} \cdot exp(d_{w}(A)) $$G^{‘}{h} = A{h} \cdot exp(d_{h}(A)) $ 观察上面4个公式发现，需要学习的是 $d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)$ 这四个变换。当输入的anchor A与GT相差较小时，可以认为这种变换是一种线性变换， 那么就可以用线性回归来建模对窗口进行微调（注意，只有当anchors A和GT比较接近时，才能使用线性回归模型，否则就是复杂的非线性问题了）。 接下来的问题就是如何通过线性回归获得 $d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)$ 了。线性回归就是给定输入的特征向量X, 学习一组参数W, 使得经过线性回归后的值跟真实值Y非常接近，即$Y=WX$。对于该问题，输入X是cnn feature map，定义为Φ；同时还有训练传入A与GT之间的变换量，即$(t_{x}, t_{y}, t_{w}, t_{h})$。输出是$d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)$四个变换。那么目标函数可以表示为： $d_{}(A) = w^{T}_{} \cdot \phi(A)$ 其中Φ(A)是对应anchor的feature map组成的特征向量，w是需要学习的参数，d(A)是得到的预测值（*表示 x，y，w，h，也就是每一个变换对应一个上述目标函数）。为了让预测值$(t_{x}, t_{y}, t_{w}, t_{h})$与真实值差距最小，设计损失函数： $Loss = \sum^{N}{i}(t^{i}{} - \hat{w}^{T}_{} \cdot \phi(A^{i}))^{2}$函数优化目标为：需要说明，只有在GT与需要回归框位置比较接近时，才可近似认为上述线性变换成立。说完原理，对应于Faster RCNN原文，foreground anchor与ground truth之间的平移量 $(t_x, t_y)$ 与尺度因子 $(t_w, t_h)$ 如下：对于训练bouding box regression网络回归分支，输入是cnn feature Φ，监督信号是Anchor与GT的差距 $(t_x, t_y, t_w, t_h)$，即训练目标是：输入Φ的情况下使网络输出与监督信号尽可能接近。那么当bouding box regression工作时，再输入Φ时，回归网络分支的输出就是每个Anchor的平移量和变换尺度 $(t_x, t_y, t_w, t_h)$，显然即可用来修正Anchor位置了。 2.5 对proposals进行bounding box regression在了解bounding box regression后，再回头来看RPN网络第二条线路，如下图。其 $num_output=36$ ，即经过该卷积输出图像为 $W\times H\times 36$ ，在caffe blob存储为 [1, 36, H, W] ，这里相当于feature maps每个点都有9个anchors，每个anchors又都有4个用于回归的$d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)$变换量。 2.6 Proposal LayerProposal Layer负责综合所有 $d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)$ 变换量和foreground anchors，计算出精准的proposal，送入后续RoI Pooling Layer。首先解释im_info。对于一副任意大小PxQ图像，传入Faster RCNN前首先reshape到固定 $M\times N$ ，im_info=[M, N, scale_factor]则保存了此次缩放的所有信息。然后经过Conv Layers，经过4次pooling变为 $W\times H=(M/16)\times(N/16)$ 大小，其中feature_stride=16则保存了该信息，用于计算anchor偏移量。 Proposal Layer forward（caffe layer的前传函数）按照以下顺序依次处理： 生成anchors，利用$[d_{x}(A),d_{y}(A),d_{w}(A),d_{h}(A)]$对所有的anchors做bbox regression回归（这里的anchors生成和训练时完全一致） 按照输入的foreground softmax scores由大到小排序anchors，提取前pre_nms_topN(e.g. 6000)个anchors，即提取修正位置后的foreground anchors。 限定超出图像边界的foreground anchors为图像边界（防止后续roi pooling时proposal超出图像边界） 剔除非常小（width&lt;threshold or height&lt;threshold）的foreground anchors 进行nonmaximum suppression 再次按照nms后的foreground softmax scores由大到小排序fg anchors，提取前post_nms_topN(e.g. 300)结果作为proposal输出。 之后输出 proposal=[x1, y1, x2, y2] ，注意，由于在第三步中将anchors映射回原图判断是否超出边界，所以这里输出的proposal是对应 $M\times N$ 输入图像尺度的，这点在后续网络中有用。另外我认为，严格意义上的检测应该到此就结束了，后续部分应该属于识别了~ RPN网络结构就介绍到这里，总结起来就是：生成anchors -&gt; softmax分类器提取fg anchors -&gt; bbox reg回归fg anchors -&gt; Proposal Layer生成proposals 【19/06/2018】处理 XML 文档使用 xml.etree.ElementTree 这个包去解析XML文件， 并且整理成为list形式【流程】 读取XML文件 区分训练集测试集根据竞赛要求 解析XML文档收录到PYTHON词典中Github 的 jupyter notebook 地址 训练集根据竞赛的 trainval.txt 文件给的图片作为训练集其余的作为训练集 解析后， 总共有 17125 张图片，其中 11540 张作为训练集 图片中的20个类的统计情况： 【20/06/2018】根据信息画出BBOXES安装 cv2 这个包 1pip install opencv-python注意： OpenCV-python 中颜色格式 是BGR 而不是 RGB 在VOC2012数据集里面，总共有20类， 根据不同的种类用不同的颜色和唯一的编码画BBOXES。 class class_mapping BGR of bbox Person 0 (0, 0, 255) Aeroplane 1 (0, 0, 255) Tvmonitor 2 (0, 128, 0) Train 3 (128, 128, 128) Boat 4 (0, 165, 255) Dog 5 (0, 255, 255) Chair 6 (80, 127, 255) Bird 7 (208, 224, 64) Bicycle 8 (235, 206, 135) Bottle 9 (128, 0, 0) Sheep 10 (140, 180, 210) Diningtable 11 (0, 255, 0) Horse 12 (133, 21, 199) Motorbike 13 (47, 107, 85) Sofa 14 (19, 69, 139) Cow 15 (222, 196, 176) Car 16 (0, 0, 0) Cat 17 (225, 105, 65) Bus 18 (255, 255, 255) Pottedplant 19 (205, 250, 255) 我写了一个show_image_with_bbox函数去画出带BBOXES的图根据处理XML文件得到的list: Github 的 jupyter notebook 地址EXAMPLE: 【21/06/2018】config settingset config class: for image enhancement: image enhancementAccording to the config of three peremeters, users could augment image with 3 different ways or using them all.For horizontal and vertical flips, 1/3 probability to triggleWith 0,90,180,270 rotation,This function could increase the number of datasets.image flips and rotation are realized by opencv and replace of height and widthNew cordinates of bboxes are calculated acccording to different change of imagedetailed in Github, jupyter notebook: addressOrignal image:horizontal flip:Vertical filp:Random rotation:Horizontal and then vertical flips: 【22/06/2018】Image reziseThis function is to rezise input image to a uniform size with same shortest side According to set the value of shortest side, convergent-divergent or augmented another side proportion Test:Left image is resized image, in this case, the orignal image amplified. Class BalanceWhen training the model, if we sent image with no repeating classes, it may help to improve the performance of model. Therefore, this function is to make sure no repeating classes in two closed input image. Test: Random output 4 iamge with is function, it could find no repeating classes in two closed image. However, it may reduce the number of trainning image because skip some images. 【25~26/06/2018】Region Proposal Networks(RPN)可以看到RPN网络实际分为2条线，上面一条通过softmax分类anchors获得foreground和background（检测目标是foreground），下面一条用于计算对于anchors的bounding box regression偏移量，以获得精确的proposal。而最后的Proposal层则负责综合foreground anchors和bounding box regression偏移量获取proposals，同时剔除太小和超出边界的proposals。其实整个网络到了Proposal Layer这里，就完成了相当于目标定位的功能。 Anchors对每一个点生成的矩形 其中每行的4个值 (x1,y1,x2,y2) 代表矩形左上和右下角点坐标。9个矩形共有3种形状，长宽比为大约为 width:height = [1:1, 1:2, 2:1] 通过遍历Conv layers计算获得的feature maps，为每一个点都配备这9种anchors作为初始的检测框。这样做获得检测框很不准确，不用担心，后面还有2次bounding box regression可以修正检测框位置. Code12345678""" intersection of two bboxes@param ai: left top x,y and right bottom x,y coordinates of bbox 1@param bi: left top x,y and right bottom x,y coordinates of bbox 2@return: area_union: whether contain target classes"""def intersection(ai, bi): 123456789""" union of two bboxes@param au: left top x,y and right bottom x,y coordinates of bbox 1@param bu: left top x,y and right bottom x,y coordinates of bbox 2@param area_intersection: intersection area@return: area_union: whether contain target classes"""def union(au, bu, area_intersection): 12345678""" calculate ratio of intersection and union@param a: left top x,y and right bottom x,y coordinates of bbox 1@param b: left top x,y and right bottom x,y coordinates of bbox 2@return: ratio of intersection and union of two bboxes"""def iou(a, b): IOU is used to bounding box regression rpn calculation Traversal all pre-anchors to calculate IOU with GT bboxes Set number and proprty of pre-anchors return specity number of result(Anchors) 12345678910111213141516""" @param C: configuration@param img_data: parsered xml information@param width: orignal width of image@param hegiht: orignal height of image@param resized_width: resized width of image after image processing@param resized_heighth: resized height of image after image processing@param img_length_calc_function: Keras's image_dim_ordering function@return: np.copy(y_rpn_cls): whether contain target classes@return: np.copy(y_rpn_regr): corrspoding return of gradient"""def calc_rpn(C, img_data, width, height, resized_width, resized_height, img_length_calc_function): 【注：其只会返回num_regions（这里设置为256）个有效的正负样本 】 【流程】Initialise paramters: see jupyter notebook Calculate the size of map feature:1(output_width, output_height) = img_length_calc_function(resized_width, resized_height) Get the GT box coordinates, and resize to account for image resizingafter rezised functon, the coordinates of bboxes need to re-calculation:12345for bbox_num, bbox in enumerate(img_data['bboxes']): gta[bbox_num, 0] = bbox['x1'] * (resized_width / float(width)) gta[bbox_num, 1] = bbox['x2'] * (resized_width / float(width)) gta[bbox_num, 2] = bbox['y1'] * (resized_height / float(height)) gta[bbox_num, 3] = bbox['y2'] * (resized_height / float(height)) 【注意gta的存储形式是（x1,x2,y1,y2）而不是（x1,y1,x2,y2）】Traverse all possible group of sizesanchor box scales [128, 256, 512]anchor box ratios [1:1,1:2,2:1]1234for anchor_size_idx in range(len(anchor_sizes)): for anchor_ratio_idx in range(len(anchor_ratios)): anchor_x = anchor_sizes[anchor_size_idx] * anchor_ratios[anchor_ratio_idx][0] anchor_y = anchor_sizes[anchor_size_idx] * anchor_ratios[anchor_ratio_idx][1] Traver one bbox group, all pre boxes generated by anchors output_width，output_height：width and height of map featuredownscale：mapping ration, defualt 16if to delete box out of iamge 12345678910111213for ix in range(output_width): x1_anc = downscale * (ix + 0.5) - anchor_x / 2 x2_anc = downscale * (ix + 0.5) + anchor_x / 2 if x1_anc &lt; 0 or x2_anc &gt; resized_width: continue for jy in range(output_height): y1_anc = downscale * (jy + 0.5) - anchor_y / 2 y2_anc = downscale * (jy + 0.5) + anchor_y / 2 if y1_anc &lt; 0 or y2_anc &gt; resized_height: continue 【注：现在我们确定了一个预选框组合有确定了中心点那就是唯一确定一个框了，接下来就是来确定这个宽的性质了：是否包含物体、如包含物体其回归梯度是多少】 要确定以上两个性质，每一个框都需要遍历图中的所有bboxes 然后计算该预选框与bbox的交并比（IOU）如果现在的交并比curr_iou大于该bbox最好的交并比或者大于给定的阈值则求下列参数，这些参数是后来要用的即回归梯度 tx：两个框中心的宽的距离与预选框宽的比ty:同txtw:bbox的宽与预选框宽的比th:同理 12345678910if curr_iou &gt; best_iou_for_bbox[bbox_num] or curr_iou &gt; C.rpn_max_overlap: cx = (gta[bbox_num, 0] + gta[bbox_num, 1]) / 2.0 cy = (gta[bbox_num, 2] + gta[bbox_num, 3]) / 2.0 cxa = (x1_anc + x2_anc) / 2.0 cya = (y1_anc + y2_anc) / 2.0 tx = (cx - cxa) / (x2_anc - x1_anc) ty = (cy - cya) / (y2_anc - y1_anc) tw = np.log((gta[bbox_num, 1] - gta[bbox_num, 0]) / (x2_anc - x1_anc)) th = np.log((gta[bbox_num, 3] - gta[bbox_num, 2])) / (y2_anc - y1_anc) 对应于Faster RCNN原文，foreground anchor与ground truth之间的平移量 $(t_x, t_y)$ 如下： 对于训练bouding box regression网络回归分支，输入是cnn feature Φ，监督信号是Anchor与GT的差距 $(t_x, t_y, t_w, t_h)$，即训练目标是：输入 Φ的情况下使网络输出与监督信号尽可能接近。那么当bouding box regression工作时，再输入Φ时，回归网络分支的输出就是每个Anchor的平移量和变换尺度 $(t_x, t_y, t_w, t_h)$，显然即可用来修正Anchor位置了。 如果相交的不是背景，那么进行一系列更新 关于bbox的相关信息更新预选框的相关更新：如果交并比大于阈值这是posbest_iou_for_loc：其记录的是有最大交并比为多少和其对应的回归梯度num_anchors_for_bbox[bbox_num]：记录的是bbox拥有的pos预选框的个数如果小于最小阈值是neg，在这两个之间是neutral需要注意的是：判断一个框为neg需要其与所有的bbox的交并比都小于最小的阈值 1234567891011121314151617181920if img_data['bboxes'][bbox_num]['class'] != 'bg': # all GT boxes should be mapped to an anchor box, so we keep track of which anchor box was best if curr_iou &gt; best_iou_for_bbox[bbox_num]: best_anchor_for_bbox[bbox_num] = [jy, ix, anchor_ratio_idx, anchor_size_idx] best_iou_for_bbox[bbox_num] = curr_iou best_x_for_bbox[bbox_num, :] = [x1_anc, x2_anc, y1_anc, y2_anc] best_dx_for_bbox[bbox_num, :] = [tx, ty, tw, th] if curr_iou &gt; C.rpn_max_overlap: bbox_type = 'pos' num_anchors_for_bbox[bbox_num] += 1 if curr_iou &gt; best_iou_for_loc: best_iou_for_loc = curr_iou best_regr = (tx, ty, tw, th) if C.rpn_min_overlap &lt; curr_iou &lt; C.rpn_max_overlap: if bbox_type != 'pos': bbox_type = 'neutral' 当结束对所有的bbox的遍历时，来确定该预选宽的性质。 y_is_box_valid：该预选框是否可用（nertual就是不可用的）y_rpn_overlap：该预选框是否包含物体y_rpn_regr:回归梯度1234567891011if bbox_type == 'neg': y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1 y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0elif bbox_type == 'neutral': y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0 y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0else: y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1 y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1 start = 4 * (anchor_ratio_idx + n_anchratios * anchor_size_idx) y_rpn_regr[jy, ix, start:start+4] = best_regr 如果有一个bbox没有pos的预选宽和其对应，这找一个与它交并比最高的anchor的设置为pos123456789for idx in range(num_anchors_for_bbox.shape[0]): if num_anchors_for_bbox[idx] == 0: # no box with an IOU greater than zero ... if best_anchor_for_bbox[idx, 0] == -1: continue y_is_box_valid[best_anchor_for_bbox[idx,0], best_anchor_for_bbox[idx,1], best_anchor_for_bbox[idx,2] + n_anchratios *best_anchor_for_bbox[idx,3]] = 1 y_rpn_overlap[best_anchor_for_bbox[idx,0], best_anchor_for_bbox[idx,1], best_anchor_for_bbox[idx,2] + n_anchratios *best_anchor_for_bbox[idx,3]] = 1 start = 4 * (best_anchor_for_bbox[idx,2] + n_anchratios * best_anchor_for_bbox[idx,3]) y_rpn_regr[best_anchor_for_bbox[idx,0], best_anchor_for_bbox[idx,1], start:start+4] = best_dx_for_bbox[idx, :] 将深度变到第一位，给向量增加一个维度, 在Tensorflow中， 第一纬度是batch size, 此外， 变换向量位置匹配要求12345678y_rpn_overlap = np.transpose(y_rpn_overlap, (2, 0, 1))y_rpn_overlap = np.expand_dims(y_rpn_overlap, axis=0)y_is_box_valid = np.transpose(y_is_box_valid, (2, 0, 1))y_is_box_valid = np.expand_dims(y_is_box_valid, axis=0)y_rpn_regr = np.transpose(y_rpn_regr, (2, 0, 1))y_rpn_regr = np.expand_dims(y_rpn_regr, axis=0) 从可用的预选框中选择num_regions如果pos的个数大于num_regions / 2，则将多下来的地方置为不可用。如果小于pos不做处理接下来将pos与neg总是超过num_regions个的neg预选框置为不可用最后， 256个预选框，128个positive,128个negative 会生成 在一张图片里面123456789101112pos_locs = np.where(np(y_rpn_overlap[0, :, :, :] =.logical_and= 1, y_is_box_valid[0, :, :, :] == 1))neg_locs = np.where(np.logical_and(y_rpn_overlap[0, :, :, :] == 0, y_is_box_valid[0, :, :, :] == 1))num_regions = 256if len(pos_locs[0]) &gt; num_regions / 2: val_locs = random.sample(range(len(pos_locs[0])), len(pos_locs[0]) - num_regions / 2) y_is_box_valid[0, pos_locs[0][val_locs], pos_locs[1][val_locs], pos_locs[2][val_locs]] = 0 num_pos = num_regions / 2if len(neg_locs[0]) + num_pos &gt; num_regions: val_locs = random.sample(range(len(neg_locs[0])), len(neg_locs[0]) - num_pos) y_is_box_valid[0, neg_locs[0][val_locs], neg_locs[1][val_locs], neg_locs[2][val_locs]] = 0 【27/06/2018】project briefRe organization of Project plan Anchors IterativeIntegration of privous work:In each anchor: config file -&gt; rpn_stride = 16 means generate one anchor in 16 pixelsJupyter Notebook address 【流程】Function description12345678910111213141516"""@param all_img_data: Parsered xml file @param class_count: Counting of the number of all classes objects@param C: Configuration class@param img_length_calc_function: resnet's get_img_output_length() function@param backend: Tensorflow in this project#param mode: train or valyield np.copy(x_img), [np.copy(y_rpn_cls), np.copy(y_rpn_regr)], img_data_aug@return: np.copy(x_img): image's matrix data@return: [np.copy(y_rpn_cls), np.copy(y_rpn_regr)]: calculated rpn class and radient@return: img_data_aug: correspoding parsed xml information"""def get_anchor_gt(all_img_data, class_count, C, img_length_calc_function, backend, mode='train'): Traverse all input image based on input xml information Apply class balance function: 1234C.balanced_classes = Truesample_selector = image_processing.SampleSelector(class_count)if C.balanced_classes and sample_selector.skip_sample_for_balanced_class(img_data): continue Apply image enhanceif input mode is train, apply image enhance to obtain augmented image xml and matrix, if mode is val, obtain image orignal xml and matrix1234if mode == 'train': img_data_aug, x_img = image_enhance.augment(img_data, C, augment=True)else: img_data_aug, x_img = image_enhance.augment(img_data, C, augment=False) verifacation width and hegiht in xml and matrix1234(width, height) = (img_data_aug['width'], img_data_aug['height'])(rows, cols, _) = x_img.shapeassert cols == widthassert rows == height Apply rezise function12(resized_width, resized_height) = image_processing.get_new_img_size(width, height, C.im_size)x_img = cv2.resize(x_img, (resized_width, resized_height), interpolation=cv2.INTER_CUBIC) Apply rpn calculation1y_rpn_cls, y_rpn_regr = rpn_calculation.calc_rpn(C, img_data_aug, width, height, resized_width, resized_height, img_length_calc_function) Zero-center by mean pixel, and preprocess image formatBGR -&gt; RGB because when apply resnet, it need RGB but in cv2, it use BGR 1x_img = x_img[:,:, (2, 1, 0)] For using pre-trainning model, needs to mins mean channel in each dim 12345x_img = x_img.astype(np.float32)x_img[:, :, 0] -= C.img_channel_mean[0]x_img[:, :, 1] -= C.img_channel_mean[1]x_img[:, :, 2] -= C.img_channel_mean[2]x_img /= C.img_scaling_factor # default to 1,so no change here expand for batch size 1x_img = np.expand_dims(x_img, axis=0) for using pre-trainning model, need to sclaling the std to match pre trained model 1y_rpn_regr[:, y_rpn_regr.shape[1]//2:, :, :] *= C.std_scaling # scaling is 4 here in tensorflow, sort as batch size, width, height, deep 1234if backend == 'tf': x_img = np.transpose(x_img, (0, 3, 2, 1)) y_rpn_cls = np.transpose(y_rpn_cls, (0, 3, 2, 1)) y_rpn_regr = np.transpose(y_rpn_regr, (0, 3, 2, 1)) generator to iteror, using next() to loop 1yield np.copy(x_img), [np.copy(y_rpn_cls), np.copy(y_rpn_regr)], img_data_aug 【执行】12data_gen_train = get_anchor_gt(train_imgs, classes_count, C, nn.get_img_output_length, K.image_dim_ordering(), mode='train')data_gen_val = get_anchor_gt(val_imgs, classes_count, C, nn.get_img_output_length,K.image_dim_ordering(), mode='val') Test:1img,rpn,img_aug = next(data_gen_train) 【28/06/2018】Resnet50 structure论文链接: https://arxiv.org/abs/1512.03385 首先，我们要问一个问题：Is learning better networks as easy as stacking more layers? 很显然不是，原因有二。一，vanishing/exploding gradients；深度会带来恶名昭著的梯度弥散/爆炸，导致系统不能收敛。然而梯度弥散/爆炸在很大程度上被normalized initialization and intermediate normalization layers处理了。二、degradation；当深度开始增加的时候，accuracy经常会达到饱和，然后开始下降，但这并不是由于过拟合引起的。可见figure1，56-layer的error大于20-layer的error。 He kaiMing大神认为靠堆layers竟然会导致degradation，那肯定是我们堆的方式不对。因此他提出了一种基于残差块的identity mapping，通过学习残差的方式，而非直接去学习直接的映射关系。 事实证明，靠堆积残差块能够带来很好效果提升。而不断堆积plain layer却会带来很高的训练误差残差块的两个优点：1) Our extremely deep residual nets are easy to optimize, but the counterpart “plain” nets (that simply stack layers) exhibit higher training error when the depth increases;2) Our deep residual nets can easily enjoy accuracy gains from greatly increased depth, producing results substantially better than previous networks. 【29/06/2018】Resnet50 image structureResNet有2个基本的block，一个是Identity Block，输入和输出的dimension是一样的，所以可以串联多个；另外一个基本block是Conv Block，输入和输出的dimension是不一样的，所以不能连续串联，它的作用本来就是为了改变feature vector的dimension 因为CNN最后都是要把image一点点的convert成很小但是depth很深的feature map，一般的套路是用统一的比较小的kernel（比如VGG都是用3x3），但是随着网络深度的增加，output的channel也增大（学到的东西越来越复杂），所以有必要在进入Identity Block之前，用Conv Block转换一下维度，这样后面就可以连续接Identity Block. 可以看下Conv Block是怎么改变输出维度的:其实就是在shortcut path的地方加上一个conv2D layer（1x1 filter size），然后在main path改变dimension，并与shortcut path对应起来. July【02/07/2018】Construct resnet by keras残差网络的关键步骤，跨层的合并需要保证x和F(x)的shape是完全一样的，否则它们加不起来。 理解了这一点，我们开始用keras做实现，我们把输入输出大小相同的模块称为identity_block，而把输出比输入小的模块称为conv_block，首先，导入所需的模块： 1234from keras.models import Modelfrom keras.layers import Input,Dense,BatchNormalization,Conv2D,MaxPooling2D,AveragePooling2D,ZeroPadding2Dfrom keras.layers import add,Flattenfrom keras.optimizers import SGD 我们先来编写identity_block，这是一个函数，接受一个张量为输入，并返回一个张量, 然后是conv层，是有shortcut的：1234567891011121314151617181920212223def Conv2d_BN(x, nb_filter,kernel_size, strides=(1,1), padding='same',name=None): if name is not None: bn_name = name + '_bn' conv_name = name + '_conv' else: bn_name = None conv_name = None x = Conv2D(nb_filter,kernel_size,padding=padding,strides=strides,activation='relu',name=conv_name)(x) x = BatchNormalization(axis=3,name=bn_name)(x) return x def Conv_Block(inpt,nb_filter,kernel_size,strides=(1,1), with_conv_shortcut=False): x = Conv2d_BN(inpt,nb_filter=nb_filter[0],kernel_size=(1,1),strides=strides,padding='same') x = Conv2d_BN(x, nb_filter=nb_filter[1], kernel_size=(3,3), padding='same') x = Conv2d_BN(x, nb_filter=nb_filter[2], kernel_size=(1,1), padding='same') if with_conv_shortcut: shortcut = Conv2d_BN(inpt,nb_filter=nb_filter[2],strides=strides,kernel_size=kernel_size) x = add([x,shortcut]) return x else: x = add([x,inpt]) return x 剩下的事情就很简单了，数好identity_block和conv_block是如何交错的，照着网络搭就好了：1234567891011121314151617181920212223242526272829303132inpt = Input(shape=(224,224,3))x = ZeroPadding2D((3,3))(inpt)x = Conv2d_BN(x,nb_filter=64,kernel_size=(7,7),strides=(2,2),padding='valid')x = MaxPooling2D(pool_size=(3,3),strides=(2,2),padding='same')(x) x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3),strides=(1,1),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[64,64,256],kernel_size=(3,3)) x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[128,128,512],kernel_size=(3,3)) x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[256,256,1024],kernel_size=(3,3)) x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3),strides=(2,2),with_conv_shortcut=True)x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3))x = Conv_Block(x,nb_filter=[512,512,2048],kernel_size=(3,3))x = AveragePooling2D(pool_size=(7,7))(x)x = Flatten()(x)x = Dense(1000,activation='softmax')(x)model = Model(inputs=inpt,outputs=x)sgd = SGD(decay=0.0001,momentum=0.9)model.compile(loss='categorical_crossentropy',optimizer=sgd,metrics=['accuracy'])model.summary() jupyter notebook 【03/07/2018】load pre-trained model of resnet50步骤如下： 下载ResNet50不包含全连接层的模型参数到本地（resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5）； 定义好ResNet50的网络结构； 将预训练的模型参数加载到我们所定义的网络结构中； 更改全连接层结构，便于对我们的分类任务进行处 或者根据需要解冻最后几个block，然后以很低的学习率开始训练。我们只选择最后一个block进行训练，是因为训练样本很少，而ResNet50模型层数很多，全部训练肯定不能训练好，会过拟合。 其次fine-tune时由于是在一个已经训练好的模型上进行的，故权值更新应该是一个小范围的，以免破坏预训练好的特征。 下载地址 因为使用了预训练模型，参数名称需要和预训练模型一致：identity层：1234567891011121314151617181920212223242526272829def identity_block(X, f, filters, stage, block): # defining name basis conv_name_base = 'res' + str(stage) + block + '_branch' bn_name_base = 'bn' + str(stage) + block + '_branch' # Retrieve Filters F1, F2, F3 = filters # Save the input value. You'll need this later to add back to the main path. X_shortcut = X # First component of main path X = Conv2D(filters = F1, kernel_size = (1, 1), strides = (1,1), padding = 'valid', name = conv_name_base + '2a')(X) X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X) X = Activation('relu')(X) # Second component of main path (≈3 lines) X = Conv2D(filters= F2, kernel_size=(f,f),strides=(1,1),padding='same',name=conv_name_base + '2b')(X) X = BatchNormalization(axis=3,name=bn_name_base+'2b')(X) X = Activation('relu')(X) # Third component of main path (≈2 lines) X = Conv2D(filters=F3,kernel_size=(1,1),strides=(1,1),padding='valid',name=conv_name_base+'2c')(X) X = BatchNormalization(axis=3,name=bn_name_base+'2c')(X) # Final step: Add shortcut value to main path, and pass it through a RELU activation (≈2 lines) X = Add()([X, X_shortcut]) X = Activation('relu')(X) return X conv层：1234567891011121314151617181920212223242526272829303132333435def convolutional_block(X, f, filters, stage, block, s = 2): # defining name basis conv_name_base = 'res' + str(stage) + block + '_branch' bn_name_base = 'bn' + str(stage) + block + '_branch' # Retrieve Filters F1, F2, F3 = filters # Save the input value X_shortcut = X ##### MAIN PATH ##### # First component of main path X = Conv2D(F1, (1, 1), strides = (s,s),padding='valid',name = conv_name_base + '2a')(X) X = BatchNormalization(axis = 3, name = bn_name_base + '2a')(X) X = Activation('relu')(X) # Second component of main path (≈3 lines) X = Conv2D(F2,(f,f),strides=(1,1),padding='same',name=conv_name_base+'2b')(X) X = BatchNormalization(axis=3,name=bn_name_base+'2b')(X) X = Activation('relu')(X) # Third component of main path (≈2 lines) X = Conv2D(F3,(1,1),strides=(1,1),padding='valid',name=conv_name_base+'2c')(X) X = BatchNormalization(axis=3,name=bn_name_base+'2c')(X) ##### SHORTCUT PATH #### (≈2 lines) X_shortcut = Conv2D(F3,(1,1),strides=(s,s),padding='valid',name=conv_name_base+'1')(X_shortcut) X_shortcut = BatchNormalization(axis=3,name =bn_name_base+'1')(X_shortcut) # Final step: Add shortcut value to main path, and pass it through a RELU activation (≈2 lines) X = Add()([X,X_shortcut]) X = Activation('relu')(X) return X resnet50结构：1234567891011121314151617181920212223242526272829303132333435363738394041424344454647def ResNet50(input_shape = (64, 64, 3), classes = 30): # Define the input as a tensor with shape input_shape X_input = Input(input_shape) # Zero-Padding X = ZeroPadding2D((3, 3))(X_input) # Stage 1 X = Conv2D(64, (7, 7), strides = (2, 2), name = 'conv1')(X) X = BatchNormalization(axis = 3, name = 'bn_conv1')(X) X = Activation('relu')(X) X = MaxPooling2D((3, 3), strides=(2, 2))(X) # Stage 2 X = convolutional_block(X, f = 3, filters = [64, 64, 256], stage = 2, block='a', s = 1) X = identity_block(X, 3, [64, 64, 256], stage=2, block='b') X = identity_block(X, 3, [64, 64, 256], stage=2, block='c') ### START CODE HERE ### # Stage 3 (≈4 lines) X = convolutional_block(X, f = 3,filters= [128,128,512],stage=3,block='a',s=2) X = identity_block(X,3,[128,128,512],stage=3,block='b') X = identity_block(X,3,[128,128,512],stage=3,block='c') X = identity_block(X,3,[128,128,512],stage=3,block='d') # Stage 4 (≈6 lines) X = convolutional_block(X,f=3,filters=[256,256,1024],stage=4,block='a',s=2) X = identity_block(X,3,[256,256,1024],stage=4,block='b') X = identity_block(X,3,[256,256,1024],stage=4,block='c') X = identity_block(X,3,[256,256,1024],stage=4,block='d') X = identity_block(X,3,[256,256,1024],stage=4,block='e') X = identity_block(X,3,[256,256,1024],stage=4,block='f') # Stage 5 (≈3 lines) X = convolutional_block(X, f = 3,filters= [512,512,2048],stage=5,block='a',s=2) X = identity_block(X,3,[512,512,2048],stage=5,block='b') X = identity_block(X,3,[512,512,2048],stage=5,block='c') # AVGPOOL (≈1 line). Use "X = AveragePooling2D(...)(X)" X = AveragePooling2D((2,2),strides=(2,2))(X) # output layer X = Flatten()(X) model = Model(inputs = X_input, outputs = X, name='ResNet50') return model 构建网络并且载入权重：12base_model = ResNet50(input_shape=(224,224,3),classes=30) base_model.load_weights('resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5') 无法载入 【04/07/2018】Loading pre-trained model对于keras：如果新模型和旧模型结构一样，直接调用model.load_weights读取参数就行。如果新模型中的几层和之前模型一样，也通过model.load_weights(‘my_model_weights.h5’, by_name=True)来读取参数， 或者手动对每一层进行参数的赋值，比如x= Dense(100, weights=oldModel.layers[1].get_weights())(x) 修改代码：12345try: base_model.load_weights('resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5',by_name=True) print("load successful")except: print("load failed") 载入成功：jupyter notebook 【05~06/07/2018】construct faster rcnn netRoiPoolingConv该函数的作用是对将每一个预选框框定的特征图大小规整到相同大小什么是ROI呢？ROI是Region of Interest的简写，指的是在“特征图上的框”；1）在Fast RCNN中， RoI是指Selective Search完成后得到的“候选框”在特征图上的映射，如下图所示；2）在Faster RCNN中，候选框是经过RPN产生的，然后再把各个“候选框”映射到特征图上，得到RoIs创建一个类，这里不同的是它是要继承keras的Layer类1class RoiPoolingConv(Layer): 编写自己的层 定义：**kwargs：表示的就是形参中按照关键字传值把多余的传值以字典的方式呈现super:子类调用父类的初始化方法1234567891011121314151617'''ROI pooling layer for 2D inputs. # Arguments pool_size: int Size of pooling region to use. pool_size = 7 will result in a 7x7 region. num_rois: number of regions of interest to be used '''# 第一个是规整后特征图大小 第二个是预选框个数 def __init__(self, pool_size, num_rois, **kwargs): self.dim_ordering = K.image_dim_ordering() # print error when kernel not tensorflow or thoean assert self.dim_ordering in &#123;'tf'&#125;, 'dim_ordering must be in tf' self.pool_size = pool_size self.num_rois = num_rois super(RoiPoolingConv, self).__init__(**kwargs) 得到特征图的输出通道个数:123def build(self, input_shape): if self.dim_ordering == 'tf': self.nb_channels = input_shape[0][3] 定义输出特征图的形状：123def compute_output_shape(self, input_shape): if self.dim_ordering == 'tf': return None, self.num_rois, self.pool_size, self.pool_size, self.nb_channels 遍历提供的所有预选框,将预选宽里的特征图规整到指定大小, 并且加入到output:12345678910111213141516171819202122232425262728293031323334353637383940def call(self, x, mask=None): assert(len(x) == 2) img = x[0] rois = x[1] input_shape = K.shape(img) outputs = [] for roi_idx in range(self.num_rois): x = rois[0, roi_idx, 0] y = rois[0, roi_idx, 1] w = rois[0, roi_idx, 2] h = rois[0, roi_idx, 3] row_length = w / float(self.pool_size) col_length = h / float(self.pool_size) num_pool_regions = self.pool_size if self.dim_ordering == 'tf': x = K.cast(x, 'int32') y = K.cast(y, 'int32') w = K.cast(w, 'int32') h = K.cast(h, 'int32') # resize porposal of feature map rs = tf.image.resize_images(img[:, y:y+h, x:x+w, :], (self.pool_size, self.pool_size)) outputs.append(rs) # 将outputs里面的变量按照第一个维度合在一起【shape:(?, 7, 7, 512)】 final_output = K.concatenate(outputs, axis=0) final_output = K.reshape(final_output, (1, self.num_rois, self.pool_size, self.pool_size, self.nb_channels)) # 将变量规整到相应的大小【shape:(1, 32, 7, 7, 512)】 final_output = K.permute_dimensions(final_output, (0, 1, 2, 3, 4)) return final_output 输出是batch个vector，其中batch的值等于RoI的个数，vector的大小为channel * w * h；RoI Pooling的过程就是将一个个大小不同的box矩形框，都映射成大小固定（w * h）的矩形框. TimeDistributed 包装器FastRcnn在做完ROIpooling后，需要将生产的所有的Roi全部送入分类和回归网络，Keras用的TimeDistributed函数： Relu激活函数本身就是逐元素计算激活值的，无论进来多少维的tensor都一样，所以不需要使用TimeDistributed。conv2D需要TimeDistributed，是因为一个ROI内的数据计算是互相依赖的，而不同ROI之间又是独立的。 在最后Faster RCNN的结构中进行类别判断和bbox框的回归时，需要对设置的num_rois个感兴趣区域进行回归处理，由于每一个区域的处理是相对独立的，便等价于此时的时间步为num_rois，因此用TimeDistributed来wrap。 改编之前的conv 和 identity层：12345678910111213141516171819202122232425262728def conv_block_td(input_tensor, kernel_size, filters, stage, block, input_shape, strides=(2, 2), trainable=True): # conv block time distributed nb_filter1, nb_filter2, nb_filter3 = filters bn_axis = 3 conv_name_base = 'res' + str(stage) + block + '_branch' bn_name_base = 'bn' + str(stage) + block + '_branch' x = TimeDistributed(Convolution2D(nb_filter1, (1, 1), strides=strides, trainable=trainable, kernel_initializer='normal'), input_shape=input_shape, name=conv_name_base + '2a')(input_tensor) x = TimeDistributed(BatchNormalization(axis=bn_axis), name=bn_name_base + '2a')(x) x = Activation('relu')(x) x = TimeDistributed(Convolution2D(nb_filter2, (kernel_size, kernel_size), padding='same', trainable=trainable, kernel_initializer='normal'), name=conv_name_base + '2b')(x) x = TimeDistributed(BatchNormalization(axis=bn_axis), name=bn_name_base + '2b')(x) x = Activation('relu')(x) x = TimeDistributed(Convolution2D(nb_filter3, (1, 1), kernel_initializer='normal'), name=conv_name_base + '2c', trainable=trainable)(x) x = TimeDistributed(BatchNormalization(axis=bn_axis), name=bn_name_base + '2c')(x) shortcut = TimeDistributed(Convolution2D(nb_filter3, (1, 1), strides=strides, trainable=trainable, kernel_initializer='normal'), name=conv_name_base + '1')(input_tensor) shortcut = TimeDistributed(BatchNormalization(axis=bn_axis), name=bn_name_base + '1')(shortcut) x = Add()([x, shortcut]) x = Activation('relu')(x) return x 1234567891011121314151617181920212223242526def identity_block_td(input_tensor, kernel_size, filters, stage, block, trainable=True): # identity block time distributed nb_filter1, nb_filter2, nb_filter3 = filters bn_axis = 3 conv_name_base = 'res' + str(stage) + block + '_branch' bn_name_base = 'bn' + str(stage) + block + '_branch' x = TimeDistributed(Convolution2D(nb_filter1, (1, 1), trainable=trainable, kernel_initializer='normal'), name=conv_name_base + '2a')(input_tensor) x = TimeDistributed(BatchNormalization(axis=bn_axis), name=bn_name_base + '2a')(x) x = Activation('relu')(x) x = TimeDistributed(Convolution2D(nb_filter2, (kernel_size, kernel_size), trainable=trainable, kernel_initializer='normal',padding='same'), name=conv_name_base + '2b')(x) x = TimeDistributed(BatchNormalization(axis=bn_axis), name=bn_name_base + '2b')(x) x = Activation('relu')(x) x = TimeDistributed(Convolution2D(nb_filter3, (1, 1), trainable=trainable, kernel_initializer='normal'), name=conv_name_base + '2c')(x) x = TimeDistributed(BatchNormalization(axis=bn_axis), name=bn_name_base + '2c')(x) x = Add()([x, input_tensor]) x = Activation('relu')(x) return x 如果将时序信号看作是2D矩阵，则TimeDistributed包装后的Dense就是分别对矩阵的每一行进行全连接。 把resnet50最后一个stage拿出来做分类层：1234567891011def classifier_layers(x, input_shape, trainable=False): # Stage 5 x = conv_block_td(x, 3, [512, 512, 2048], stage=5, block='a', input_shape=input_shape, strides=(2, 2), trainable=trainable) x = identity_block_td(x, 3, [512, 512, 2048], stage=5, block='b', trainable=trainable) x = identity_block_td(x, 3, [512, 512, 2048], stage=5, block='c', trainable=trainable) # AVGPOOL x = TimeDistributed(AveragePooling2D((7, 7)), name='avg_pool')(x) return x RoiPoolingConv：返回的shape为(1, 32, 7, 7, 512)含义是batch_size,预选框的个数，特征图宽，特征图高度，特征图深度 TimeDistributed：输入至少为3D张量，下标为1的维度将被认为是时间维。即对以一个维度下的变量当作一个完整变量来看待本文是32。你要实现的目的就是对32个预选宽提出的32个图片做出判断。 out_class的shape:(?, 32, 21); out_regr的shape:(?, 32, 80)1234567891011121314def classifier(base_layers, input_rois, num_rois, nb_classes = 21, trainable=False): pooling_regions = 14 input_shape = (num_rois,14,14,1024) out_roi_pool = RoiPoolingConv(pooling_regions, num_rois)([base_layers, input_rois]) out = classifier_layers(out_roi_pool, input_shape=input_shape, trainable=True) out = TimeDistributed(Flatten())(out) out_class = TimeDistributed(Dense(nb_classes, activation='softmax', kernel_initializer='zero'), name='dense_class_&#123;&#125;'.format(nb_classes))(out) # note: no regression target for bg class out_regr = TimeDistributed(Dense(4 * (nb_classes-1), activation='linear', kernel_initializer='zero'), name='dense_regress_&#123;&#125;'.format(nb_classes))(out) return [out_class, out_regr] 定义RPN网络： x_class:每一个锚点属于前景还是背景【注：这里使用的是sigmoid激活函数所以其输出的通道数是num_anchors】 x_regr：每一个锚点对应的回归梯度12345678def rpn(base_layers,num_anchors): x = Convolution2D(512, (3, 3), padding='same', activation='relu', kernel_initializer='normal', name='rpn_conv1')(base_layers) x_class = Convolution2D(num_anchors, (1, 1), activation='sigmoid', kernel_initializer='uniform', name='rpn_out_class')(x) x_regr = Convolution2D(num_anchors * 4, (1, 1), activation='linear', kernel_initializer='zero', name='rpn_out_regress')(x) return [x_class, x_regr, base_layers] resnet前面部分作为公共层：123456789101112131415161718192021222324252627282930313233343536373839404142434445def nn_base(input_tensor=None, trainable=False): # Determine proper input shape input_shape = (None, None, 3) if input_tensor is None: img_input = Input(shape=input_shape) else: if not K.is_keras_tensor(input_tensor): img_input = Input(tensor=input_tensor, shape=input_shape) else: img_input = input_tensor bn_axis = 3 # Zero-Padding x = ZeroPadding2D((3, 3))(img_input) # Stage 1 x = Convolution2D(64, (7, 7), strides=(2, 2), name='conv1', trainable = trainable)(x) x = BatchNormalization(axis=bn_axis, name='bn_conv1')(x) x = Activation('relu')(x) x = MaxPooling2D((3, 3), strides=(2, 2))(x) # Stage 2 x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1), trainable = trainable) x = identity_block(x, 3, [64, 64, 256], stage=2, block='b', trainable = trainable) x = identity_block(x, 3, [64, 64, 256], stage=2, block='c', trainable = trainable) # Stage 3 x = conv_block(x, 3, [128, 128, 512], stage=3, block='a', trainable = trainable) x = identity_block(x, 3, [128, 128, 512], stage=3, block='b', trainable = trainable) x = identity_block(x, 3, [128, 128, 512], stage=3, block='c', trainable = trainable) x = identity_block(x, 3, [128, 128, 512], stage=3, block='d', trainable = trainable) # Stage 4 x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a', trainable = trainable) x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b', trainable = trainable) x = identity_block(x, 3, [256, 256, 1024], stage=4, block='c', trainable = trainable) x = identity_block(x, 3, [256, 256, 1024], stage=4, block='d', trainable = trainable) x = identity_block(x, 3, [256, 256, 1024], stage=4, block='e', trainable = trainable) x = identity_block(x, 3, [256, 256, 1024], stage=4, block='f', trainable = trainable) return x 搭建网络：123456789101112131415# define the base network (resnet here)shared_layers = nn.nn_base(img_input, trainable=True)# define the RPN, built on the base layers# 9 types of anchorsnum_anchors = len(C.anchor_box_scales) * len(C.anchor_box_ratios)rpn = nn.rpn(shared_layers, num_anchors)classifier = nn.classifier(shared_layers, roi_input, C.num_rois, nb_classes=len(classes_count), trainable=True)model_rpn = Model(img_input, rpn[:2])model_classifier = Model([img_input, roi_input], classifier)# this is a model that holds both the RPN and the classifier, used to load/save weights for the modelsmodel_all = Model([img_input, roi_input], rpn[:2] + classifier) jupyter notebook 【09/07/2018】Loss define 由于涉及到分类和回归，所以需要定义一个多任务损失函数(Multi-task Loss Function)，包括Softmax Classification Loss和Bounding Box Regression Loss，公式定义如下： $L({p_{i}},{t_{i}}) = \frac{1}{N_{cls}}\sum_{i}L_{cls}(p_{i},p_{i}^{\ast}) + \lambda\frac{1}{N_{reg}}\sum_{i}L_{reg}(t_{i},t_{i}^{\ast})$ Softmax Classification：对于RPN网络的分类层(cls)，其向量维数为2k = 18，考虑整个特征图conv5-3，则输出大小为W×H×18，正好对应conv5-3上每个点有9个anchors，而每个anchor又有两个score(fg/bg)输出，对于单个anchor训练样本，其实是一个二分类问题。为了便于Softmax分类，需要对分类层执行reshape操作，这也是由底层数据结构决定的。在上式中，$p_{i}$为样本分类的概率值，$p_{i}^{\ast}$为样本的标定值(label)，anchor为正样本时$p_{i}^{\ast}$为1，为负样本时$p_{i}^{\ast}$为0，$L_{cls}$为两种类别的对数损失(log loss)。 Bounding Box Regression：RPN网络的回归层输出向量的维数为4k = 36，回归参数为每个样本的坐标$[x,y,w,h]$，分别为box的中心位置和宽高，考虑三组参数预测框(predicted box)坐标$[x,y,w,h]$，anchor坐标$[x_{a},y_{a},w_{a},h_{a}]$，ground truth坐标$[x^{\ast},y^{\ast},w^{\ast},h^{\ast}]$，分别计算预测框相对anchor中心位置的偏移量以及宽高的缩放量{$t$}，ground truth相对anchor的偏移量和缩放量{$t^{\ast}$} $t_{x}=\frac{(x-x_{a})}{w_{a}}$ , $t_{y}=\frac{(y-y_{a})}{h_{a}}$ , $t_{w}=log(\frac{w}{w_{a}})$ , $t_{h}=log(\frac{h}{h_{a}})$ (1)$t_{x}^{\ast}=\frac{(x^{\ast}-x_{a})}{w_{a}}$ , $t_{y}^{\ast}=\frac{(y^{\ast}-y_{a})}{h_{a}}$ , $t_{w}^{\ast}=log(\frac{w^{\ast}}{w_{a}})$ , $t_{h}^{\ast}=log(\frac{h^{\ast}}{h_{a}})$ (2) 回归目标就是让{t}尽可能地接近${t^{\ast}}$，所以回归真正预测输出的是${t}$，而训练样本的标定真值为${t^{\ast}}$。得到预测输出${t}$后，通过上式(1)即可反推获取预测框的真实坐标。 在损失函数中，回归损失采用Smooth L1函数: $$ Smooth_{L1}(x) =\left{\begin{aligned}0.5x^{2} \ \ |x| \leqslant 1\|x| - 0.5 \ \ otherwise\end{aligned}\right.$$ $L_{reg} = Smooth_{L1}(t-t^{\ast})$Smooth L1损失函数曲线如下图所示，相比于L2损失函数，L1对离群点或异常值不敏感，可控制梯度的量级使训练更易收敛。 在损失函数中，$p_{i}^{\ast}L_{reg}$这一项表示只有目标anchor$(p_{i}^{\ast}=1)$才有回归损失，其他anchor不参与计算。这里需要注意的是，当样本bbox和ground truth比较接近时(IoU大于某一阈值)，可以认为上式的坐标变换是一种线性变换，因此可将样本用于训练线性回归模型，否则当bbox与ground truth离得较远时，就是非线性问题，用线性回归建模显然不合理，会导致模型不work。分类层(cls)和回归层(reg)的输出分别为{p}和{t}，两项损失函数分别由$N_{cls}$和$N_{reg}$以及一个平衡权重λ归一化。 【10/07/2018】loss code generator to iteror, using next() to loop1yield np.copy(x_img), [np.copy(y_rpn_cls), np.copy(y_rpn_regr)], img_data_aug Rpn calculation:1img,rpn,img_aug = next(data_gen_train) 连续两个def 是装饰器，装饰器其实也就是一个函数，一个用来包装函数的函数，返回一个修改之后的函数对象。经常被用于有切面需求的场景，较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计，有了装饰器，我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲，装饰器的作用就是为已经存在的对象添加额外的功能。 根据：$L$ 的 cls 部分$L({p_{i}},{t_{i}}) = \frac{1}{N_{cls}}\sum_{i}L_{cls}(p_{i},p_{i}^{\ast})$ 在上式中，$p_{i}$为样本分类的概率值，$p_{i}^{\ast}$为样本的标定值(label)，anchor为正样本时$p_{i}^{\ast}$为1，为负样本时$p_{i}^{\ast}$为0，$L_{cls}$为两种类别的对数损失(log loss)。 因此， 定义 rpn loss cls:123456def rpn_loss_cls(num_anchors): def rpn_loss_cls_fixed_num(y_true, y_pred): # binary_crossentropy -&gt; logloss # epsilon to increase robustness return lambda_rpn_class * K.sum(y_true[:, :, :, :num_anchors] * K.binary_crossentropy(y_pred[:, :, :, :], y_true[:, :, :, num_anchors:])) / K.sum(epsilon + y_true[:, :, :, :num_anchors]) return rpn_loss_cls_fixed_num 根据$L$ 的 reg 部分$L({p_{i}},{t_{i}}) = \lambda\frac{1}{N_{reg}}\sum_{i}L_{reg}(t_{i},t_{i}^{\ast})$在损失函数中，回归损失采用Smooth L1函数: $$ Smooth_{L1}(x) =\left{\begin{aligned}0.5x^{2} \ \ |x| \leqslant 1\|x| - 0.5 \ \ otherwise\end{aligned}\right.$$$L_{reg} = Smooth_{L1}(t-t^{\ast})$ 因此， 定义 rpn loss reg:12345678910111213def rpn_loss_regr(num_anchors): def rpn_loss_regr_fixed_num(y_true, y_pred): # difference of ture value and predicted value x = y_true[:, :, :, 4 * num_anchors:] - y_pred # absulote value of difference x_abs = K.abs(x) # if absulote value less than 1, x_bool == 1, else x_bool = 0 x_bool = K.cast(K.less_equal(x_abs, 1.0), tf.float32) return lambda_rpn_regr * K.sum(y_true[:, :, :, :4 * num_anchors] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :, :, :4 * num_anchors]) return rpn_loss_regr_fixed_num 对class的loss来说用一样的方程，但是class_loss_cls是无差别求loss【这个可以用K.mean，是因为其是无差别的求loss】，不用管是否可用1234567891011def class_loss_regr(num_classes): def class_loss_regr_fixed_num(y_true, y_pred): x = y_true[:, :, 4*num_classes:] - y_pred x_abs = K.abs(x) x_bool = K.cast(K.less_equal(x_abs, 1.0), 'float32') return lambda_cls_regr * K.sum(y_true[:, :, :4*num_classes] * (x_bool * (0.5 * x * x) + (1 - x_bool) * (x_abs - 0.5))) / K.sum(epsilon + y_true[:, :, :4*num_classes]) return class_loss_regr_fixed_numdef class_loss_cls(y_true, y_pred): return lambda_cls_class * K.mean(categorical_crossentropy(y_true[0, :, :], y_pred[0, :, :])) 【11/07/2018】IridisHigh Performance Computing (HPC)Introduction Iridis 5 specifications #251 in hte world(Based on July 2018 TOPP500 list) with $R_{peak}\sim1305.6\ TFlops/s$ 464 2.0 GHz nodes with 40 cores per node, 192 GB memeory 10 nodes with 4xGTX1080TI GPUs, 28 cores(hyper-threaded), 128 GB memeory 10 nodes with 2xVolta Tesia GPUs, same as thandard compute 2.2 PB disk with paraller file system (&gt;12GB\s) £5M Project delivered by OCF/IBM MobaXterm create my own conda envieromentFllowing instroduction before Slurm command Command Definition sbatch Submits job scripts into system for execution (queued) scancel Cancels a job scontrol Used to display Slurm state, several options only available to root sinfo Display state of partitions and nodes squeue Display state of jobs salloc Submit a job for execution, or initiate job in real time Bash script1234567891011121314#!/bin/bash#SBATCH -J faster_rcnn #SBATCH -o train_7.out#SBATCH --ntasks=28#SBATCH --nodes=1#SBATCH --ntasks-per-node=8#SBATCH --time=00:05:00#SBATCH --gres=gpu:1#SBATCH -p lyceummodule load condamodule load cudasource activate projectpython test_frcnn.py 【12~13/07/2018】change plan因为faster r-cnn的搭建过程比想象中复杂，在咨询老师的意见以后，决定砍掉capsule的测试，专心faster-rcnn并且找到一些fine turn的方法。 1）基础特征提取网络ResNet，IncRes V2，ResNeXt 都是显著超越 VGG 的特征网络，当然网络的改进带来的是计算量的增加。 2）RPN通过更准确地 RPN 方法，减少 Proposal 个数，提高准确度。 3）改进分类回归层分类回归层的改进，包括 通过多层来提取特征 和 判别。 @改进1：ION论文：Inside outside net: Detecting objects in context with skip pooling and recurrent neural networks提出了两个方面的贡献： 1）Inside Net所谓 Inside 是指在 ROI 区域之内，通过连接不同 Scale 下的 Feature Map，实现多尺度特征融合。这里采用的是 Skip-Pooling，从 conv3-4-5-context 分别提取特征，后面会讲到。多尺度特征 能够提升对小目标的检测精度。 2）Outside Net所谓 Outside 是指 ROI 区域之外，也就是目标周围的 上下文（Contextual）信息。作者通过添加了两个 RNN 层（修改后的 IRNN）实现上下文特征提取。上下文信息 对于目标遮挡有比较好的适应。 @改进2：多尺度之 HyperNet论文：Hypernet: Towards accurate region proposal generation and joint object detection基于 Region Proposal 的方法，通过多尺度的特征提取来提高对小目标的检测能力，来看网络框图：分为 三个主要特征 来介绍（对应上面网络拓扑图的 三个红色框）： 1）Hyper Feature Extraction （特征提取）多尺度特征提取是本文的核心点，作者的方法稍微有所不同，他是以中间的 Feature 尺度为参考，前面的层通过 Max Pooling 到对应大小，后面的层则是通过 反卷积（Deconv）进行放大。多尺度 Feature ConCat 的时候，作者使用了 LRN进行归一化（类似于 ION 的 L2 Norm）。 2）Region Proposal Generation（建议框生成）作者设计了一个轻量级的 ConvNet，与 RPN 的区别不大（为写论文强创新)。一个 ROI Pooling层，一个 Conv 层，还有一个 FC 层。每个 Position 通过 ROI Pooling 得到一个 13*13 的 bin，通过 Conv（3*3*4）层得到一个 13*13*4 的 Cube，再通过 FC 层得到一个 256d 的向量。后面的 Score+ BBox_Reg 与 Faster并无区别，用于目标得分 和 Location OffSet。考虑到建议框的 Overlap，作者用了 Greedy NMS 去重，文中将 IOU参考设为 0.7，每个 Image 保留 1k 个 Region，并选择其中 Top-200 做 Detetcion。通过对比，要优于基于 Edge Box 重排序的 Deep Box，从多尺度上考虑比 Deep Proposal 效果更好。 3）Object Detection（目标检测）与 Fast RCNN基本一致，在原来的检测网络基础上做了两点改进：a）在 FC 层之前添加了一个 卷积层（3363），对特征有效降维；b）将 DropOut 从 0.5 降到 0.25；另外，与 Proposal一样采用了 NMS 进行 Box抑制，但由于之前已经做了，这一步的意义不大。 @改进3：多尺度之 MSCNN论文：A Unified Multi-scale Deep Convolutional Neural Network for Fast Object Detectiona）原图缩放，多个Scale的原图对应不同Scale的Feature；该方法计算多次Scale，每个Scale提取一次Feature，计算量巨大。 b）一幅输入图像对应多个分类器；不需要重复提取特征图，但对分类器要求很高，一般很难得到理想的结果。 c）原图缩放，少量Scale原图-&gt;少量特征图-&gt;多个Model模板；相当于对 a）和 b）的 Trade-Off。 d）原图缩放，少量Scale原图-&gt;少量特征图-&gt;特征图插值-&gt;1个Model； e）RCNN方法，Proposal直接给到CNN；和 a）全图计算不同，只针对Patch计算。 f）RPN方法，特征图是通过CNN卷积层得到；和 b）类似，不过采用的是同尺度的不同模板，容易导致尺度不一致问题。 g）上套路，提出我们自己的方法，多尺度特征图；每个尺度特征图对应一个 输出模板，每个尺度cover一个目标尺寸范围。 NMS和soft-nms算法Repulsion loss：遮挡下的行人检测 加入overlapping 与不同的 loss融合以上两个到faster rcnn中 【16~20/07/2018】旅行 【23/07/2018】fix boxes location by regrident使用regr对anchor所确定的框进行修正 12345678910111213141516171819""" fix boxes with grident@param X: current cordinates of box@param T: coresspoding grident@return: Fixed cordinates of box"""def apply_regr_np(X, T): try: x = X[0, :, :] y = X[1, :, :] w = X[2, :, :] h = X[3, :, :] tx = T[0, :, :] ty = T[1, :, :] tw = T[2, :, :] th = T[3, :, :] $t_{x}=\frac{(x-x_{a})}{w_{a}}$ , $t_{y}=\frac{(y-y_{a})}{h_{a}}$ , $t_{w}=log(\frac{w}{w_{a}})$ , $t_{h}=log(\frac{h}{h_{a}})$ (1)$t_{x}^{\ast}=\frac{(x^{\ast}-x_{a})}{w_{a}}$ , $t_{y}^{\ast}=\frac{(y^{\ast}-y_{a})}{h_{a}}$ , $t_{w}^{\ast}=log(\frac{w^{\ast}}{w_{a}})$ , $t_{h}^{\ast}=log(\frac{h^{\ast}}{h_{a}})$ (2) 回归目标就是让{t}尽可能地接近${t^{\ast}}$，所以回归真正预测输出的是${t}$，而训练样本的标定真值为${t^{\ast}}$。得到预测输出${t}$后，通过上式(1)即可反推获取预测框的真实坐标。 过程：123456789101112131415161718192021222324 # centre cordinate cx = x + w/2. cy = y + h/2. # fixed centre cordinate cx1 = tx * w + cx cy1 = ty * h + cy # fixed wdith and height w1 = np.exp(tw.astype(np.float64)) * w h1 = np.exp(th.astype(np.float64)) * h # fixed left top corner's cordinate x1 = cx1 - w1/2. y1 = cy1 - h1/2. # apporximate x1 = np.round(x1) y1 = np.round(y1) w1 = np.round(w1) h1 = np.round(h1) return np.stack([x1, y1, w1, h1])except Exception as e: print(e) return X NMS no max suppression该函数的作用是从所给定的所有预选框中选择指定个数最合理的边框。 定义：123456789101112""" NMS , delete overlapping box@param boxes: (n,4) box and coresspoding cordinates@param probs: (n,) box adn coresspding possibility@param overlap_thresh: treshold of delet box overlapping@param max_boxes: maximum keeping number of boxes@return: boxes: boxes cordinates(x1,y1,x2,y2)@return: probs: coresspoding possibility"""def non_max_suppression_fast(boxes, probs, overlap_thresh=0.9, max_boxes=300): 12345678910111213141516if len(boxes) == 0: return []# grab the coordinates of the bounding boxesx1 = boxes[:, 0]y1 = boxes[:, 1]x2 = boxes[:, 2]y2 = boxes[:, 3]np.testing.assert_array_less(x1, x2)np.testing.assert_array_less(y1, y2)# if the bounding boxes integers, convert them to floats --# this is important since we'll be doing a bunch of divisionsif boxes.dtype.kind == "i": boxes = boxes.astype("float") 对输入的数据进行确认不能为空左上角的坐标小于右下角数据类型的转换12345678# initialize the list of picked indexes pick = []# calculate the areasarea = (x2 - x1) * (y2 - y1)# sort the bounding boxes idxs = np.argsort(probs) pick（拾取）用来存放边框序号计算框的面积probs按照概率从小到大排序123456while len(idxs) &gt; 0:# grab the last index in the indexes list and add the# index value to the list of picked indexeslast = len(idxs) - 1i = idxs[last]pick.append(i) 接下来就是按照概率从大到小取出框，且框的重合度不可以高于overlap_thresh。代码的思路是这样的： 每一次取概率最大的框（即idxs最后一个）删除掉剩下的框中重和度高于overlap_thresh的框直到取满max_boxes为止1234567891011# find the intersectionxx1_int = np.maximum(x1[i], x1[idxs[:last]])yy1_int = np.maximum(y1[i], y1[idxs[:last]])xx2_int = np.minimum(x2[i], x2[idxs[:last]])yy2_int = np.minimum(y2[i], y2[idxs[:last]])ww_int = np.maximum(0, xx2_int - xx1_int)hh_int = np.maximum(0, yy2_int - yy1_int)area_int = ww_int * hh_int 取出idxs队列中最大概率框的序号，将其添加到pick中12345678# find the unionarea_union = area[i] + area[idxs[:last]] - area_int# compute the ratio of overlapoverlap = area_int/(area_union + 1e-6)# delete all indexes from the index list that haveidxs = np.delete(idxs, np.concatenate(([last],np.where(overlap &gt; overlap_thresh)[0]))) 计算取出来的框与剩下来的框区域的交集12if len(pick) &gt;= max_boxes: break 计算重叠率，然后删除掉重叠率较高的位置[np.concatest]，是因为最后一个位置你已经用过了，就得将其从队列中删掉当取足max_boxes框，停止循环123boxes = boxes[pick].astype("int")probs = probs[pick]return boxes, probs 返回pick内存取的边框和对应的概率 【24/07/2018】rpn to porposal fixed该函数的作用是将rpn网络的预测结果转化到一个个预选框函数流程：遍历anchor_size，在遍历anchor_ratio 得到框的长宽在原图上的映射 得到相应尺寸的框对应的回归梯度，将深度都放到第一个维度注1：regr_layer[0, :, :, 4 * curr_layer:4 * curr_layer + 4]当某一个维度的取值为一个值时，那么新的变量就会减小一个维度注2：curr_layer代表的是特定长度和比例的框所代表的编号 得到anchor对应的（x,y,w,h） 使用regr对anchor所确定的框进行修正 对修正后的边框一些不合理的地方进行矫正。如，边框回归后的左上角和右下角的点不能超过图片外，框的宽高不可以小于0注：得到框的形式是（x1,y1,x2,y2） 得到all_boxes形状是（n,4），和每一个框对应的概率all_probs形状是（n,） 删除掉一些不合理的点，即右下角的点值要小于左上角的点值注：np.where() 返回位置信息，这也是删除不符合要求点的一种方法np.delete(all_boxes, idxs, 0)最后一个参数是在哪一个维度删除 最后是根据要求选取指定个数的合理预选框。这一步是重要的，因为每一个点可以有9个预选框，而又拥有很多点，一张图片可能会有几万个预选框。而经过这一步预选迅速下降到几百个。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100""" rpn to porposal@param rpn_layer: porposal's coresspoding possibiliy, whether item exciseted@param regr_layer: porposal's coresspoding regrident@param C: Configuration@param dim_ordering: Dimensional organization@param use_regr=True: wether use regurident to fix proposal@param max_boxes=300: max boxes after apply this function@param overlap_thresh=0.9: threshold of overlapping@param C: Configuration@return: max_boxes proposal with format (x1,y1,x2,y2)"""def rpn_to_roi(rpn_layer, regr_layer, C, dim_ordering, use_regr=True, max_boxes=300,overlap_thresh=0.9): # std_scaling default 4 regr_layer = regr_layer / C.std_scaling anchor_sizes = C.anchor_box_scales anchor_ratios = C.anchor_box_ratios assert rpn_layer.shape[0] == 1 # obtain img's width and height's matrix (rows, cols) = rpn_layer.shape[1:3] curr_layer = 0 A = np.zeros((4, rpn_layer.shape[1], rpn_layer.shape[2], rpn_layer.shape[3])) # anchor size is [128, 256, 512] for anchor_size in anchor_sizes: # anchor ratio is [1,2,1] for anchor_ratio in anchor_ratios: # rpn_stride = 16 # obatin anchor's weidth and height on feature map anchor_x = (anchor_size * anchor_ratio[0])/C.rpn_stride anchor_y = (anchor_size * anchor_ratio[1])/C.rpn_stride # obtain current regrident # when one dimentional obtain a value, the new varirant will decrease one dimenttion regr = regr_layer[0, :, :, 4 * curr_layer:4 * curr_layer + 4] # put depth to first bacause tensorflow as backend regr = np.transpose(regr, (2, 0, 1)) # The rows of the output array X are copies of the vector x; columns of the output array Y are copies of the vector y # each cordinartes of matrix cls and rows X, Y = np.meshgrid(np.arange(cols),np. arange(rows)) # obtain anchors's (x,y,w,h) A[0, :, :, curr_layer] = X - anchor_x/2 A[1, :, :, curr_layer] = Y - anchor_y/2 A[2, :, :, curr_layer] = anchor_x A[3, :, :, curr_layer] = anchor_y # fix boxes with grident if use_regr: # fixed corinates of box A[:, :, :, curr_layer] = apply_regr_np(A[:, :, :, curr_layer], regr) # fix unreasonable cordinates # np.maximum(1,[]) will set the value less than 1 in [] to 1 # box's width and height can't less than 0 A[2, :, :, curr_layer] = np.maximum(1, A[2, :, :, curr_layer]) A[3, :, :, curr_layer] = np.maximum(1, A[3, :, :, curr_layer]) # fixed right bottom cordinates A[2, :, :, curr_layer] += A[0, :, :, curr_layer] A[3, :, :, curr_layer] += A[1, :, :, curr_layer] # left top corner cordinates can't out image A[0, :, :, curr_layer] = np.maximum(0, A[0, :, :, curr_layer]) A[1, :, :, curr_layer] = np.maximum(0, A[1, :, :, curr_layer]) # right bottom corner cordinates can't out img A[2, :, :, curr_layer] = np.minimum(cols-1, A[2, :, :, curr_layer]) A[3, :, :, curr_layer] = np.minimum(rows-1, A[3, :, :, curr_layer]) # next layer curr_layer += 1 # obtain (n,4) object and coresspoding cordinate all_boxes = np.reshape(A.transpose((0, 3, 1,2)), (4, -1)).transpose((1, 0)) # obtain(n,) object and creoespdoing possibility all_probs = rpn_layer.transpose((0, 3, 1, 2)).reshape((-1)) # cordinates of left top and right bottom of box x1 = all_boxes[:, 0] y1 = all_boxes[:, 1] x2 = all_boxes[:, 2] y2 = all_boxes[:, 3] # find where right cordinate bigger than left cordinate idxs = np.where((x1 - x2 &gt;= 0) | (y1 - y2 &gt;= 0)) # delete thoese point at 0 dimentional -&gt; all boxes all_boxes = np.delete(all_boxes, idxs, 0) all_probs = np.delete(all_probs, idxs, 0) # apply NMS to reduce overlapping boxes result = non_max_suppression_fast(all_boxes, all_probs, overlap_thresh=overlap_thresh, max_boxes=max_boxes)[0] return result 【25/07/2018】generate classifier’s trainning data该函数的作用是生成classifier网络训练的数据,需要注意的是它对提供的预选框还会做一次选择就是将容易判断的背景删除 代码流程：得到图片的基本信息，并将图片的最短边规整到相应的长度。并将bboxes的长度做相应的变化 遍历所有的预选框R, 将每一个预选框与所有的bboxes求交并比，记录最大交并比。用来确定该预选框的类别。 对最佳的交并比作不同的判断:当最佳交并比小于最小的阈值时，放弃概框。因为，交并比太低就说明是很好判断的背景没必要训练。当大于最小阈值时，则保留相关的边框信息当在最小和最大之间，就认为是背景。有必要进行训练。大于最大阈值时认为是物体，计算其边框回归梯度 得到该类别对应的数字将该数字对应的地方置为1【one-hot】将该类别加入到y_class_numcoords是用来存储边框回归梯度的，labels来决定是否要加入计算loss中12345678class_num = 2class_label = 10 * [0]print(class_label)class_label[class_num] = 1print(class_label)输出：[0, 0, 0, 0, 0, 0, 0, 0, 0, 0][0, 0, 1, 0, 0, 0, 0, 0, 0, 0] 如果不是背景的话，计算相应的回归梯度 返回数据 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136""" generate classifier training data@param R: porposal -&gt; boxes@param img_data: image data@param C: configuration@param class_mapping: classes and coresspoding numbers@return: np.expand_dims(X, axis=0): boxes after filter@return: np.expand_dims(Y1, axis=0): boxes coresspoding class@return: np.expand_dims(Y2, axis=0): boxes coresspoding regurident@return IoUs: IOU"""def calc_iou(R, img_data, C, class_mapping): # obtain boxxes information from img data bboxes = img_data['bboxes'] # obtain width and height of img (width, height) = (img_data['width'], img_data['height']) # get image dimensions for resizing # Fix image's shortest edge to config setting: eg: 600 (resized_width, resized_height) = get_new_img_size(width, height, C.im_size) # record parameters, bboxes cordinates on feature map gta = np.zeros((len(bboxes), 4)) # change bboxes's width and height because the img was rezised for bbox_num, bbox in enumerate(bboxes): # get the GT box coordinates, and resize to account for image resizing # /C.rpn_stride mapping to feature map gta[bbox_num, 0] = int(round(bbox['x1'] * (resized_width / float(width))/C.rpn_stride)) gta[bbox_num, 1] = int(round(bbox['x2'] * (resized_width / float(width))/C.rpn_stride)) gta[bbox_num, 2] = int(round(bbox['y1'] * (resized_height / float(height))/C.rpn_stride)) gta[bbox_num, 3] = int(round(bbox['y2'] * (resized_height / float(height))/C.rpn_stride)) x_roi = [] y_class_num = [] y_class_regr_coords = [] y_class_regr_label = [] IoUs = [] # for debugging only # for all given proposals -&gt; boxes for ix in range(R.shape[0]): # current boxes's cordinates (x1, y1, x2, y2) = R[ix, :] x1 = int(round(x1)) y1 = int(round(y1)) x2 = int(round(x2)) y2 = int(round(y2)) best_iou = 0.0 best_bbox = -1 # using current proposal to compare with given xml's boxes for bbox_num in range(len(bboxes)): # calculate current iou curr_iou = iou([gta[bbox_num, 0], gta[bbox_num, 2], gta[bbox_num, 1], gta[bbox_num, 3]], [x1, y1, x2, y2]) # update parameters if curr_iou &gt; best_iou: best_iou = curr_iou best_bbox = bbox_num # if iou to small, we don't put it in trainning because it should be backgroud if best_iou &lt; C.classifier_min_overlap: continue else: # saveing left top cordinates, width and height w = x2 - x1 h = y2 - y1 x_roi.append([x1, y1, w, h]) # saving this bbox's iou IoUs.append(best_iou) # hard to classfier -&gt; set it to backgroud if C.classifier_min_overlap &lt;= best_iou &lt; C.classifier_max_overlap: # hard negative example cls_name = 'bg' # valid proposal elif C.classifier_max_overlap &lt;= best_iou: # coresspoding class name cls_name = bboxes[best_bbox]['class'] # calculate rpn graident with true cordinates given by xml file cxg = (gta[best_bbox, 0] + gta[best_bbox, 1]) / 2.0 cyg = (gta[best_bbox, 2] + gta[best_bbox, 3]) / 2.0 cx = x1 + w / 2.0 cy = y1 + h / 2.0 tx = (cxg - cx) / float(w) ty = (cyg - cy) / float(h) tw = np.log((gta[best_bbox, 1] - gta[best_bbox, 0]) / float(w)) th = np.log((gta[best_bbox, 3] - gta[best_bbox, 2]) / float(h)) else: print('roi = &#123;&#125;'.format(best_iou)) raise RuntimeError # class name's mapping number class_num = class_mapping[cls_name] # list of calss label class_label = len(class_mapping) * [0] # set class_num's coresspoding location to 1 class_label[class_num] = 1 # privous is one-hot vector # saving the one-hot vector y_class_num.append(copy.deepcopy(class_label)) # coords used to saving calculated graident coords = [0] * 4 * (len(class_mapping) - 1) # labels used to decide whether adding to loss calculation labels = [0] * 4 * (len(class_mapping) - 1) if cls_name != 'bg': label_pos = 4 * class_num sx, sy, sw, sh = C.classifier_regr_std coords[label_pos:4+label_pos] = [sx*tx, sy*ty, sw*tw, sh*th] labels[label_pos:4+label_pos] = [1, 1, 1, 1] y_class_regr_coords.append(copy.deepcopy(coords)) y_class_regr_label.append(copy.deepcopy(labels)) else: y_class_regr_coords.append(copy.deepcopy(coords)) y_class_regr_label.append(copy.deepcopy(labels)) # no bboxes if len(x_roi) == 0: return None, None, None, None # matrix with [x1, y1, w, h] X = np.array(x_roi) # boxxes coresspoding class number Y1 = np.array(y_class_num) # matrix of whether adding to calculation and coresspoding regrident Y2 = np.concatenate([np.array(y_class_regr_label),np.array(y_class_regr_coords)],axis=1) # adding batch size dimention return np.expand_dims(X, axis=0), np.expand_dims(Y1, axis=0), np.expand_dims(Y2, axis=0), IoUs 【26~27/07/2018】model parameters12345678910111213141516171819202122232425# rpn optimizeroptimizer = Adam(lr=1e-5)# classifier optimizeroptimizer_classifier = Adam(lr=1e-5)# defined loss apply, metrics used to print accurymodel_rpn.compile(optimizer=optimizer, loss=[losses.rpn_loss_cls(num_anchors), losses.rpn_loss_regr(num_anchors)])model_classifier.compile(optimizer=optimizer_classifier, loss=[losses.class_loss_cls, losses.class_loss_regr(len(classes_count)-1)], metrics=&#123;'dense_class_&#123;&#125;'.format(len(classes_count)): 'accuracy'&#125;)# for saving weightmodel_all.compile(optimizer='sgd', loss='mae')# traing time of each epochsepoch_length = 1000# totoal epochsnum_epochs = 2000#iter_num = 0# losses saving matrixlosses = np.zeros((epoch_length, 5))rpn_accuracy_rpn_monitor = []rpn_accuracy_for_epoch = []start_time = time.time()# current total lossbest_loss = np.Inf# sorted classing mappingclass_mapping_inv = &#123;v: k for k, v in class_mapping.items()&#125; Training process函数流程：训练rpn网络并且进行预测：训练RPN网络,X是图片、Y是对应类别和回归梯度【注：并不是所有的点都参与训练，只有符合条件的点才参与训练】 根据rpn网络的预测结果得到classifier网络的训练数据:将预测结果转化为预选框计算宽属于哪一类，回归梯度是多少如果没有有效的预选框则结束本次循环得到正负样本在的位置【Y1[0, :, -1]：0指定batch的位置，：指所有框，-1指最后一个维度即背景类】neg_samples = neg_samples[0]：这样做的原因是将其变为一维的数组下面这一步是选择C.num_rois个数的框，送入classifier网络进行训练。思路是：当C.num_rois大于1的时候正负样本尽量取到各一半，小于1的时候正负样本随机取一个。需要注意的是我们这是拿到的是正负样本在的位置而不是正负样本本身，这也是随机抽取的一般方法 训练classifier网络:打印Loss和accury如果网络有两个不同的输出，那么第一个是和损失接下来是分损失【loss_class[3]：代表是准确率在定义网络的时候定义了】12classifer网络的loss输出：[1.4640709, 1.0986123, 0.36545864, 0.15625] 还有就是这些loss都是list数据类型，所以要把它倒腾到numpy数据中当结束一轮的epoch时，只有当这轮epoch的loss小于最优的时候才会存储这轮的训练数据。并结束这轮epoch进入下一轮epoch. 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155# Training Processprint('Starting training')for epoch_num in range(num_epochs): #progbar is used to print % of processing progbar = generic_utils.Progbar(epoch_length) # print current process print('Epoch &#123;&#125;/&#123;&#125;'.format(epoch_num + 1, num_epochs)) while True: try: # verbose True to print RPN situation, if can't generate boxes on positivate object, it will print error if len(rpn_accuracy_rpn_monitor) == epoch_length and C.verbose: # postivate boxes / all boxes mean_overlapping_bboxes = float(sum(rpn_accuracy_rpn_monitor))/len(rpn_accuracy_rpn_monitor) rpn_accuracy_rpn_monitor = [] print('Average number of overlapping bounding boxes from RPN = &#123;&#125; for &#123;&#125; previous iterations'.format(mean_overlapping_bboxes, epoch_length)) if mean_overlapping_bboxes == 0: print('RPN is not producing bounding boxes that overlap the ground truth boxes. Check RPN settings or keep training.') # obtain img, rpn information and img xml format X, Y, img_data = next(data_gen_train) # train RPN net, X is img, Y is correspoding class type and graident loss_rpn = model_rpn.train_on_batch(X, Y) # predict new Y from privious rpn model P_rpn = model_rpn.predict_on_batch(X) # transform predicted rpn to cordinates of boxes R = rpn_to_boxes.rpn_to_roi(P_rpn[0], P_rpn[1], C, K.image_dim_ordering(), use_regr=True, overlap_thresh=0.7, max_boxes=300) # note: calc_iou converts from (x1,y1,x2,y2) to (x,y,w,h) format # X2: [x,y,w,h] # Y1: coresspoding class number -&gt; one hot vector # Y2: boxes coresspoding regrident X2, Y1, Y2, IouS = rpn_to_classifier.calc_iou(R, img_data, C, class_mapping) # no box, stop this epoch if X2 is None: rpn_accuracy_rpn_monitor.append(0) rpn_accuracy_for_epoch.append(0) continue # if last position of one-hot is 1 -&gt; is background neg_samples = np.where(Y1[0, :, -1] == 1) # else is postivate sample pos_samples = np.where(Y1[0, :, -1] == 0) # obtain backgourd samples's coresspoding rows if len(neg_samples) &gt; 0: neg_samples = neg_samples[0] else: neg_samples = [] # obtain posivate samples's coresspoding rows if len(pos_samples) &gt; 0: pos_samples = pos_samples[0] else: pos_samples = [] # saving posivate samples's number rpn_accuracy_rpn_monitor.append(len(pos_samples)) rpn_accuracy_for_epoch.append((len(pos_samples))) # default 4 here if C.num_rois &gt; 1: # wehn postivate samples less than 2 if len(pos_samples) &lt; C.num_rois//2: # chosse all samples selected_pos_samples = pos_samples.tolist() else: # random choose 2 samples selected_pos_samples = np.random.choice(pos_samples, C.num_rois//2, replace=False).tolist() try: # random choose num_rois - positave samples naegivate samples selected_neg_samples = np.random.choice(neg_samples, C.num_rois - len(selected_pos_samples), replace=False).tolist() except: # if no enought neg samples, copy priouvs neg sample selected_neg_samples = np.random.choice(neg_samples, C.num_rois - len(selected_pos_samples), replace=True).tolist() # samples picked to classifier network sel_samples = selected_pos_samples + selected_neg_samples else: # in the extreme case where num_rois = 1, we pick a random pos or neg sample selected_pos_samples = pos_samples.tolist() selected_neg_samples = neg_samples.tolist() if np.random.randint(0, 2): sel_samples = random.choice(neg_samples) else: sel_samples = random.choice(pos_samples) # train classifier, img data, selceted samples' cordinates, mapping number of selected samples, coresspoding regreident loss_class = model_classifier.train_on_batch([X, X2[:, sel_samples, :]], [Y1[:, sel_samples, :], Y2[:, sel_samples, :]]) # in Keras, if loss part bigger than 1, it will return sum of part losses, each loss and accury # put each losses and accury into losses losses[iter_num, 0] = loss_rpn[1] losses[iter_num, 1] = loss_rpn[2] losses[iter_num, 2] = loss_class[1] losses[iter_num, 3] = loss_class[2] losses[iter_num, 4] = loss_class[3] # next iter iter_num += 1 # display and update current mean value of losses progbar.update(iter_num, [('rpn_cls', np.mean(losses[:iter_num, 0])), ('rpn_regr', np.mean(losses[:iter_num, 1])), ('detector_cls', np.mean(losses[:iter_num, 2])), ('detector_regr', np.mean(losses[:iter_num, 3]))]) # reach epoch_length if iter_num == epoch_length: loss_rpn_cls = np.mean(losses[:, 0]) loss_rpn_regr = np.mean(losses[:, 1]) loss_class_cls = np.mean(losses[:, 2]) loss_class_regr = np.mean(losses[:, 3]) class_acc = np.mean(losses[:, 4]) # negativate samples / all samples mean_overlapping_bboxes = float(sum(rpn_accuracy_for_epoch)) / len(rpn_accuracy_for_epoch) # reset rpn_accuracy_for_epoch = [] # print trainning loss and accrury if C.verbose: print('Mean number of bounding boxes from RPN overlapping ground truth boxes: &#123;&#125;'.format(mean_overlapping_bboxes)) print('Classifier accuracy for bounding boxes from RPN: &#123;&#125;'.format(class_acc)) print('Loss RPN classifier: &#123;&#125;'.format(loss_rpn_cls)) print('Loss RPN regression: &#123;&#125;'.format(loss_rpn_regr)) print('Loss Detector classifier: &#123;&#125;'.format(loss_class_cls)) print('Loss Detector regression: &#123;&#125;'.format(loss_class_regr)) # trainng time of one epoch print('Elapsed time: &#123;&#125;'.format(time.time() - start_time)) # total loss curr_loss = loss_rpn_cls + loss_rpn_regr + loss_class_cls + loss_class_regr # reset iter_num = 0 # reset time start_time = time.time() # if obtain smaller total loss, save weight of current model if curr_loss &lt; best_loss: if C.verbose: print('Total loss decreased from &#123;&#125; to &#123;&#125;, saving weights'.format(best_loss,curr_loss)) best_loss = curr_loss model_all.save_weights(C.model_path) break except Exception as e: print('Exception: &#123;&#125;'.format(e)) continueprint('Training complete, exiting.') jupyter notebook 【30/07/2018】Running at GPU enviormentMeet error in GPU version tensorflowNo enough memory. Try to Running at Irius: Setting 3 differnet configration:at Prjoect1 file:set epoch_length to number of training img12epoch_length = 11540num_epochs = 100 Apply img enhance function123C.use_horizontal_flips = TrueC.use_vertical_flips = TrueC.rot_90 = True at Prjoect file:set epoch_length to 1000, increase epoch12epoch_length = 1000num_epochs = 2000 Apply img enhance and class balance function1234C.use_horizontal_flips = TrueC.use_vertical_flips = TrueC.rot_90 = TrueC.balanced_classes = True at Prjoect3 file:set epoch_length to 1000, increase epoch12epoch_length = 1000num_epochs = 2000 Apply img enhance123C.use_horizontal_flips = TrueC.use_vertical_flips = TrueC.rot_90 = True check irdius work1myqueue 12ssh pink59nvidia-smi 【31/07/2018】obtain trained model and log file因为 Iriuds 的GPU使用时长限制最高为24小时，因此，需要在下一次开始前载入上一次训练的模型。每次训练的粗略结果更新在LogBook最前面. plot rpn and classfier loss获取日志中每个小epoch的rpn_cls, rpn_regr, detc_cls, detc_regr遍历日志，用正则匹配出相应的数值添加到List中：1234567891011121314151617181920212223242526272829303132333435363738def obtain_each_batch(filename): n = 0 rpn_cls = [] rpn_regr = [] detector_cls = [] detector_regr = [] f = open(filename,'r',buffering=-1) lines = f.readlines() for line in lines: n = n + 1 match = re.match(r'.* - rpn_cls: (.*) - rpn_regr: .*', line, re.M|re.I) if match is None: continue else: rpn_cls.append(float(match.group(1))) match = re.match(r'.* - rpn_regr: (.*) - detector_cls: .*', line, re.M|re.I) if match is None: continue else: rpn_regr.append(float(match.group(1))) match = re.match(r'.* - detector_cls: (.*) - detector_regr: .*', line, re.M|re.I) if match is None: continue else: detector_cls.append(float(match.group(1))) match = re.match(r'.* - detector_regr: (.*)\n', line, re.M|re.I) if match is None: continue else: det_regr = match.group(1)[0:6] detector_regr.append(float(det_regr)) f.close() print(n) return rpn_cls, rpn_regr, detector_cls, detector_regr 每个epoch都会计算accury, loss of rpn cls, loss of rpn regr, loss of detc cls, loss of detc regr遍历日志找到相应的数值添加到list中：1234567891011121314151617181920212223242526272829303132333435def obtain_batch(filename): n = 0 accuracy = [] loss_rpn_cls = [] loss_rpn_regr = [] loss_detc_cls = [] loss_detc_regr = [] f = open(filename,'r',buffering=-1) lines = f.readlines() for line in lines: n = n + 1 if 'Classifier accuracy for bounding boxes from RPN' in line: result = re.findall(r"\d+\.?\d*",line) accuracy.append(float(result[0])) if 'Loss RPN classifier' in line: result = re.findall(r"\d+\.?\d*",line) loss_rpn_cls.append(float(result[0])) if 'Loss RPN regression' in line: result = re.findall(r"\d+\.?\d*",line) loss_rpn_regr.append(float(result[0])) if 'Loss Detector classifier' in line: result = re.findall(r"\d+\.?\d*",line) loss_detc_cls.append(float(result[0])) if 'Loss Detector regression' in line: result = re.findall(r"\d+\.?\d*",line) loss_detc_regr.append(float(result[0])) f.close() print(n) return accuracy, loss_rpn_cls, loss_rpn_regr, loss_detc_cls, loss_detc_regr plot epoch loss and accury1234567891011121314151617filename = r'F:\desktop\新建文件夹\1000-no_balance\train1.out'aa,bb,cc,dd,ee = obtain_batch(filename)x_cor = np.arange(0,len(aa),1)plt.plot(x_cor,aa, c='b', label = "Accuracy")plt.plot(x_cor,bb, c='c', label = "Loss RPN classifier")plt.plot(x_cor,cc, c='g', label = "Loss RPN regression")plt.plot(x_cor,dd, c='k', label = "Loss Detector classifier")plt.plot(x_cor,ee, c='m', label = "Loss Detector regression")plt.ylabel("Value of Accuracy and Loss") plt.xlabel("Number of Epoch")plt.title('Loss and Accuracy for Totoal Epochs') plt.legend()plt.ylim(0,2)#plt.xlim(0,11540)plt.savefig("pic1.PNG", dpi = 600)plt.show() 12345678910111213141516filename = r'F:\desktop\新建文件夹\1000-no_balance\train1.out'a,b,c,d = obtain_each_batch(filename)x_cor = np.arange(0,len(a),1)plt.plot(x_cor,a, c='b', label = "rpn_cls")plt.plot(x_cor,b, c='c', label = "rpn_regr")plt.plot(x_cor,c, c='g', label = "detector_cls")plt.plot(x_cor,d, c='k', label = "detector_regr")plt.ylabel("Value of Loss") plt.xlabel("Epoch Length")plt.title('Loss for Lenght of Epoch') plt.legend()#plt.ylim(0,2)plt.xlim(80787,92327)plt.savefig("pic2.PNG", dpi = 600)plt.show() Jupyter notebook August【01~02/08/2018】test network首先是搭建网络，用于train部分相同的设置搭建不过在这里图像增强就设置为关闭了 构建rpn输出123shared_layers = nn.nn_base(img_input, trainable=True)num_anchors = len(C.anchor_box_scales)*len(C.anchor_box_ratios)rpn_layers = nn.rpn(shared_layers,num_anchors) 构建classifier输出，参数分别是：特征层输出，预选框，探测框的数目，多少个类，是否可训练1classifier = nn.classifier(feature_map_input, roi_input, C.num_rois,nb_classes=len(class_mapping), trainable=True) 载入训练好的权重：1234567C.model_path = 'gpu_resnet50_weights.h5'try: print('Loading weights from &#123;&#125;'.format(C.model_path)) model_rpn.load_weights(C.model_path, by_name=True) model_classifier.load_weights(C.model_path, by_name=True)except: print('can not load') 读取需要检测的图片：将图片规整到制定的大小 将图片缩放到规定的大小 首先从配置文件夹中得到最小边的大小 得到图片的高度和宽度 根据高度和宽度谁大谁小，确定规整后图片的高宽 将图片缩放到指定的大小，用的是立方插值。返回的缩放后的图片img和相应的缩放的比例。 123456789def format_img_size(img, C): (height,width,_) = img.shape if width &lt;= height: ratio = C.im_size/width else: ratio = C.im_size/height new_width, new_height = image_processing.get_new_img_size(width,height, C.im_size) img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_CUBIC) return img, ratio 对图片每一个通道的像素值做规整 将图片的BGR变成RGB，因为网上训练好的RESNET图片都是以此训练的 将图片数据类型转换为np.float32，并减去每一个通道的均值，理由同上 图片的像素值除一个缩放因子，此处为1 将图片的深度变到第一个位置 给图片增加一个维度 1234567891011def format_img_channels(img, C): """ formats the image channels based on config """ img = img[:, :, (2, 1, 0)] img = img.astype(np.float32) img[:, :, 0] -= C.img_channel_mean[0] img[:, :, 1] -= C.img_channel_mean[1] img[:, :, 2] -= C.img_channel_mean[2] img /= C.img_scaling_factor img = np.transpose(img, (2, 0, 1)) img = np.expand_dims(img, axis=0) return img 如果用的是tensorflow内核，需要将图片的深度变换到最后一位。 进行区域预测Y1:anchor包含物体的概率Y2:每一个anchor对应的回归梯度F:卷积后的特征图，接下来会有用 1[Y1, Y2, F] = model_rpn.predict(X) 获得rpn预测的结果以及对应的回归梯度，这一步就是对图片上隔16个像素的每个anchor进行rpn计算 根据rpn预测的结果，得到预选框:这里会返回300个预选框以及它们对应的坐标(x1,y1,x2,y2)12# transform predicted rpn to cordinates of boxesR = rpn_to_boxes.rpn_to_roi(Y1, Y2, C, K.image_dim_ordering(), use_regr=True, overlap_thresh=0.7) 将预选框的坐标由(x1,y1,x2,y2) 改到 (x,y,w,h)12R[:, 2] -= R[:, 0]R[:, 3] -= R[:, 1] 遍历所有的预选框需要注意的是每一次遍历预选框的个数为C.num_rois每一次遍历32个预选框，那么总共需要300/32, 10批次取出32个预选框，并增加一个维度【注：当不满一个32，其自动只取到最后一个】当预选框被取空的时候，停止循环当最后一次去不足32个预选框时，补第一个框使其达到32个。1234567891011121314151617181920# divided 32 bboxes as one groupfor jk in range(R.shape[0]//C.num_rois + 1): # pick num_rios(32) bboxes one time, only pick to last bboxes in last group ROIs = np.expand_dims(R[C.num_rois*jk:C.num_rois*(jk+1), :], axis=0) #print(ROIs.shape) # no proposals, out iter if ROIs.shape[1] == 0: break # when last time can't obtain num_rios(32) bboxes, adding bboxes with 0 to fill to 32 bboxes if jk == R.shape[0]//C.num_rois: #pad R curr_shape = ROIs.shape target_shape = (curr_shape[0],C.num_rois,curr_shape[2]) ROIs_padded = np.zeros(target_shape).astype(ROIs.dtype) ROIs_padded[:, :curr_shape[1], :] = ROIs ROIs_padded[0, curr_shape[1]:, :] = ROIs[0, 0, :] # 10 group with 320 bboxes ROIs = ROIs_padded 这样就可以送入分类网络了 进行类别预测和边框回归 预测P_cls：该边框属于某一类别的概率P_regr：每一个类别对应的边框回归梯度F:rpn网络得到的卷积后的特征图ROIS:处理得到的区域预选框1[P_cls, P_regr] = model_classifier_only.predict([F, ROIs]) 遍历每一个预选宽如果该预选框的最大概率小于设定的阈值（即预测的肯定程度大于一定的值，我们才认为这次的类别的概率预测是有效的，或者最大的概率出现在背景上，则认为这个预选框是无效的，进行下一次预测。123456for ii in range(P_cls.shape[1]): # if smaller than setting threshold, we think this bbox invalid # and if this bbox's class is background, we don't need to care about it if np.max(P_cls[0, ii, :]) &lt; bbox_threshold or np.argmax(P_cls[0, ii, :]) == (P_cls.shape[2] - 1): continue 不属于上面的两种情况，取最大的概率处为此边框的类别得到其名称。创建两个list，用于存放不同类别对应的边框和概率1234567# obatain max possibility's class name by class mappingcls_name = class_mapping[np.argmax(P_cls[0, ii, :])]# saving bboxes and probsif cls_name not in bboxes: bboxes[cls_name] = [] probs[cls_name] = [] 得到该预选框的信息得到类别对应的编号12345# obtain current cordinates of proposal(x, y, w, h) = ROIs[0, ii, :]# obtain the position with max possibilitycls_num = np.argmax(P_cls[0, ii, :]) 这样符合条件的预选框以及对应的分类类别和概率就可以画在图片上了 根据类别编号得到该类的边框回归梯度对回归梯度进行规整化对预测的边框进行修正向相应的类里面添加信息【乘 C.rpn_stride，边框的预测都是在特征图上进行的要将其映射到规整后的原图上】12345678910111213141516try: # obtain privous position's bbox's regrient (tx, ty, tw, th) = P_regr[0, ii, 4*cls_num:4*(cls_num+1)] # waiting test tx /= C.classifier_regr_std[0] ty /= C.classifier_regr_std[1] tw /= C.classifier_regr_std[2] th /= C.classifier_regr_std[3] # fix box with regreient x, y, w, h = rpn_to_boxes.apply_regr(x, y, w, h, tx, ty, tw, th)except: pass# cordinates of current's box on real imgbboxes[cls_name].append([C.rpn_stride*x, C.rpn_stride*y, C.rpn_stride*(x+w), C.rpn_stride*(y+h)])# coresspoding posbilityprobs[cls_name].append(np.max(P_cls[0, ii, :])) 这样修正过的框可以画在图上： 遍历bboxes里的类，取出某一类的bbox，合并一些重合度较高的选框No Max Supression1234567# for all classes in current boxesfor key in bboxes: # bboxes's cordinates bbox = np.array(bboxes[key]) # apply NMX to merge some overlapping boxes new_boxes, new_probs = rpn_to_boxes.non_max_suppression_fast(bbox, np.array(probs[key]), overlap_thresh=0.5) 最终的图： Jupyter notebook resultSmall img, only 8k Overlapping img Crowed People cow and people car and plane Street img Lots Dogs Overlapping car and people 直观看的话效果还不错，但是一些重叠的物体框会出现反复，或者取不到。而且分类有一点过拟合。 【03/08/2018】evaluationmAPmAP是目标算法中衡量算法的精确度的指标，涉及两个概念：查准率Precision、查全率Recall。对于object detection任务，每一个object都可以计算出其Precision和Recall，多次计算/试验，每个类都 可以得到一条P-R曲线，曲线下的面积就是AP的值，这个mean的意思是对每个类的AP再求平均，得到的就是mAP的值，mAP的大小一定在[0,1]区间。 AP:Precision对Recall积分，可通过改变正负样本阈值求得矩形面积，进而求积分得到，也可以通过sklearn.metrics.average_precision_score函数直接得到。 传入预测值和真实值和resize比例，得到可以传入sklearn.metrics.average_precision_score函数的值，即：真实值和预测概率 首先搭建rpn和分类器网络，按照之前的train部分来就可以了这里注意分类网络的输入换成测试图片的feature map12345678910num_features = 1024input_shape_img = (None, None, 3)input_shape_features = (None, None, num_features)img_input = Input(shape=input_shape_img)roi_input = Input(shape=(C.num_rois, 4))feature_map_input = Input(shape=input_shape_features)classifier = nn.classifier(feature_map_input, roi_input, C.num_rois, nb_classes=len(class_mapping), trainable=True) 然后载入需要测试的模型权重 按照VOC的数据集标注，把测试集分出来：12345678train_imgs = []test_imgs = []for each in all_imgs: if each['imageset'] == 'trainval': train_imgs.append(each) if each['imageset'] == 'test': test_imgs.append(each) 按照之前的预测方法，求出图片的预测框坐标以及对应的分类名字，然后把这些信息放入对应的字典里面，与xml解析的文件一样的格式：1234for jk in range(new_boxes.shape[0]): (x1, y1, x2, y2) = new_boxes[jk, :] det = &#123;'x1': x1, 'x2': x2, 'y1': y1, 'y2': y2, 'class': key, 'prob': new_probs[jk]&#125; all_dets.append(det) 然后读取标注的框的真实数值： 遍历真实信息里面的每一个狂，将bbox_matched这个属性标注为FALSE，之后如果预测框和标注框对应上的话，这个属性就会被设置为True 获取预测框里面的分类对应概率，并且按照概率从大到小得到idx位置： 按照概率大小，对每一个对应的预测框，对比每一个标注的框，如果预测的类与当前标注框的类相同并且没有被匹配过，计算两个框的iou，如果大于0.5的话就表明预测框匹配当前标注框，保存预测概率以及对应的是否匹配： 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051# process each bbox with hightest probfor box_idx in box_idx_sorted_by_prob: # obtain current box's cordinates, class and prob pred_box = pred[box_idx] pred_class = pred_box['class'] pred_x1 = pred_box['x1'] pred_x2 = pred_box['x2'] pred_y1 = pred_box['y1'] pred_y2 = pred_box['y2'] pred_prob = pred_box['prob'] # if not in P list, save current class infomration to it if pred_class not in P: P[pred_class] = [] T[pred_class] = [] # put porb to P P[pred_class].append(pred_prob) # used to check whether find current object found_match = False # compare each real bbox # obtain real box's cordinates, class and prob for gt_box in gt: gt_class = gt_box['class'] # bacause the image is rezied, so calculate the real cordinates gt_x1 = gt_box['x1']/fx gt_x2 = gt_box['x2']/fx gt_y1 = gt_box['y1']/fy gt_y2 = gt_box['y2']/fy # obtain box_matched - all false at beginning gt_seen = gt_box['bbox_matched'] # ture class != predicted class if gt_class != pred_class: continue # already matched if gt_seen: continue # calculate iou of predicted bbox and real bbox iou = rpn_calculation.iou((pred_x1, pred_y1, pred_x2, pred_y2), (gt_x1, gt_y1, gt_x2, gt_y2)) # if iou &gt; 0.5, we will set this prediction correct if iou &gt;= 0.5: found_match = True gt_box['bbox_matched'] = True break else: continue # 1 means this position's bbox correct match with orignal image T[pred_class].append(int(found_match)) 遍历每一个标注框，如果没有被匹配到并且diffcult属性不是true的话，说明这个框漏检了，在之前保存的概率以及对应是否有概率里面加入物体1以及对应概率012345678910# adding missing object compared to orignal imagefor gt_box in gt: if not gt_box['bbox_matched'] and not gt_box['difficult']: if gt_box['class'] not in P: P[gt_box['class']] = [] T[gt_box['class']] = [] # T = 1 means there are object, P = 0 means we did't detected that T[gt_box['class']].append(1) P[gt_box['class']].append(0) 把当前信息存入到总的一个词典里面，就可以使用average_precision_score这个sklearn里面的函数计算ap了。与此同时，保存得到的结果并且显示总的map： jupyter notebook 【06~10/08/2018】adjust将计算ap的函数包装好：jupyter notebook Project1 all: 9 models:ALL_2 NO THRESHOLD OTHER WITH THRESHOLD MOST 0.8 Classes ALL_1 ALL_2 ALL_3 ALL_4 ALL_5 ALL_6 ALL_7 ALL_8 ALL_9 motorbike 0.2305 0.2412 0.2132 0.2220 0.2889 0.2528 0.2204 0.2644 0.2336 person 0.6489 0.6735 0.7107 0.6652 0.7120 0.7041 0.7238 0.7003 0.7201 car 0.1697 0.1563 0.2032 0.2105 0.2308 0.2221 0.2436 0.2053 0.2080 aeroplane 0.7968 0.7062 0.7941 0.6412 0.7871 0.7331 0.7648 0.7659 0.6902 bottle 0.2213 0.2428 0.2261 0.1943 0.2570 0.2437 0.2899 0.1442 0.2265 sheep 0.6162 0.5702 0.6876 0.6295 0.6364 0.5710 0.6536 0.6349 0.6455 tvmonitor 0.1582 0.1601 0.2231 0.1748 0.1551 0.1603 0.1317 0.1584 0.1678 boat 0.3842 0.2621 0.2261 0.1943 0.3499 0.2437 0.2057 0.2748 0.3509 chair 0.2811 0.0563 0.0891 0.0621 0.1353 0.0865 0.0907 0.0854 0.1282 bicycle 0.1464 0.1224 0.1346 0.1781 0.1406 0.1448 0.1810 0.1071 0.1673 cat 0.8901 0.8565 0.9103 0.8417 0.8289 0.8274 0.7572 0.9143 0.8118 pottedplant 0.2075 0.0926 0.1790 0.0532 0.1517 0.1150 0.1080 0.1022 0.0939 horse 0.1185 0.0588 0.0726 0.0489 0.0696 0.0695 0.0637 0.0651 0.0640 sofa 0.2797 0.2309 0.2852 0.2966 0.3855 0.4817 0.3659 0.3132 0.3090 dog 0.5359 0.5077 0.5578 0.4413 0.4832 0.5793 0.5687 0.4910 0.4598 cow 0.7582 0.6229 0.7295 0.5420 0.5379 0.5312 0.5147 0.5706 0.6503 diningtable 0.3979 0.2734 0.3739 0.2963 0.4715 0.4987 0.3895 0.4983 0.4666 bus 0.6203 0.5572 0.6468 0.6032 0.6320 0.6096 0.7169 0.5938 0.5485 bird 0.6164 0.6662 0.5692 0.5751 0.5407 0.4125 0.4925 0.4347 0.5208 train 0.8655 0.6916 0.7141 0.7166 0.7643 0.8107 0.7100 0.7194 0.6263 mAP 0.4472 0.3874 0.4341 0.3859 0.4279 0.4141 0.4096 0.4022 0.4045 Project1 epoch_lenght=1000, epoch:1041 : 7 models:ALL WITH THRESHOLD MOST 0.51 Classes ALL_1 ALL_2 ALL_3 ALL_4 ALL_5 ALL_6 ALL_7 motorbike 0.2433 0.2128 0.2232 0.2262 0.2286 0.2393 0.2279 person 0.6560 0.6537 0.6742 0.6952 0.6852 0.6719 0.6636 car 0.1562 0.1905 0.1479 0.2024 0.2010 0.1379 0.1583 aeroplane 0.7359 0.6837 0.6729 0.6687 0.6957 0.7339 0.6391 bottle 0.1913 0.1937 0.2635 0.1843 0.2570 0.1632 0.1863 sheep 0.5429 0.5579 0.6219 0.5355 0.5881 0.5441 0.5824 tvmonitor 0.1295 0.1601 0.1368 0.1407 0.1147 0.1349 0.1154 boat 0.1913 0.2880 0.2635 0.3433 0.3335 0.3422 0.3069 chair 0.0587 0.0657 0.0342 0.0680 0.0695 0.0752 0.0760 bicycle 0.1013 0.1485 0.1225 0.1871 0.1685 0.1037 0.1490 cat 0.8737 0.8557 0.8007 0.7982 0.8045 0.8067 0.7732 pottedplant 0.0694 0.1059 0.0748 0.0878 0.0893 0.0690 0.0865 horse 0.0556 0.0561 0.0581 0.0770 0.0575 0.0539 0.0522 sofa 0.2177 0.2917 0.1699 0.1940 0.3177 0.1863 0.1857 dog 0.6269 0.4989 0.5015 0.5333 0.4914 0.5572 0.4747 cow 0.5216 0.6229 0.5283 0.6426 0.4358 0.4227 0.4589 diningtable 0.3076 0.3889 0.3283 0.2404 0.4219 0.4153 0.2627 bus 0.5865 0.5222 0.6312 0.5853 0.5042 0.4882 0.5576 bird 0.5339 0.5039 0.5150 0.5152 0.5838 0.3890 0.4680 train 0.4994 0.6541 0.6702 0.6920 0.5959 0.5893 0.6861 mAP 0.3699 0.3765 0.3748 0.3814 0.3786 0.3562 0.3555 在测试集上的结果不是很好，不同class的ap差距较大，可能是由于训练时候不平均或者训练集太小的原因 尝试加入VOC2007的数据进训练集当中，观察结果。解析VOC2007的过程中遇到了OpenCV读取不了图片的BUG。（已修复）VOC2012的数据莫名没有了，因为之前测试过的原因，一直以为是VOC2007的数据解析有问题，大概是Irius的文件上限时间到了自动清除了数据。 20个类当中的AP差距过大，其实数据集是不平衡的，有的类只有大概1000个样本，但是人这个样本就有2W多，而且之前的训练过程中每次图片都是在训练集中随机选的，所以尝试修改了流程，当所有训练集中的数据都被读取训练过以后再打乱训练集，与此同时配合class_balance的功能使用。 实际上使用的时候class balance效果不是很好，后面没有开启。 用了一个较大的学习率尝试训练没有载入imagenet预训练权重的版本。 交叉法 【13/08/2018】soft-NMS传统的非最大抑制算法首先在被检测图片中产生一系列的检测框B以及对应的分数S。当选中最大分数的检测框M，它被从集合B中移出并放入最终检测结果集合D。于此同时，集合B中任何与检测框M的重叠部分大于重叠阈值Nt的检测框也将随之移除。非最大抑制算法中的最大问题就是它将相邻检测框的分数均强制归零。在这种情况下，如果一个真实物体在重叠区域出现，则将导致对该物体的检测失败并降低了算法的平均检测率（average precision, AP）。 换一种思路，如果我们只是通过一个基于与M重叠程度相关的函数来降低相邻检测框的分数而非彻底剔除。虽然分数被降低，但相邻的检测框仍在物体检测的序列中。图二中的实例可以说明这个问题。针对NMS存在的这个问题，我们提出了一种新的Soft-NMS算法（图三），它只需改动一行代码即可有效改进传统贪心NMS算法。在该算法中，我们基于重叠部分的大小为相邻检测框设置一个衰减函数而非彻底将其分数置为零。简单来讲，如果一个检测框与M有大部分重叠，它会有很低的分数；而如果检测框与M只有小部分重叠，那么它的原有检测分数不会受太大影响。在标准数据集PASCAL VOC 和 MS-COCO等标准数据集上，Soft-NMS对现有物体检测算法在多个重叠物体检测的平均准确率有显著的提升。同时，Soft-NMS不需要额外的训练且易于实现，因此，它很容易被集成到当前的物体检测流程中。 伪代码： 公式：NMS$$ s_{i}=\left{\begin{aligned}s_{i}, \ \ \ \ iou(M,b_{i}) &lt; N_{t} \0, \ \ \ \ iou(M,b_{i}) \geq N_{t}\end{aligned}\right.$$ SOFT NMS$$ s_{i}=\left{\begin{aligned}s_{i}, \ \ \ \ iou(M,b_{i}) &lt; N_{t} \1-iou(M,b_{i}), \ \ \ \ iou(M,b_{i}) \geq N_{t}\end{aligned}\right.$$ 当相邻检测框与M的重叠度超过重叠阈值Nt后，检测框的检测分数呈线性衰减。在这种情况下，与M相邻很近的检测框衰减程度很大，而远离M的检测框并不受影响。 但是，上述分数重置函数并不是一个连续函数，在重叠程度超过重叠阈值Nt时，该分数重置函数产生突变，从而可能导致检测结果序列产生大的变动，因此我们更希望找到一个连续的分数重置函数。它对没有重叠的检测框的原有检测分数不产生衰减，同时对高度重叠的检测框产生大的衰减。综合考虑这些因素，我们进一步对soft-NMS中的分数重置函数进行了改进： Gaussian penalty: 根据这个伪代码以及公式，实现代码：123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118""" NMS , delete overlapping box@param boxes: (n,4) box and coresspoding cordinates@param probs: (n,) box adn coresspding possibility@param overlap_thresh: treshold of delet box overlapping@param max_boxes: maximum keeping number of boxes@param method: 1 for linear soft NMS, 2 for gaussian soft NMS@param sigma: parameter of gaussian soft NMSprob_thresh: threshold of probs after soft NMS@return: boxes: boxes cordinates(x1,y1,x2,y2)@return: probs: coresspoding possibility"""def soft_nms(boxes, probs, overlap_thresh=0.9, max_boxes=300, method = 1, sigma=0.5, prob_thresh=0.49): # number of input boxes N = boxes.shape[0] # if the bounding boxes integers, convert them to floats -- # this is important since we'll be doing a bunch of divisions if boxes.dtype.kind == "i": boxes = boxes.astype("float") # iterate all boxes for i in range(N): # obtain current boxes' cordinates and probs maxscore = probs[i] maxpos = i tx1 = boxes[i,0] ty1 = boxes[i,1] tx2 = boxes[i,2] ty2 = boxes[i,3] ts = probs[i] pos = i + 1 # get max box while pos &lt; N: if maxscore &lt; probs[pos]: maxscore = probs[pos] maxpos = pos pos = pos + 1 # add max box as a detection boxes[i,0] = boxes[maxpos,0] boxes[i,1] = boxes[maxpos,1] boxes[i,2] = boxes[maxpos,2] boxes[i,3] = boxes[maxpos,3] probs[i] = probs[maxpos] # swap ith box with position of max box boxes[maxpos,0] = tx1 boxes[maxpos,1] = ty1 boxes[maxpos,2] = tx2 boxes[maxpos,3] = ty2 probs[maxpos] = ts # cordinates of max box tx1 = boxes[i,0] ty1 = boxes[i,1] tx2 = boxes[i,2] ty2 = boxes[i,3] ts = probs[i] pos = i + 1 # NMS iterations, note that N changes if detection boxes fall below threshold while pos &lt; N: x1 = boxes[pos, 0] y1 = boxes[pos, 1] x2 = boxes[pos, 2] y2 = boxes[pos, 3] s = probs[pos] # calculate the areas, +1 for robatness area = (x2 - x1 + 1) * (y2 - y1 + 1) iw = (min(tx2, x2) - max(tx1, x1) + 1) # # confirm left top cordinates less than top right if iw &gt; 0: ih = (min(ty2, y2) - max(ty1, y1) + 1) # confirm left top cordinates less than top right if ih &gt; 0: # find the union ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih) #iou between max box and detection box ov = iw * ih / ua if method == 1: # linear if ov &gt; overlap_thresh: weight = 1 - ov else: weight = 1 elif method == 2: # gaussian weight = np.exp(-(ov * ov)/sigma) else: # original NMS if ov &gt; overlap_thresh: weight = 0 else: weight = 1 # obtain adjusted probs probs[pos] = weight*probs[pos] # if box score falls below threshold, discard the box by swapping with last box # update N if probs[pos] &lt; prob_thresh: boxes[pos,0] = boxes[N-1, 0] boxes[pos,1] = boxes[N-1, 1] boxes[pos,2] = boxes[N-1, 2] boxes[pos,3] = boxes[N-1, 3] probs[pos] = probs[N-1] N = N - 1 pos = pos - 1 pos = pos + 1 # keep is the idx of current keeping objects, front ith objectes keep = [i for i in range(N)] return boxes[keep], probs[keep] 【14/08/2018】OVERLAPPING OBJECT DETECTION【15/08/2018】【16/08/2018】【17/08/2018】September]]></content>
      <categories>
        <category>Msc Project</category>
      </categories>
      <tags>
        <tag>Deep Learning</tag>
        <tag>Object Detection</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[四月は君の嘘]]></title>
    <url>%2FApri.html</url>
    <content type="text"><![CDATA[YouTube Source【四月是你的謊言AMV】你還會記得嗎？四月は君の嘘 Your Lie in April BiliBili Source【四月是你的谎言】你还会记得吗 4月的最后一天了呢与你相遇的四月就要来了「君と出会った四月が来ているから」没有你的四月就要来了「君のいない四月は来ないで」]]></content>
      <categories>
        <category>My Life</category>
      </categories>
      <tags>
        <tag>友人A</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[Using Machine Learning in NBA]]></title>
    <url>%2FMachine%20Learning%20in%20NBA.html</url>
    <content type="text"><![CDATA[Fan-map plotting Obtain the game data in 2000~2017 in NBA Obtain the follwers of 2015~2017 NBA rookies by twittR package Obtain location information and plot the fan-map by Tableau Machine learning applyall-star players in 2017 prediction PCA 主成份分析 in all-star player prediction Keras是一个高层神经网络API，Keras由纯Python编写而成并基Tensorflow后端 All-star players in 2017 prediction Tensorboard is used to compare and choose better model. Best rookies in 2017 prediction PCA 主成份分析 in best rookies prediction Keras是一个高层神经网络API，Keras由纯Python编写而成并基Tensorflow后端 Best rookies in 2017 prediction First and Second rookies in 2017 prediction Muti-classes classification application Tensorboard is used to compare and choose better model. Potential all-star players in 2015~16 prediction PCA 主成份分析 in potential rookies prediction Keras是一个高层神经网络API，Keras由纯Python编写而成并基Tensorflow后端 Potential rookies in 2015~16 prediction Tensorboard is used to compare and choose better model. ResultWebsite: 2017 NBA ALL-STAR AND BEST ROOKIES PREDICTION WITH FAN-MAPexample: Neural network model 2D PCA (linear unseparable) Tensorboard check]]></content>
      <categories>
        <category>Machine Learning</category>
      </categories>
      <tags>
        <tag>Machine Learning</tag>
        <tag>NBA</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[MXnet 配置]]></title>
    <url>%2FMXnet_Config.html</url>
    <content type="text"><![CDATA[MXnet GPU version configuration (Windows 10. GTX960M)Tools Microsoft Visual Studio 2015 CUDA 9.0 cuDNN7 CMake OpenCV3.0 OpenBLAS Anaconda Graphviz Methods Step 1: Install VS2015 Step 2: Install CUDA 9.0 Step 3: Install cuDNN7 解压后把cudnn目录下的bin目录加到PATH环境变量里 Step 4: Install Opencv 下载并解压，然后创建环境变量OpenCV_DIR，把opencv/build目录添加进去,把\opencv\build\x64\vc12\bin和\opencv\build\x86\vc12\bin添加到PATH路径 Step 5: Install openBLAS 需要下载mingw64_dll.zip和OpenBLAS-v0.2.14-Win64-int64.zip两个文件. 创建环境变量 OpenBLAS_HOME，把openBLAS根目录加进去,把DLL所在目录需要添加到环境变量path中. 创建 “C:\Program files (x86)\OpenBLAS\” 复制相关文件进去 Step 6: Install Anaconda 把安装路径添加到PATH里去 Step 7: Install MXnet 创建MXnet 文件夹 然后使用命令行CD至该文件夹 1git clone --recursive https://github.com/dmlc/mxnet 在根目录创建build文件夹 (补充操作 目前未使用： 打开make文件夹的config.mk 文件 修改USE_CUDNN = 0 to USE_CUDNN = 1, 修改USE_BLAS = openBLAS ) Step 8: Install Cmake Configure and Genreate VS工程 mxnet.sln Configure的配置选择如下图 Step 9: 使用VS2015 打开mxnet.sln 切换成release模式 64位 然后启动编译 编译完成后，在mxnet_build\Release目录下生成了libmxnet.dll文件 Step 10: Install graphviz library 添加安装路径到环境变量path Step 11: 使用Anaconda的命令行 新建一个虚拟环境 1conda create --name MXNet python=2.7 MXNET目前不太适配python3, 激活环境 1activate MXNet Step 12: cd 至mxnet文件夹的python文件夹里， 拷贝如图的各个文件到该文件夹 然后使用 1python setup.py install Step 13: 再次拷贝上图的文件到Anaconda的MXNet虚拟环境的 Lib\site-packages\mxnet-版本名.egg\mxnet 中 并且添加此路径到环境变量path中 Step 14: 1conda install nb_conda 激活这个虚拟环境到juptyer notebook里面 打开jupyter book就可以进行测试了]]></content>
      <categories>
        <category>System</category>
      </categories>
      <tags>
        <tag>MXnet</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[IT salary in USA]]></title>
    <url>%2FIT%20salary%20in%20USA.html</url>
    <content type="text"><![CDATA[ResultChart with Anlysis Website: SALARY DISTRIBUTION OF DIFFERENT JOBS IN USA Data processDatasets used to anlysis U.S. Technology Jobs on Dice.com Found in Kaggle US jobs on Monster.com Found in KaggleData Clean Obtain salay and coresspoding states information in US jos on Monster.com clean process Obtain salay and coresspoding cities information in US jos on Monster.com clean process Obtain skills information in U.S Technology Jobs on Dice.com clean process Chart PlottingChart 1: Map chart of average salay of 3 types jobsTableau address Chart 2: Bubble chart of average salay of 3 types jobsTableau address Chart 3 : Cloud words chart of skills used in IT-jobsCloud words tool]]></content>
      <categories>
        <category>Data Visualisation</category>
      </categories>
      <tags>
        <tag>data analysis</tag>
        <tag>Machine Learning</tag>
      </tags>
  </entry>
</search>
