2020-05-19
血泪的教训,Cache与DMA的一致性

最近开了一个四轴飞行器的坑,把一直在吃灰的STM32H7的板子拿出来用了用。正好机架啥的都还没有,就打算从小模块开始,于是拿出了一块SPI显示屏。在玩H7的时候,知道它M7的内核有Cache,果断开起来,白赚的速度不要白不要。。。本应该是这样的,直到我给SPI打开了DMA。

对于SPI的控制,我开辟了一块空间做“显存”,显存数据定时通过SPI传给显示屏,而绘图都在显存内进行绘图。这个SPI屏是240*240的分辨率,SPI波特率30M,算下来RGB565的彩点,刷屏速率只有30Hz左右,还会把CPU资源给占完。这哪行啊,CPU占满了拿什么去飞控←_←。于是我打开了DMA。。。这下惨了,原本正常的显示屏上千疮百孔,总有些区域显示不全。

spi-lcd

而且奇怪的是,只要打开DMA,必出现上述现象;而只要关闭DMA,上述现象就消失了。玩个单片机还能闹鬼了不成?作为一名程序员,发现问题首先问自己又错在哪里了。因为计算机总是忠实的按照自己的编程运行,闹鬼是不可能的。就这样折腾了老半天,在一次调试的过程中,突然发现,如果在启动DMA写数据前,先遍历访问一次“显存”区域,会使现象减弱或者消失。这时,即便对Cache了解并不深入的我,也终于意识到了问题所在,果断关闭Cache,果不其然,BUG消失了。


Cache的DMA之间会有数据一致性的问题。DMA全名Direct Memory Access,即直接内存访问,顾名思义,DMA访问总线前是不会经过Cache的。而Cache又名高速缓存,位于CPU和总线之间。Cache包括读缓冲和写缓冲,读缓冲指CPU想访问的总线地址如果已经在Cache中,则直接从Cache中读;写缓冲指CPU写总线会先写到Cache中,直到满足某些条件,Cache的数据才会写入内存。

所以问题很明显了,程序写了“显存”区域之后,有的数据还在Cache中,未写入内存;而这是DMA开始工作,直接从内存中读取数据,读到的就不是完整的数据。同理,如果DMA从外设将数据送回内存,CPU在读的时候也可能因为Cache读缓冲导致没有读到最新的数据。

解决方法也很简单:

  • 关Cache。但这不是个好办法,既然有Cache,那自然是想用的,把它关了不就亏了。
  • 设置Cache熟悉。Cache可以设置直接读写某块地址空间,这应该是比较好的办法。
  • 手动刷新Cache。即在DMA发送前,手动刷新Cache,保证数据已经回写到内存中。

问题根本原因还是对硬件不够熟悉,想做嵌入式开发,对硬件的熟悉性真的很重要!

Read More
 2020-05-15
对目标检测网络的理解

最近对目标检测网络的结构设计有了一些新的认识,想记录一下。我将分几个阶段来分析目标检测的发展过程,同时会写到我自己对目标检测的理解。

一、图像分类与目标检测

为了讲目标检测算法的发展过程,必须需要先说一说目标检测是一个怎样的任务,与常规的图像分类任务有怎样的不同。

可以总结出一下几点图像分类和目标检测的区别:

  1. 二者的目标不同,这一点是显然的。
    • 图像分类的输出数量也称作输出维度是确定的,即想把图像分为几类就有几个输出,每个维度代表图像属于这个类别的概率。
    • 目标检测的输出数量是不确定的,我们希望目标的输出应该是当前图像有几个对象,它就产生几个输出。同时对于每个对象,不仅要输出它的类别,还要输出它的位置。
  2. 二者所处理的图像上有很大的不同,这一点容易被忽略。
    • 图像分类的目标图像往往只有一个对象且占据较大的图像尺寸
    • 目标检测的目标图像可以有很多个不同的对象,并且每个对象既可以占据较大画幅也可以占据较小的画幅。

在理解了二者的不同后,我们可以逐渐从图像分类过渡到目标检测上。

二、图像金字塔与滑动窗口

基于上面对目标检测任务的介绍,我们可以很容易的想到目标检测的一个解决方案:将需要进行目标检测的图像切片成多个小区域(不同区域之间可以有重叠部分),然后使用一个图像分类器,对每个区域进行分类,如果某个区域被图像分类器识别,则说明这个区域有一个目标。

为什么可以这样操作的呢?回顾上面提到的图像分类器所处理的图像的特点——通常只有一个对象,且该对象占据图像较大的画幅。对图像进行切片操作后所得到的区域,我们期待它只包含一个对象,且占据较大画幅。注意,这里是期待,即我们不能保证上述条件一定满足。但如果被检测的目标的大小差异不大,且是预先已知的,假设我们的待检目标基本上都是60个像素左右,那么我们对图像切片的时候只需要保证切出来的图像都是60个像素左右,就可以满足上述条件。比如,在下面这张图中,我们将图像切片成红,黄,蓝,三个部分,随后使用图像分类器,可以发现猫在蓝色框中。

