UNeedCryDear / yolov8-opencv-onnxruntime-cpp

yolov8 hub,cpp with onnxruntime and opencv
Apache License 2.0
319 stars 56 forks source link

获取mask中心点,是相对于原图还是裁切的mask图? #37

Closed StanleyYake closed 8 months ago

StanleyYake commented 10 months ago

您好,非常感谢您的code。这里我有点疑问,裁切之后,如果还是想获取mask中心区域的坐标点,这里获取的是裁剪后的吧https://github.com/UNeedCryDear/yolov8-opencv-onnxruntime-cpp/blob/main/yolov8_utils.cpp#L159 如何获取mask中心点在原图里的坐标信息呢

UNeedCryDear commented 10 months ago

这其中涉及几种参数概念,我分别用以下命名来解释。(所涉及到的batch size全部为1,所以都不考虑bs参数)

  1. src_img: 原始图片,就是输入到模型的图片,举个例子来说为3x320x160大小
  2. forward_img: 推理的时候图片的大小,python下面为动态推理,这里暂时不考虑动态,以3x640x640大小为准。也就是将src_img处理为640x640大小,具体处理方法有多种,比如将原图长边等比例拉升到640,短边不足部分padding。也就是320->640,160->320,短边拉升之后为320,不足640,分别在上下padding160之后,就变成3x640x640大小了。
  3. protos_mask:模型的“output1”输出口结果,,默认mask_ratio为4的情况下,大小为640/4=160,也就是32x160x160 你截图这里的裁剪,就是在protos_mask上面截取一块矩形区域,将其放大4倍之后的位置就是forward_img中该矩形的位置,然后经过反向的LetterBox()的操作即可转成src_img上面对应的roi。中心位置原理一样

所以,如果在protos_mask上面rect(20,60,10,20)大小的矩形的话,转到forward_img的时候矩形区域变成Rect(80,240,40,80),之后减去y轴padding的160,变成Rect(80,80,40,80),最终再缩小两倍,也就是在src_img上的位置为(40,40,20,40)

StanleyYake commented 10 months ago

好的,感谢您细心的回答。我的模型是bs=1,图片默认也是640x640输入,在该函数里看到了您的特征图到原图的缩放,我的反投射代码如下,//sigmoid cv::exp(-masks_feature, dest); dest = 1.0 / (1.0 + dest);我利用这里的dest图片获取到了中心networkCenter,缩放然后去掉左填充和顶部填充

    Point originalCenter;
    originalCenter.x = floor((net_width / seg_width * networkCenter.x - params[2]) / params[0]);
    originalCenter.y = floor((net_width / seg_width * networkCenter.y - params[3]) / params[1]);

刚好我这里有个值originalCenter算出来是负值,看到裁剪之前您还做了尺寸的微调和防溢出,可以仿照您的代码做偏移和微调吗?因为这部分部署第一次做,问题有点多,博主能否解释一下呀,先谢谢啦。PS:可以留个联系方式吗,yaked19@163.com

UNeedCryDear commented 10 months ago

可以的,溢出这部分是防止在Mat(Rect())抠图的时候由于负数或者越界导致的错误。防止越界的方法也很多,举个例子来说,图片大小为60x60,现在有个roi区域为Rect(10,20,51,20),此时roi的右边部分超过了图片,有两种做法,一种是将超过的部分丢掉,也就是将51修正为50,另外一种是整体roi左移一个像素,保持roi大小不变,也就是roi变成Rect(9,20,51,20),这两种各自有各自的理由,看你是需要哪个。然后就是上下左右都需要判断是否越界的问题。但是按理来说原图中心不至于为负数,你看下你原始的bbox中心为负数吗? 至于你的反投射,我不明白你这么做的道理是什么,但是原理上来说没问题,具体你实现过程中出现问题的话,得需要花点时间去debug了,找个简单的数据进去,看下计算结果是否符合预期,这个我暂时没空帮你。

StanleyYake commented 10 months ago

好的,我带入了图片测试分辨率较大(4024x3036),padding参数params=(0.159045726, 0.159045726, 0.0, 78.0), bounding box原始计算值Rect temp_rect = output.box;是(2031, 449, 792, 789)。下面这里计算值为(2012, 440, 830, 805)

