钢铁缺陷检测mask rcnn版本

2023-11-09 17:40

本文主要是介绍钢铁缺陷检测mask rcnn版本,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、项目主框架代码(tensorflow版本的Mask RCNN)

Mask RCNN论文及代码解析参考我的另一篇博客:https://blog.csdn.net/qq_32172681/article/details/99761084

Mask RCNN keras实现代码,大神的github地址:https://github.com/matterport/Mask_RCNN

运行环境:tensorflow_gpu 1.14.0,CUDA版本是10.0,cudnn版本号7.4.1,python3.6,tensorflow+keras实现。

 

二、训练过程

1、数据预处理/标签预处理

数据预处理:

去均值

数据增强imgaug

共12000+张图像,其中10000张为训练集,其他为验证集

 

标签预处理:

(1)标签格式

训练集的标签采用RLE编码,RLE编码见我的另一篇博客:https://blog.csdn.net/qq_32172681/article/details/100537042

如下图所示:ImageId_ClassId为image_name+"_"+class_id,EncodedPixels为图片的RLE编码,一共有4类缺陷,因此每4行数据表示一个图片的标签。

 

(2)将EncodedPixels转换为mask和bbox

  • 1个EncodedPixels得到1个mask,它的size为图像大小[256,1600],1表示mask,0表示background
  • 1个mask得到多个bbox:(x1,y1,x2,y2)

此部分代码参考自kaggle public kernel:https://www.kaggle.com/applefish/get-bboxes-from-segmentation-labels

"""将rle编码转换为mask数组(size=[256,1600],1表示mask,0表示background)"""
def rle_decode(mask_rle, shape=(768, 768)):"""mask_rle: run-length as string formated (start length)shape: (height,width) of array to returnReturns numpy array, 1 - mask, 0 - background"""s = mask_rle.split()starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]starts -= 1ends = starts + lengthsimg = np.zeros(shape[0] * shape[1], dtype=np.uint8)for lo, hi in zip(starts, ends):img[lo:hi] = 1return img.reshape((shape[1], shape[0])).T  # Needed to align to RLE direction""""""
def masks_as_image(in_mask_list, all_masks=None, shape=(256, 1600)):# Take the individual masks and create a single mask arrayif all_masks is None:all_masks = np.zeros(shape, dtype=np.int16)# if isinstance(in_mask_list, list):for mask in in_mask_list:if isinstance(mask, str):all_masks += rle_decode(mask, shape)return np.expand_dims(all_masks, -1)"""从一个rle编码的mask,获取所有的bbox"""
def get_bboxes_from_rle(encoded_pixels, return_mask=False):"""get all bboxes from a whole mask label""""""将rle编码转换为mask(size=[256,1600],1表示mask,0表示background)"""mask = masks_as_image([encoded_pixels])lbl = label(mask)props = regionprops(lbl)# get bboxes by a for loop"""获取mask中的全部bbox"""bboxes = []for prop in props:bboxes.append([prop.bbox[1], prop.bbox[0], prop.bbox[4], prop.bbox[3]])if return_mask:return bboxes, maskreturn bboxes

 

(3)每张image生成instance count个mask,bbox,class_id (实例分割)

  • masks: [height, width, instance count]
  • bboxes:[num_instances, (y1, x1, y2, x2)]
  • class_ids:size=instance count
def load_mask(self,image_id):"""Generate instance masks and bboxs for an image.Returns:masks: A bool array of shape [height, width, instance count] withone mask per instance.bboxs: bbox array [num_instances, (y1, x1, y2, x2)]class_ids: a 1D array of class IDs of the instance masks."""train_data = pd.read_csv(ROOT_DIR + "\\train.csv")instance_masks = []instance_bboxes = []class_ids = []instance_num = 0# 类别数为4for i in range(4):# 得到每个图片每个类别的rle编码字符串rle_0 = train_data.query('ImageId_ClassId=="' + image_id + '_' + str(i + 1) + '"')['EncodedPixels'].values[0]if isinstance(rle_0, str) != True and i != 0:continue# 得到一张图片一个类别的所有bboxes和masksbboxes_0, mask_0 = get_bboxes_from_rle(rle_0, True)if max(mask_0.reshape(-1)) != 0:# 每个类别有len(bboxes_0)个实例 (每个实例对应一个classids、instance_bboxes、instance_masks)for j in range(len(bboxes_0)):# class_idclass_ids.append(i+1)# maskx1,y1,x2,y2 = bboxes_0[j][0],bboxes_0[j][1],bboxes_0[j][2],bboxes_0[j][3]mask_temp = np.zeros([256,1600,1])mask_temp[y1:y2+1,x1:x2+1] = mask_0[y1:y2+1,x1:x2+1]instance_masks.append(mask_0)# bboxinstance_bboxes.append(np.array(bboxes_0[j]))# Pack instance masks into an arrayif class_ids:masks = np.stack(instance_masks,axis=2).astype(np.bool)[:,:,:,0]bboxes = np.stack(instance_bboxes,axis=0)class_ids = np.array(class_ids, dtype=np.int32)else:masks = np.empty([0, 0, 0])bboxes = np.empty([0, 0, 0])class_ids = np.empty([0], np.int32)return masks, bboxes, class_ids

 