cat

然而,往往目标检测的对象都不会是固定大小的。大的对象如果进行小切片会导致切片的结果都不能包含完整的目标,从而图像分类全部失败;而小的对象如果进行大切片会导致定位不精确,或者切片图像中包含多个目标。更进一步,一般的图像的分类器都只能接受固定分辨率的图像输入,也只能对固定大小(或者说大小变化不大)的图像进行分类,这更加与目标检测中的不同大小的对象这一条件所不符合。

所以,为了解决对象大小不确定所带来的问题,有一个自然而然的想法就是,我们将输入图像进行缩放,分别缩放2倍,4倍,8倍……这样将图像进行不同倍数的缩放的方法被称为图像金字塔。虽然对象大小不一,但将图像缩放不同倍数,总有一张图像上该对象的大小是正好合适的。在不同缩放大小的图像上都使用固定大小的滑动窗口,就可以解决对象大小不统一的问题。

更进一步,滑动窗口方法的定位往往不够精准,我们只能知道目标在某个窗口中,但窗口的位置只有一些固定的位置,这导致了定位较为粗略。由于应用了图像金字塔,总有某个缩放下的滑动窗口中只包含一个目标。既然只包含一个目标,我们可以将我们的分类器进行一下“升级”,即现在图像分类器不仅仅输出图像的类别,同时输出该图像中对象的位置,由于前面以及保证了滑动窗口中只有一个对象,所以也不用担心窗口中多个目标从而不知道该输出哪个目标的位置。

这样处理之后,目标检测的问题可以说就已经被解决了。但图像金字塔加滑动窗口的策略计算量实在是太大,所以目标检测有后续的发展。

三、改进图像金字塔

上面说到,目标检测就是由图像金字塔+滑动窗口的方式解决的,那么想改良目标检测方法,减少计算量就得从这两点出发。

那么我们首先来改进目标检测两个步骤中的图像金字塔。

我们知道,图像金字塔设计之初是用来解决目标大小不确定这个问题的。由于应用于滑动窗口的分类器(注意这里分类器指还可以同时对单一目标进行定位的分类器)只能接受固定分辨率大小的图像输入,且只能对固定大小(或者大小不怎么变化)的目标进行分类。使用图像金字塔其实是人为构造了不同大小的对象,从而总有一个对象可以被分类器正确分类。这是一个改变输入(图像),去适应系统(分类器)的过程。

四、改进滑动窗口

Read More
 2020-05-15
对目标检测网络的理解

最近对目标检测网络的结构设计有了一些新的认识,想记录一下。我将分几个阶段来分析目标检测的发展过程,同时会写到我自己对目标检测的理解。

一、图像分类与目标检测

为了讲目标检测算法的发展过程,必须需要先说一说目标检测是一个怎样的任务,与常规的图像分类任务有怎样的不同。

可以总结出一下几点图像分类和目标检测的区别:

  1. 二者的目标不同,这一点是显然的。
    • 图像分类的输出数量也称作输出维度是确定的,即想把图像分为几类就有几个输出,每个维度代表图像属于这个类别的概率。
    • 目标检测的输出数量是不确定的,我们希望目标的输出应该是当前图像有几个对象,它就产生几个输出。同时对于每个对象,不仅要输出它的类别,还要输出它的位置。
  2. 二者所处理的图像上有很大的不同,这一点容易被忽略。
    • 图像分类的目标图像往往只有一个对象且占据较大的图像尺寸
    • 目标检测的目标图像可以有很多个不同的对象,并且每个对象既可以占据较大画幅也可以占据较小的画幅。

在理解了二者的不同后,我们可以逐渐从图像分类过渡到目标检测上。

二、图像金字塔与滑动窗口

基于上面对目标检测任务的介绍,我们可以很容易的想到目标检测的一个解决方案:将需要进行目标检测的图像切片成多个小区域(不同区域之间可以有重叠部分),然后使用一个图像分类器,对每个区域进行分类,如果某个区域被图像分类器识别,则说明这个区域有一个目标。

为什么可以这样操作的呢?回顾上面提到的图像分类器所处理的图像的特点——通常只有一个对象,且该对象占据图像较大的画幅。对图像进行切片操作后所得到的区域,我们期待它只包含一个对象,且占据较大画幅。注意,这里是期待,即我们不能保证上述条件一定满足。但如果被检测的目标的大小差异不大,且是预先已知的,假设我们的待检目标基本上都是60个像素左右,那么我们对图像切片的时候只需要保证切出来的图像都是60个像素左右,就可以满足上述条件。比如,在下面这张图中,我们将图像切片成红,黄,蓝,三个部分,随后使用图像分类器,可以发现猫在蓝色框中。

cat

