前言
最近读了小样本计数的开山之作Learning To Count Everying,并对代码进行了一下复现,在这里做一个简短的记录。
原文链接:[2104.08391] Learning To Count Everything (arxiv.org)
代码链接:cvlab-stonybrook/LearningToCountEverything (github.com)
我对本文代码进行调试,并且增加了注释也就是我的理解,放到了github,可以直接下载查看:[PaperRecurrent/FamNet–Learning To Count Everything at main · LengNian/PaperRecurrent (github.com)](https://github.com/LengNian/PaperRecurrent/tree/main/FamNet--Learning To Count Everything)但是这里需要自己下载数据集和密度图保存才可以运行
环境调试
在我印象中,根据readme进行就可以了,只需要修改一下train.py中的
parser.add_argument("-dp", "--data_path", type=str, default='/home/hoai/DataSets/AgnosticCounting/FSC147_384_V2/', help="Path to the FSC147 dataset")
将其改为
parser.add_argument("-dp", "--data_path", type=str, default='./data/', help="Path to the FSC147 dataset")
就可以了,同时要把自己下载的FSC-147数据集放到data文件夹下,也就是
—data
—image_384_VarV2
—x.jpg(图片)
—其余自带的文件
数据集可以在readme中的链接下载
当我们想要测试val和test的结果时可以使用
python test.py --test_split val
python test.py --test_split test
在使用demo自己画框进行测试时,也就是使用
python demo.py --input-image orange.jpg
进行测试时,按n可以进行画框,按空格或者回车进行保存,按q或者ESC代表画框结束,进行预测。
可能按q结束时会报错
cv2.error: OpenCV(4.10.0) D:\a\opencv-python\opencv-python\opencv\modules\highgui\src\window_w32.cpp:1261: error: (-27:Null pointer) NULL window: 'Image' in function 'cvDestroyWindow'
这时候把demo.py中的
cv2.destroyWindow("Image")
修改为
cv2.destroyWindow("image")
因为这篇论文是近年的,所以没有很多版本不兼容的问题。
读论文
现有的计数都是针对某一个确定类别的方法进行,比如人群计数,车辆计数等,但是本篇论文提出的方法与类别无关,而是只要给出几个特定类别的示例图像,就可以在图像中对该示例类别进行计数。同时该论文也提出一个小样本计数数据集FSC-147。
本文提出的FamNet网络,包括两个部分,分别是特征提取模块和密度预测模块。
本文是通过给定很少的几个样本,从而得到图像中给定样本的数目,比如一副图像中有40个苹果,通过给定3个苹果的边界框,从而可以对图像中的40个苹果计数。这里特别说明给定的3个苹果称为示例图像或者示例类别,苹果则是感兴趣的类别。
为了更好的介绍之后的工作,这里先对FSC-147数据集进行介绍,该数据分为train、val和test。每张图片作者都有三个示例类别即对其标注了边界框以及对每张图片中感兴趣的类别进行了点注释。
特征提取模块:结构就是ResNet的前四个块。使用ResNet的前四个块提取特征,然后保存第三和第四个块得到的特征图。
得到特征图以后会对示例特征与图像特征之间进行操作(我的理解就是进行了卷积操作),得到相关图。为了解释不同比例下的对象,作者这里使用了三种不同的缩放尺度对示例特征进行缩放,即0.9,1,1.1。也就是会得到这三个不同比例下的示例特征和图像特征的相关图。看到这里可能会不懂到底是怎么操作,别急,只要知道得到了相关图就可以了,请继续往下看。
密度预测模块:密度预测模块的输入就是上面提到的相关图,会输出密度图。
下面我介绍一下得到示例特征和图像特征相关图的流程:
上面提到会保存ResNet中第三和第四个块的特征图。以第三个特征图为例,此时第三个特征图就是图像特征,因为给出了示例图像的边界框,所以根据边界框我们就可以得到给出的示例图像所对应的示例特征,也就是在图像特征图上根据边界框截取对应部分(截取过程中还涉及调整大小以及插值等问题,这里就不赘述,主要讲整个流程)。得到三个边界框对应的示例特征以后,会堆叠起来,此时把堆叠起来的示例特征图的形状就是[3, channel, height, width],前面那个3就代表三个边界框所堆叠起来。之后怎么得到相关图呢,就是对图像特征进行卷积操作,卷积核就是我们得到的示例特征。然后上面也提到会有不同的缩放尺度,所以这里对特征图进行缩放,缩放比列分别是0.9和1.1。然后再使用缩放后的示例特征对图像特征进行卷积操作,就得到了不同缩放尺度下的相关图。将三个不同尺度下的相关图再次堆叠。此时的相关图形状为[3, 3, height, width]。这就是得到部分相关图的流程,然后重复上述流程对ResNet第四个块中的特征图进行相同的操作,也会得到一个相同形状的相关图,再将这两个相关图堆叠,得到的最终相关图形状就为[3, 6, height, width] (这里还涉及到了交换维度顺序等内容,所以是[3,6,h,w]而不是[6,3,h,w],详细可见源码)
这是我认为文中蛮重要一个点,接下来我将解释另一个点Test-time adaptation。
这里定义几个变量B是边界框的集合,b是单个边界框,Z为密度图,$ Z_b $就是边界框所对应的那块区域的密度图。
作者提出了两个损失:最小计数损失和扰动损失,分别介绍。
最小计数损失(Min-Count Loss):
扰动损失(Perturbation Loss):
示例图像周围密度值在理想情况下应该类似于高斯分布,所以通过计算预测密度图与理想情况下的高斯分布的差异。
The combined adaption Loss,将这两种损失进行结合:
在测试进行时,使用这种方法能够使网络更适应与当前的目标类别,在测试阶段会进行100次迭代,传递梯度,优化参数,从而使网络能够更适应当前预测的类别。
源码理解
代码主要关注utils.py中的内容。
如果想仔细看源码的理解,可以去参考我的注释[PaperRecurrent/FamNet–Learning To Count Everything at main · LengNian/PaperRecurrent (github.com)](https://github.com/LengNian/PaperRecurrent/tree/main/FamNet--Learning To Count Everything)。
参考文献
CVPR论文解读《Learning To Count Everything》 - 同淋雪 - 博客园 (cnblogs.com)