在使用opencv的dnn模块做深度学习时,经常会遇到一个疑惑。疑惑是这样的:用C++编写的程序,当执行完outs = net.forward()之后,如果outs里有Mat的维数大于2,那么设断点调试的时候,可以看到rows和cols都等于-1,以目标检测为例,输出outs里包含box和score,断点调试查看out_score的信息如下:
但是最后看程序运行的可视化结果,是没问题的。这个疑惑,在opencv源码里可以找到解释,在mat.hpp文件,第2559行至2561行,中间的注释里说:当矩阵的维数大于2时,rows和columns的值为-1
首先分析,out_score的形状,如果是基于pytorch框架做目标检测,那么它的形状是(batchsize, num_proposal, num_class)。在使用opencv的dnn模块做深度学习目标检测时,通常是输入一张图片到神经网络里做前向计算的,因此batchsize这个维度是多余的。想要避免出现rows和cols都等于-1这种情况,在生成onnx文件之前,添加对输出张量的消除batchsize维度的处理,这个在pytorch里有现成的函数squeeze()满足这个需求。假如没有添加这个步骤,那么在opencv里也有解决办法。使用opencv的reshape函数就能达到pytorch或者numpy里的squeeze()函数消除维数是1的那个维度,示例代码: out_score = out_score.reshape(0, num_proposal); num_proposal是二维矩阵的行数
之所以需要消除维数是1的那个维度,是因为在接下来计算每一个proposal的最大类别置信度(也就是求out_score的每一行的最大值)时,可以使用opencv提供的minMaxLoc函数来完成,而在调用这个函数之前需要提取out_score的一行,示例代码: Mat scores = out_score.row(idx).colRange(0, num_class); 这时如果out_sxcore的dims大于2,程序就会报错,因而需要在此之前消除维数是1的维度。调用opencv现成函数来求最大类别置信度,这样就不需要自己手动编写for循环了。当然,如果你自己手动编写for循环求最大类别置信度,那么前面out_score的dims是否大于2就无关紧要了,这时也就不要消除维数是1的维度。
上面讲述的是编写C++程序调用opencv的dnn模块做深度学习时的一个bug,但是编写python程序调用opencv的dnn模块做深度学习时就不会出现这个问题,因为在python程序里,当执行完outs = net.forward()之后,返回的是numpy.array这种数据结构,这是与C++程序的一个区别。
不过,在C++程序里,当Mat的dims>2时,想要访问Mat的高和宽,可以通过size属性获取,例如上面的out_score,在程序里添加 cout<<out_score.size[0]<<","<<out_score.size[1]<<","<<out_score.size[2]<<endl;
就可以打印出out_socre的维度信息了。