然而,往往目标检测的对象都不会是固定大小的。大的对象如果进行小切片会导致切片的结果都不能包含完整的目标,从而图像分类全部失败;而小的对象如果进行大切片会导致定位不精确,或者切片图像中包含多个目标。更进一步,一般的图像的分类器都只能接受固定分辨率的图像输入,也只能对固定大小(或者说大小变化不大)的图像进行分类,这更加与目标检测中的不同大小的对象这一条件所不符合。

所以,为了解决对象大小不确定所带来的问题,有一个自然而然的想法就是,我们将输入图像进行缩放,分别缩放2倍,4倍,8倍……这样将图像进行不同倍数的缩放的方法被称为图像金字塔。虽然对象大小不一,但将图像缩放不同倍数,总有一张图像上该对象的大小是正好合适的。在不同缩放大小的图像上都使用固定大小的滑动窗口,就可以解决对象大小不统一的问题。

更进一步,滑动窗口方法的定位往往不够精准,我们只能知道目标在某个窗口中,但窗口的位置只有一些固定的位置,这导致了定位较为粗略。由于应用了图像金字塔,总有某个缩放下的滑动窗口中只包含一个目标。既然只包含一个目标,我们可以将我们的分类器进行一下“升级”,即现在图像分类器不仅仅输出图像的类别,同时输出该图像中对象的位置,由于前面以及保证了滑动窗口中只有一个对象,所以也不用担心窗口中多个目标从而不知道该输出哪个目标的位置。

这样处理之后,目标检测的问题可以说就已经被解决了。但图像金字塔加滑动窗口的策略计算量实在是太大,所以目标检测有后续的发展。

三、改进图像金字塔

上面说到,目标检测就是由图像金字塔+滑动窗口的方式解决的,那么想改良目标检测方法,减少计算量就得从这两点出发。

那么我们首先来改进目标检测两个步骤中的图像金字塔。

我们知道,图像金字塔设计之初是用来解决目标大小不确定这个问题的。由于应用于滑动窗口的分类器(注意这里分类器指还可以同时对单一目标进行定位的分类器)只能接受固定分辨率大小的图像输入,且只能对固定大小(或者大小不怎么变化)的目标进行分类。使用图像金字塔其实是人为构造了不同大小的对象,从而总有一个对象可以被分类器正确分类。这是一个改变输入(图像),去适应系统(分类器)的过程。

四、改进滑动窗口

Read More
 2020-05-19
血泪的教训,Cache与DMA的一致性

最近开了一个四轴飞行器的坑,把一直在吃灰的STM32H7的板子拿出来用了用。正好机架啥的都还没有,就打算从小模块开始,于是拿出了一块SPI显示屏。在玩H7的时候,知道它M7的内核有Cache,果断开起来,白赚的速度不要白不要。。。本应该是这样的,直到我给SPI打开了DMA。

对于SPI的控制,我开辟了一块空间做“显存”,显存数据定时通过SPI传给显示屏,而绘图都在显存内进行绘图。这个SPI屏是240*240的分辨率,SPI波特率30M,算下来RGB565的彩点,刷屏速率只有30Hz左右,还会把CPU资源给占完。这哪行啊,CPU占满了拿什么去飞控←_←。于是我打开了DMA。。。这下惨了,原本正常的显示屏上千疮百孔,总有些区域显示不全。

spi-lcd

而且奇怪的是,只要打开DMA,必出现上述现象;而只要关闭DMA,上述现象就消失了。玩个单片机还能闹鬼了不成?作为一名程序员,发现问题首先问自己又错在哪里了。因为计算机总是忠实的按照自己的编程运行,闹鬼是不可能的。就这样折腾了老半天,在一次调试的过程中,突然发现,如果在启动DMA写数据前,先遍历访问一次“显存”区域,会使现象减弱或者消失。这时,即便对Cache了解并不深入的我,也终于意识到了问题所在,果断关闭Cache,果不其然,BUG消失了。


Cache的DMA之间会有数据一致性的问题。DMA全名Direct Memory Access,即直接内存访问,顾名思义,DMA访问总线前是不会经过Cache的。而Cache又名高速缓存,位于CPU和总线之间。Cache包括读缓冲和写缓冲,读缓冲指CPU想访问的总线地址如果已经在Cache中,则直接从Cache中读;写缓冲指CPU写总线会先写到Cache中,直到满足某些条件,Cache的数据才会写入内存。

所以问题很明显了,程序写了“显存”区域之后,有的数据还在Cache中,未写入内存;而这是DMA开始工作,直接从内存中读取数据,读到的就不是完整的数据。同理,如果DMA从外设将数据送回内存,CPU在读的时候也可能因为Cache读缓冲导致没有读到最新的数据。

解决方法也很简单:

  • 关Cache。但这不是个好办法,既然有Cache,那自然是想用的,把它关了不就亏了。
  • 设置Cache熟悉。Cache可以设置直接读写某块地址空间,这应该是比较好的办法。
  • 手动刷新Cache。即在DMA发送前,手动刷新Cache,保证数据已经回写到内存中。

问题根本原因还是对硬件不够熟悉,想做嵌入式开发,对硬件的熟悉性真的很重要!

Read More