(4)一张图片的mask和bbox处理结果如下(每个实例对应一个mask和一个bbox):

说明我们的数据预处理是没有问题滴~

display的代码如下:

img = cv2.imread(ROOT_DIR + "\\steels_train_images\\" + image_id)
img_0 = img.copy()
fig, [ax1, ax2, ax3] = plt.subplots(1, 3, figsize=(15, 5))
print('Found bbox', bboxes_0[j])
cv2.rectangle(img_0, (bboxes_0[j][0], bboxes_0[j][1]), (bboxes_0[j][2], bboxes_0[j][3]), (255, 0, 0), 5)ax1.imshow(img)
ax1.set_title('Image')
ax2.set_title('Mask')
ax3.set_title('Image with derived bounding box')
ax2.imshow(mask_temp[..., 0], cmap='gray')
ax3.imshow(img_0)
plt.show()

 

2、使用mask_rcnn_coco预训练模型微调

加快收敛速度

 

3、调节参数 RPN_ANCHOR_SCALES

RPN_ANCHOR_SCALES 表示anchor边长的平方,Mask RCNN中建议值为(32,64,128, 256, 512),分别是2-6个卷积特征图上anchor的尺度。我的输入图像形状IMAGE_SHAPE 为[256 1600  3],所以第2-6个卷积特征图大小为64*400、32*200、16*100、8*50、4*25,第2-6个特征图相对于原图的BACKBONE_STRIDES为[4, 8, 16, 32, 64],所以5种anchor尺度(32,64,128, 256, 512)对应到原图都是8像素。特征图上每个位置的anchors有3种比例RPN_ANCHOR_RATIOS为 [0.5, 1, 2],因此我一共有(64*400+32*200+16*100+8*50+4*25)* 3 = 102300个anchor。

当RPN_ANCHOR_SCALES 值为建议值: (32,64,128, 256, 512) 时,RPN损失如下:

当 RPN_ANCHOR_SCALES = (128, 256, 512, 1024, 2048) 时,训练1个epoch,RPN损失如下:

由此可以看出,RPN_ANCHOR_SCALES对RPN损失的影响很大,我猜想应该是图像宽度为1600,比较大,因此需要更大的anchor,我采用的anchor尺度映射到原图也就是32(anchor边长的平方为32)。

RPN_ANCHOR_SCALES 更详细的理解可以点击:https://blog.csdn.net/qq_17172467/article/details/83986518

 

4、自定义优化器

一次训练更多的图片,相当于扩大batchsize,达到扩大显存的效果(默认优化器:SGD+Momentum)

我是用mask rcnn做分割,模型比较庞大,1080显卡最多也就能跑batch size=2,但又想起到batch size=64的效果,那可以怎么办呢?一种可以考虑的方案是,每次算batch size=2,然后把梯度缓存起来,32个batch后才更新参数。也就是说,每个小batch都算梯度,但每32个batch才更新一次参数。

我的需求是,SGD+Momentum实现梯度累加功能,借鉴了keras的optimizier的定义,可以看出每个优化器SGD、Adam等都是重载了Optimizer类,主要是需要重写get_updates方法。

完整实现代码如下:

"""自定义优化器,多个batch更新一次梯度,可以小GPU训练多图片"""from keras.legacy import interfaces
from keras.optimizers import Optimizer
from keras import backend as Kclass MySGD(Optimizer):"""Keras中简单自定义SGD优化器每隔一定的batch才更新一次参数Includes support for momentum,learning rate decay, and Nesterov momentum.# Argumentslr: float >= 0. Learning rate.momentum: float >= 0. Parameter that accelerates SGD in the relevant direction and dampens oscillations.decay: float >= 0. Learning rate decay over each update.nesterov: boolean. Whether to apply Nesterov momentum.steps_per_update: how many batch to update gradient"""def __init__(self, lr=0.01, momentum=0., decay=0.,nesterov=False, steps_per_update=32, **kwargs):super(MySGD, self).__init__(**kwargs)with K.name_scope(self.__class__.__name__):self.iterations = K.variable(0, dtype='int64', name='iterations')self.lr = K.variable(lr, name='lr')self.steps_per_update = steps_per_update  # 多少batch才更新一次self.momentum = K.variable(momentum, name='momentum')self.decay = K.variable(decay, name='decay')self.initial_decay = decayself.nesterov = nesterovprint("每%dbatch更新一次梯度" % self.steps_per_update)@interfaces.legacy_get_updates_supportdef get_updates(self, loss, params):"""主要的参数更新算法"""# learning rate decaylr = self.lrif self.initial_decay > 0:lr = lr * (1. / (1. + self.decay * K.cast(self.iterations,K.dtype(self.decay))))shapes = [K.int_shape(p) for p in params]sum_grads = [K.zeros(shape) for shape in shapes]  # 平均梯度,用来梯度下降grads = self.get_gradients(loss, params)  # 当前batch梯度self.updates = [K.update_add(self.iterations, 1)]  # 定义赋值算子集合self.weights = [self.iterations] + sum_grads  # 优化器带来的权重,在保存模型时会被保存for p, g, sg in zip(params, grads, sum_grads):# momentum 梯度下降v = self.momentum * sg / float(self.steps_per_update) - lr * g  # velocityif self.nesterov:new_p = p + self.momentum * v - lr * gelse:new_p = p + v# 如果有约束,对参数加上约束if getattr(p, 'constraint', None) is not None:new_p = p.constraint(new_p)cond = K.equal(self.iterations % self.steps_per_update, 0)# 满足条件才更新参数self.updates.append(K.switch(cond, K.update(sg, v), p))self.updates.append(K.switch(cond, K.update(p, new_p), p))# 满足条件就要重新累积,不满足条件直接累积self.updates.append(K.switch(cond, K.update(sg, g), K.update(sg, sg + g)))return self.updatesdef get_config(self):config = {'lr': float(K.get_value(self.lr)),'steps_per_update': self.steps_per_update,'momentum': float(K.get_value(self.momentum)),'decay': float(K.get_value(self.decay)),'nesterov': self.nesterov}base_config = super(MySGD, self).get_config()return dict(list(base_config.items()) + list(config.items()))

更详细的内容参考我的另一篇博客:https://blog.csdn.net/qq_32172681/article/details/100804607

 

5、优化器由sgd+momentum换成adam

adam自适应学习率,适合稀疏性数据

 

6、同时使用adam+学习率衰减,效果有微弱提升

在一篇博客上看到的方法

 

7、损失加权

LOSS_WEIGHTS参数表示5项损失的权重,此处我想重点训练"mrcnn_class_loss"、"mrcnn_mask_loss",因此我调高他们的权重,以期望获得更加优化的结果。

LOSS_WEIGHTS = {"rpn_class_loss": 0.25,"rpn_bbox_loss": 0.25,"mrcnn_class_loss": 2,"mrcnn_bbox_loss": 0.5,"mrcnn_mask_loss": 2
}

 

8、dropout(防止过拟合)

在全连接层加上dropout=0.5,如下:

# Classifier head
mrcnn_class_logits = KL.TimeDistributed(KL.Dense(num_classes),name='mrcnn_class_logits')(shared)
mrcnn_class_logits = KL.TimeDistributed(KL.Dropout(0.5),name='mrcnn_class_logits_dropout')(mrcnn_class_logits)
mrcnn_probs = KL.TimeDistributed(KL.Activation("softmax"),name="mrcnn_class")(mrcnn_class_logits)# BBox head
# [batch, num_rois, NUM_CLASSES * (dy, dx, log(dh), log(dw))]
x = KL.TimeDistributed(KL.Dense(num_classes * 4, activation='linear'),name='mrcnn_bbox_fc')(shared)
x = KL.TimeDistributed(KL.Dropout(0.5),name='mrcnn_bbox_fc_dropout')(x)

只有mrcnn中分类和框回归任务有全连接层,所以这部分损失明显增大。

试验下结果,再考虑是否在卷积层加dropout,测试的时候记得去掉dropout,学习率自动衰减

 

9、准确率:86%

衡量标准为dice系数:

 

10、其他优化

cascade-rcnn模型准确率更高,但是也更慢,还没有尝试

