【Libtorch】语义分割部署踩坑记录(目前包括resize产生新的值、标注不同类别时重叠等问题,后续不断更新)

6 篇文章 1 订阅
3 篇文章 0 订阅

本博客记录本人在使用Libtorch(C++版本的Pytorch)实现语义分割的训练以及推理时碰到的坑点。

一、语义分割标签图resize问题

1.背景

一般对较大尺寸的图像时,会指定大小对原图进行resize,与之对应的标签图也要resize,比如将大小为(1024,1280)的原图指定到(512,640)。

2.问题点描述

但是要注意如果使用OpenCV自带的resize函数时对标签图会带来个问题:
每个分类的标签图上理论上的像素值只有N+1个(N是分类个数,加1是背景类),如果直接使用OpenCV自带的resize函数,将会在0和255的边缘处产生新的值,这样一来可能会产生新的值。

3.什么时候会产生这个问题点?

如果非常不凑巧的话,会发生以下一种错误的情况:比如说本人现在是3分类,标签图原始的像素值只有0,85,170,255四个像素值。然后使用了OpenCV自带的resize函数后,在0和170的边缘处产生了85这个像素值,而这个边缘就会被认为是第一类!事实上本人在过去做过的多分类的语义分割项目中,看到过边缘处冒出小点点但是是其他类别的分割结果,直到今天可以解释这个奇怪的现象!
本人亲自对resize过后的图保存下(此处注意又有坑,要用png或者bmp格式保存,不要用jpg格式保存,否则肯定会产生新的值),然后使用Python代码对所有可能的像素值遍历一遍,来观察0到255每个像素出现过的次数,代码如下:

import cv2
import numpy as np
src_path = "./your_image.png"
img = cv2.imdecode(np.fromfile(src_path, dtype=np.uint8), -1)

count = 0
for pixel in range(256):
    aa = img[img==pixel]
    if len(aa)>0:
        count+=1
        print("pixel:",pixel)
        print("这个像素值在整张图中的个数是:",len(aa))
 print("这个图的像素值个数是:",count)

4.如何避免这个问题点

综上所述,我们的目标是对原始标签图resize时,要让结果图不会产生新的像素值。
但是本人试过OpenCV自带的resize函数的最近邻插值(即参数0),仍然会产生新的值出来,所以必须得自己实现一个不会产生新的像素值的resize函数。
目前在网上已经有人实现了这样的功能,不过他的数据是float,而我的数据是unsigned char,所以在他的基础上修改了下,代码如下:


//srcImage是单通道图,在用imread读图时要记得加参数0
cv::Mat resize_no_new_pixel(cv::Mat srcImage,int out_h,int out_w)
{
	cv::Mat dstImage(out_h, out_w, CV_8UC1);
	int height = srcImage.rows;
	int width = srcImage.cols;
	double w_scale = (double)width / out_w;
	double h_scale = (double)height / out_h;
	for (int j = 0; j < out_h; j++)
	 {
		unsigned char* data = dstImage.ptr<unsigned char>(j);
		for (int i = 0; i < out_w; i++) {
			int raw_w = int(i * w_scale);
			int raw_h = int(j * h_scale);
			data[i] = srcImage.at<unsigned char>(raw_h, raw_w);
		}
	}
	return dstImage;
}

同时本人在做实验时顺便实现了Python版本的代码:

import numpy as np

def resize_no_new_pixel(src_img,out_h,out_w):
    dst_img = np.zeros((out_h,out_w))

    height =  src_img.shape[0]
    width  =  src_img.shape[1]

    w_scale = float(width/out_w)
    h_scale = float(height/out_h)
    
    for j in range(out_h):
        for i in range(out_w):
            raw_w = int(i*w_scale)
            raw_h = int(j*h_scale)
            dst_img[j][i]=src_img[raw_h][raw_w]

    return dst_img

参考资料:
resize缩放使用最邻近插值INTER_NEAREST产生新值的问题

二、语义分割多分类标注重叠问题

1.背景

语义分割的多分类问题一般是一个像素对应唯一一个类别,不可以对应多个类别。

2.问题点

在实际标注图像数据的过程中可能会出现两个类别贴的太近,导致某个像素位置被标注了两类。这样一来,这个位置的像素可能会出现类别错误的情况。

3.什么时候会产生这个问题点?