int left = floor((net_width / seg_width * rang_x - params[2]) / params[0]); 
int top = floor((net_height / seg_height * rang_y - params[3]) / params[1]);
int width = ceil(net_width / seg_width * rang_w / params[0]);
int height = ceil(net_height / seg_height * rang_h / params[1]);

我利用sigmoid后的dest图片(cwh: 1x33x32)获取到了中心networkCenter(15.08,15.46),计算方式如下

    cv::Moments moments = cv::moments(dest);
    cv::Point2f networkCenter;
    networkCenter.x = moments.m10 / moments.m00;
    networkCenter.y = moments.m01 / moments.m00;

缩放然后去掉左填充和顶部填充

    Point originalCenter;
    originalCenter.x = floor((net_width / seg_width * networkCenter.x - params[2]) / params[0]);
    originalCenter.y = floor((net_width / seg_width * networkCenter.y - params[3]) / params[1]);

刚好我这里有个值originalCenter算出来是负值(379, -102)。 我再试试您说的防止越界。 问题1:利用sigmoid后的dest图片求中心点是否正确?这个图是1x33x32 问题2:产生负值的原因?我继续调试看看

UNeedCryDear commented 10 months ago

Rect的格式是XYWH,如果你要计算中心点,应该是(x+w/2,y+h/2),按理来说你这个的networkCenter()的值就有点不对了,原图上的bbox的xy为2012, 440,xy差距这么大的情况下,你的networkCenter点是(15.08,15.46),差距0.几就没道理。

StanleyYake commented 9 months ago

感谢您的答复,最近旋转矩形的坐标是这样获得的,在之前的基础上加入了偏移 微信图片_20231213085220 这里是计算的三个中心点:oriBox是直接横平竖直的bbox的中心点(x+w/2,y+h/2); MomentsCenter是在dest图片(cwh: 1x33x32)获取到了中心(MomentsCenter_Mod是center加了rang偏移后的结果;MomentsCenter_Big是最终在原图上的结果);rotated center是旋转矩形的中心点,以下是打印的log。三个值都略微有差异,但是通过画图,oriBox是肉眼看上去比较对的结果,其他有偏移,感觉并不是中心。 图片100 我把原始Bounding Box(单色:青色和品红)和旋转矩形(BGRW四色线)画在了一起。 Snipaste_2023-12-13_08-44-59 这个图里实际发现旋转矩形的rotated boundingbox计算的有问题,有些边界(BGRW四色线)已经跨越了mask了,是否不应该在sigmoid后的dest图片(cwh: 1x33x32)上获取到了中心和旋转矩形中心,而应该在更之前,原始的mask如何获取?

UNeedCryDear commented 9 months ago

你一直没有回答我一个问题是,为什么你要从网络输出中计算好proto-mask,然后再还原为原始检测框?正常来说都是从原始检测框中计算proto-mask上的位置然后转为原始mask的。 而你说的rotated-rect没有完整包括进去原始mask也很简单,四舍五入的问题再加上你的图片比较大缩放的原因,就会导致差距过大。比较proto-mask上面的一个像素偏移,对应到你的原始图片就可能是十几二十个的偏移量,所以你如果需要计算mask的rotated-rect,你应该对原始图片上面的最终mask使用cv::minAreaRect()来计算。

StanleyYake commented 9 months ago

好的,谢谢楼主,学生愚钝。这个地方我确实是想从原始检测框中计算proto-mask上的位置然后转为原始mask的。楼主帮我们做了一步crop bbox中的mask,我改掉Getmask2函数中的可以获取到原始的了。 Snipaste_2023-12-13_11-52-49

StanleyYake commented 9 months ago

博主您好,这里我改写resize到原图发现mask被裁切了,然后看其他实现有用逆 warpAffine完成这部分https://github.com/UNeedCryDear/yolov8-opencv-onnxruntime-cpp/blob/538243e3eb0cf0a8ba24e36f531de219d8d80350/yolov8_utils.cpp#L174 resize(dest, mask, src_img_shape, INTER_LINEAR); Snipaste_2023-12-13_17-44-19

UNeedCryDear commented 9 months ago

这里都可以,看你自己的需求,只要能达到目的,保证两个数据一致即可。