没有用批标准化,因为batchsize=2,太小了

激活函数是relu,也可以考虑用He初始化

USE_MINI_MASK参数设置为True,可以将mask放缩到固定大小,对于高分辨率图像可以节省大量内存。

 

下面是默认设置:

参数初始化:xavier权重初始化、0 bias初始化

激活函数:relu

L2权重衰减:避免过拟合

 

11、记录一个愚蠢的错误

模型训练一开始,mrcnn_bbox损失为0,然后慢慢开始有值,如下:

排查了一下问题,发现是bbox的坐标应该是(y1,x1,y2,x2),我处理成(x1,y1,x2,y2)了。

嗯,问题解决,果然越是悬疑的bug,越蠢...

 

 

知乎匿名用户:
感觉除了层数和每层隐节点的个数,也没啥好调的。其它参数,近两年论文基本都用同样的参数设定:迭代几十到几百epoch。sgd,mini batch size从几十到几百皆可。步长0.1,可手动收缩,weight decay取0.005,momentum取0.9。dropout加relu。weight用高斯分布初始化,bias全初始化为0。最后记得输入特征和预测目标都做好归一化。做完这些你的神经网络就应该跑出基本靠谱的结果,否则反省一下自己的人品。

 

这篇关于钢铁缺陷检测mask rcnn版本的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/377601

相关文章

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中,积水问题日益严重,特别是在大雨过后,积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术,我们能够智能化地检测和识别积水区域,减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积水检测系统,结合深度学习和直观的图形界面,为用户提供高效的解决方案。 源码地址: PyQt5+YoloV5 实现积水检测系统 预览: 项目背景

JavaFX应用更新检测功能(在线自动更新方案)

JavaFX开发的桌面应用属于C端,一般来说需要版本检测和自动更新功能,这里记录一下一种版本检测和自动更新的方法。 1. 整体方案 JavaFX.应用版本检测、自动更新主要涉及一下步骤: 读取本地应用版本拉取远程版本并比较两个版本如果需要升级,那么拉取更新历史弹出升级控制窗口用户选择升级时,拉取升级包解压,重启应用用户选择忽略时,本地版本标志为忽略版本用户选择取消时,隐藏升级控制窗口 2.

PostgreSQL中的多版本并发控制(MVCC)深入解析

引言 PostgreSQL作为一款强大的开源关系数据库管理系统,以其高性能、高可靠性和丰富的功能特性而广受欢迎。在并发控制方面,PostgreSQL采用了多版本并发控制(MVCC)机制,该机制为数据库提供了高效的数据访问和更新能力,同时保证了数据的一致性和隔离性。本文将深入解析PostgreSQL中的MVCC功能,探讨其工作原理、使用场景,并通过具体SQL示例来展示其在实际应用中的表现。 一、

[数据集][目标检测]血细胞检测数据集VOC+YOLO格式2757张4类别

数据集格式:Pascal VOC格式+YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):2757 标注数量(xml文件个数):2757 标注数量(txt文件个数):2757 标注类别数:4 标注类别名称:["Platelets","RBC","WBC","sickle cell"] 每个类别标注的框数:

InnoDB的多版本一致性读的实现

InnoDB是支持MVCC多版本一致性读的,因此和其他实现了MVCC的系统如Oracle,PostgreSQL一样,读不会阻塞写,写也不会阻塞读。虽然同样是MVCC,各家的实现是不太一样的。Oracle通过在block头部的事务列表,和记录中的锁标志位,加上回滚段,个人认为实现上是最优雅的方式。 而PostgreSQL则更是将多个版本的数据都放在表中,而没有单独的回滚段,导致的一个结果是回滚非

Temu官方宣导务必将所有的点位材料进行检测-RSL资质检测

关于饰品类产品合规问题宣导: 产品法规RSL要求 RSL测试是根据REACH法规及附录17的要求进行测试。REACH法规是欧洲一项重要的法规,其中包含许多对化学物质进行限制的规定和高度关注物质。 为了确保珠宝首饰的安全性,欧盟REACH法规规定,珠宝首饰上架各大电商平台前必须进行RSLReport(欧盟禁限用化学物质检测报告)资质认证,以确保产品不含对人体有害的化学物质。 RSL-铅,

JeecgBoot 升级springboot版本到2.6.0

1. 环境描述 Jeecgboot 3.0,他所依赖的springboot版本为2.3.5Release,将springboot版本升级为2.6.0。过程全纪录,从2开始描述。 2. 修改springboot版本号 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-pare