比如还是3分类语义分割项目,标注的像素值总共有0,85,170,255四个像素值。那么假如某个地方第1类和第2类的目标挨的特别近,在标注的时候又不小心有重叠区域了。

如果我们的代码在生成标签图以及给到tensor时是通过叠加的方式,那么显然会有出现85+170=255导致肯定不属于第3类的部分像素被标记成第3类,这样就发生错误。代码如下:

//img是原图
int out_h = 512;
int out_w = 512;
cv::Mat mask = cv::Mat::zeros(img.size(),CV_8UC1);
cv::Mat mask_resize;
for(int i=0;i<num_classes;i++)
{
     XXXXX//读取标签原图给到mask_temp;
     mask = mask + mask_tmp / 255 * int( 255 / num_classes * (i + 1));
}
//做resize处理
mask_resize = resize_no_new_pixel(mask,out_h,out_w);

//mask_resize是mask经过resize的图
torch::Tensor label_tensor = torch::from_blob(mask_resize.data, { mask_resize.rows, mask_resize.cols, 1 }, torch::kByte).permute({ 2, 0, 1 });
torch::Tensor label = torch::zeros_like(label_tensor);
for (int i = 0; i < num_classes; i++)
{
	label = label + ( label_tensor == int(  (255 / num_classes)*(i + 1)))*(i+1);
}

4.如何避免这个问题点

综上所述,我们的目标是要让每个像素位置有唯一确定的像素,且不能被标记成错误的类别
本人一开始的想法是对于重叠的像素直接作为0处理,但是实现起来不是两三行代码搞定的事情。本人目前的做法比较粗暴,在从第1个类别到最后一个类别循环遍历时,直接将当前的类别对应的标记结果赋给汇总的标签图,这样标注的重叠区域的像素会被当做类别号较大的类别。比如第1个类别和第2个类别重叠的区域的类别被当作第2类。
代码如下:

//img是原图
int out_h = 512;
int out_w = 512;
cv::Mat mask = cv::Mat::zeros(img.size(),CV_8UC1);
cv::Mat mask_resize;
for(int i=0;i<num_classes;i++)
{
    XXXXX//读取标签原图给到mask_temp;
   	for (int k = 0; k < height__; k++)
	{
		for (int j = 0; j < width__; j++)
		{
			if (mask_tmp.at<unsigned char>(k, j) != 0)
			{
				mask.at<unsigned char>(k, j) = mask_tmp.at<unsigned char>(k, j) / 255 * int(255 / num_classes * (i + 1));
			}
		}
	}
}
//做resize处理
mask_resize = resize_no_new_pixel(mask,out_h,out_w);

//mask_resize是mask经过resize的图
torch::Tensor label_tensor = torch::from_blob(mask_resize.data, { mask_resize.rows, mask_resize.cols, 1 }, torch::kByte).permute({ 2, 0, 1 });
torch::Tensor label = torch::zeros_like(label_tensor);
for (int i = 0; i < num_classes; i++)
{
	auto index_label = label_tensor == int((255 / num_classes) * (i + 1));
	label.index_put_({ index_label }, (i + 1));
}

对于标注重叠区域类别的处理方案后续会再更新。

三、图像尺寸问题

本人对图像的resize采取的是smallestmaxsize函数,即短边缩放到指定的参数,长边按照同比例缩放。
但是最近本人发现一个问题,如果本身原始图像尺寸就是奇数,然后经过缩放之后,它的宽如果不是4的倍数的话训练时的loss会发散得特别厉害。
所以本人要两种解决思路:
1.指定宽为缩放的尺寸,这样很容易判断宽是否为4的倍数。
2.仍然采用smallestmaxsize函数,但是要计算缩放之后的宽是多少,如果不是4的倍数的话,取相近的一个4的倍数的数作为缩放后的宽。

四、随即旋转图像问题

如果在数据增强的部分加入了随机旋转的话,对图像分割来说要随即旋转原图和标签图。
使用的函数是cv::warpAffine()

cv::warpAffine(src, dst, rot_mat, dst_sz, interpolation, border_mode);

而需要注意的是border_mode,也就是边缘插的像素值。
对于原图来说,合理的方式应该是插边缘像素值,所以border_mode取1;
对于标签图来说,应该是在其他地方统统取0,所以border_mode取0。

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值