【机器学习小记】【YOLO】deeplearning.ai course4 3rd week programming

卷积神经网络-车辆识别

  • 模型
  • 加载模型
  • 一些其他的问题
    • tf.boolean_mask
    • tf.image.non_max_suppression
    • tf.keras.backend.gather
    • ValueError: bad marshal data (unknown type code)
    • yolo_head出现错误

目标:
	1. 学习使用YOLO(you only look once)算法
	修改【参考文章】的代码,使用tensorflow2实现

参考自:
1.【中文】【吴恩达课后编程作业】Course 4 - 卷积神经网络 - 第三周作业 - 车辆识别
2.吴恩达深度学习课后作业tensorflow2实现


确保先安装了keras,tf.keras不顶用
pip install keras


模型

  1. 输入的图片是(m,608,608,3)
  2. 经过一个CNN(Convolutional Neural Network)之后,输出(m,19,19,5,85)
    也就是把原本的图片分成了
    19x19的小格子
    ,每个格子上有5个锚框(anchor boxes),每个锚框都包含了
    (可能是该分类的概率(置信度)p,
    物体中心的x坐标,
    物体中心的y坐标,
    物体高度h,
    物体宽度w,
    每个分类的概率c(共有80个分类))

实际上,为了方便数据的操作,最后CNN输出的是(m,19,19,425)的向量


每个小格子预测一种类型的物体,将置信度p*每个分类的概率c,然后取p*c的最大值,就得到了该小格子最大可能是什么物体。

  1. 将CNN输出的(m,19,19,425)向量进行分类,分成3类方便之后的操作。

(1)box_confidnce:(m,19,19,5,1),包含每个小格子的置信度 p
(2)boxes:(m,19,19,5,4),包含每个格子预测物体的 位置 ( p x , p y , p h , p w ) (p_x,p_y,p_h,p_w) px,py,ph,pw
(3)box_classes_probs:(m,19,19,5,80),包含每个格子预测物体种类的概率

  1. 根据阈值 threshold舍弃掉一些锚框
def yolo_filter_boxes(box_confidence,boxes,box_class_probs,threshold=0.6):
    """
    通过阈值来过滤对象和分类的置信度。

    参数:
        box_confidence  - tensor类型,维度为(19,19,5,1),包含19x19单元格中每个单元格预测的5个锚框中的所有的锚框的pc (一些对象的置信概率)。
        boxes - tensor类型,维度为(19,19,5,4),包含了所有的锚框的(px,py,ph,pw )。
        box_class_probs - tensor类型,维度为(19,19,5,80),包含了所有单元格中所有锚框的所有对象( c1,c2,c3,···,c80 )检测的概率。
        threshold - 实数,阈值,如果分类预测的概率高于它,那么这个分类预测的概率就会被保留。

    返回:
        scores - tensor 类型,维度为(None,),包含了保留了的锚框的分类概率。
        boxes - tensor 类型,维度为(None,4),包含了保留了的锚框的(b_x, b_y, b_h, b_w)
        classess - tensor 类型,维度为(None,),包含了保留了的锚框的索引

    注意:"None"是因为你不知道所选框的确切数量,因为它取决于阈值。
          比如:如果有10个锚框,scores的实际输出大小将是(10,)
    """

    # 第一步,计算锚框的得分
    box_scores = box_confidence * box_class_probs
    # out: (19,19,5,80)

    # 第二步,找到最大值锚框的索引,[以及]对应最大值锚框的分数
    box_classes = tf.argmax(box_scores,axis=-1)
    # out: (19,19,5)
    box_class_scores = tf.reduce_max(box_scores,axis=-1)
    # out: (19,19,5)

    # 第三步,根据阈值 创建掩码
    filtering_mask = box_class_scores >= threshold
    # out: (19,19,5)的boolean数组

    # 对scores,boxes以及classes使用掩码
    # boolean_mask如果axis不写,则从第一个维度开始
    # boolean_mask每次执行的结果,放进一个数组
    scores = tf.boolean_mask(box_class_scores,filtering_mask)
    # out: (1786,) 消失了一部分数据
    boxes = tf.boolean_mask(boxes,filtering_mask)
    # out: (1786,4)
    classes = tf.boolean_mask(box_classes,filtering_mask)
    # out: (1786,)

    return scores,boxes,classes

测试:

box_confidence = tf.random.normal([19, 19, 5, 1], mean=1, stddev=4, seed=1)
boxes = tf.random.normal([19, 19, 5, 4], mean=1, stddev=4, seed=1)
box_class_probs = tf.random.normal([19, 19, 5, 80], mean=1, stddev=4, seed=1)
scores, boxes, classes = yolo_filter_boxes(box_confidence, boxes, box_class_probs, threshold=0.5)

print("scores[2] = " + str(scores[2].numpy()))
print("boxes[2] = " + str(boxes[2].numpy()))
print("classes[2] = " + str(classes[2].numpy()))
print("scores.shape = " + str(scores.shape))
print("boxes.shape = " + str(boxes.shape))
print("classes.shape = " + str(classes.shape))

输出:

scores[2] = 16.064978
boxes[2] = [ 1.0273384 -2.1178942  4.8872733 -4.0143332]
classes[2] = 78
scores.shape = (1786,)
boxes.shape = (1786, 4)
classes.shape = (1786,)

  1. 由于非最大值抑制输入的boxes的格式是对角线的两个顶点作为定位的,这里需要转换一下。

  2. 原来的图像可能也不是608x608的,这里也需要缩放一下boxes,然后再进行非最大值抑制

  3. 对上面的输出进行非最大值抑制/非极大值抑制NMS(Non-Maximum Suppression)

IoU = 交集的面积/并集的面积

非最大值抑制

流程:

  1. 根据置信度p对边框进行排序
  2. 选择置信度最高的box_max添加到输出列表,并将其从边框列表中删除
  3. 计算所有边框的面积
  4. 计算box_max与其他边框的IoU
  5. 删除IoU小于阈值的边框
  6. 重复以上过程,直至边框列表为空

(所以,非最大值抑制,还是可以有部分交集的,但是不能太多)

def yolo_non_max_suppression(scores,boxes,classes,max_boxes=10,iou_threshold=0.5):
    """
    为锚框实现非最大值抑制( Non-max suppression (NMS))

    参数:
        scores - tensor类型,维度为(None,),yolo_filter_boxes()的输出,每个小框的分数
        boxes - tensor类型,维度为(None,4),yolo_filter_boxes()的输出,每个小框的位置
        classes - tensor类型,维度为(None,),yolo_filter_boxes()的输出,每个小框预测的物体类别
        max_boxes - 整数,预测的锚框数量的最大值
        iou_threshold - 实数,交并比阈值。

    返回:
        scores - tensor类型,维度为(None,),每个锚框的预测的可能值
        boxes - tensor类型,维度为(None,4),预测的锚框的坐标
        classes - tensor类型,维度为(None,),每个锚框的预测的分类

    注意:"None"是明显小于max_boxes的,这个函数也会改变scores、boxes、classes的维度,这会为下一步操作提供方便。

    """

    # 使用使用tf.image.non_max_suppression()来获取与我们保留的框相对应的索引列表
    # 返回分数最高的max_boxes个边框的【索引值】
    nms_indices = tf.image.non_max_suppression(boxes=boxes,
                                               scores=scores,
                                               max_output_size=max_boxes,
                                               iou_threshold=iou_threshold)

    # 使用keras.backend.gather()根据索引选择对应的分数、位置和分类
    scores = tf.keras.backend.gather(reference=scores,indices=nms_indices)
    boxes = tf.keras.backend.gather(boxes,nms_indices)
    classes = tf.keras.backend.gather(classes,nms_indices)

    return scores,boxes,classes

测试:

scores = tf.random.normal([54, ], mean=1, stddev=4, seed=1)
boxes = tf.random.normal([54, 4], mean=1, stddev=4, seed=1)
classes = tf.random.normal([54, ], mean=1, stddev=4, seed=1)
scores, boxes, classes = yolo_non_max_suppression(scores, boxes, classes)

print("scores[2] = " + str(scores[2].numpy()))
print("boxes[2] = " + str(boxes[2].numpy()))
print("classes[2] = " + str(classes[2].numpy()))
print("scores.shape = " + str(scores.numpy().shape))
print("boxes.shape = " + str(boxes.numpy().shape))
print("classes.shape = " + str(classes.numpy().shape))

输出:

scores[2] = 6.938395
boxes[2] = [ 3.4738503 -0.2052151  1.9243622  1.6695945]
classes[2] = 0.59752893
scores.shape = (10,)
boxes.shape = (10, 4)
classes.shape = (10,)

将上面的操作,合并一下

def yolo_eval(yolo_outputs,image_shape=(720.,1280.),max_boxes=10,
              score_threshold=0.6,iou_threshold=0.5):
    """
    将YOLO编码的输出(很多锚框)转换为预测框以及它们的分数,框坐标和类。

    参数:
        yolo_outputs - 编码模型的输出(对于维度为(608,608,3)的图片),包含4个tensors类型的变量:
                        box_confidence : tensor类型,维度为(19, 19, 5, 1)
                        box_xy         : tensor类型,维度为(19, 19, 5, 2)
                        box_wh         : tensor类型,维度为(19, 19, 5, 2)
                        box_class_probs: tensor类型,维度为(19, 19, 5, 80)
        image_shape - tensor类型,维度为(2,),包含了输入的图像的维度,这里是(608.,608.)
        max_boxes - 整数,预测的锚框数量的最大值
        score_threshold - 实数,可能性阈值。
        iou_threshold - 实数,交并比阈值。

    返回:
        scores - tensor类型,维度为(None,4),每个锚框的预测的可能值
        boxes - tensor类型,维度为(None,4),预测的锚框的坐标
        classes - tensor类型,维度为(None,),每个锚框的预测的分类
    """

    # 获取YOLO CNN模型的输出
    box_confidence,box_xy,box_wh,box_class_probs = yolo_outputs

    # 中心点转换为边角
    boxes = yolo_utils.yolo_boxes_to_corners(box_xy,box_wh)

    # 可信度分值过滤
    scores,boxes,classes = yolo_filter_boxes(box_confidence,
                                             boxes,
                                             box_class_probs,
                                             score_threshold)
    # 缩放锚框,以适应图像
    boxes = yolo_utils.scale_boxes(boxes,image_shape)

    # 使用非最大值抑制
    scores,boxes,classes = yolo_non_max_suppression(scores,
                                                    boxes,
                                                    classes,
                                                    max_boxes,
                                                    iou_threshold)

    return scores,boxes,classes

测试:

yolo_outputs = (tf.random.normal([19, 19, 5, 1], mean=1, stddev=4, seed=1),
                tf.random.normal([19, 19, 5, 2], mean=1, stddev=4, seed=1),
                tf.random.normal([19, 19, 5, 2], mean=1, stddev=4, seed=1),
                tf.random.normal([19, 19, 5, 80], mean=1, stddev=4, seed=1))
scores, boxes, classes = yolo_eval(yolo_outputs)

print("scores[2] = " + str(scores[2].numpy()))
print("boxes[2] = " + str(boxes[2].numpy()))
print("classes[2] = " + str(classes[2].numpy()))
print("scores.shape = " + str(scores.numpy().shape))
print("boxes.shape = " + str(boxes.numpy().shape))
print("classes.shape = " + str(classes.numpy().shape))

输出:

scores[2] = 138.6871
boxes[2] = [-3737.2234  2206.7576 -1381.3837  2188.9182]
classes[2] = 60
scores.shape = (10,)
boxes.shape = (10, 4)
classes.shape = (10,)

加载模型

def predict(yolo_model,image_file,is_show_info=True,is_plot=True):
    """
    运行存储在sess的计算图以预测image_file的边界框,打印出预测的图与信息。

    参数:
        sess - 包含了YOLO计算图的TensorFlow/Keras的会话。
        image_file - 存储在images文件夹下的图片名称
    返回:
        out_scores - tensor类型,维度为(None,),锚框的预测的可能值。
        out_boxes - tensor类型,维度为(None,4),包含了锚框位置信息。
        out_classes - tensor类型,维度为(None,),锚框的预测的分类索引。
    """
    class_names = yolo_utils.read_classes("model_data/coco_classes.txt")
    anchors = yolo_utils.read_anchors("model_data/yolo_anchors.txt")
    image_shape = (720., 1280.)
    # 图像预处理
    image, image_data = yolo_utils.preprocess_image("images/"+image_file,model_image_size=(608,608))

    # 预测图像,结果为(None,19,19,425)
    yolo_model_output = yolo_model.predict(image_data)
    print("yolo_model_output.shape",yolo_model_output.shape)

    # yolo_head将yolo模型的输出进行转换为各个各自种每个锚框的(坐标、宽高、预测值、分类值)
    yolo_outputs = yolo_head(yolo_model_output,anchors,len(class_names))

    scores, boxes, classes = yolo_eval(yolo_outputs, image_shape)

    # 打印预测信息
    if is_show_info:
        print("在" + str(image_file) + "中找到了" + str(len(boxes)) + "个锚框。")

    # 指定要绘制的边框的颜色
    colors = yolo_utils.generate_colors(class_names)

    # 绘制边界并保存图片
    yolo_utils.draw_boxes(image,scores,boxes,classes,class_names,colors)
    image.save(os.path.join("out",image_file),quality=100)

    # 打印出已经绘制了边界框的图
    if is_plot:
        output_image = plt.imread(os.path.join("out",image_file))
        plt.imshow(output_image)
        plt.show()

    return scores,boxes,classes

yolo_utils.preprocess_image进行图片的预处理:缩放图片,归一化图片
yolo_head将CNN的输出(1,19,19,425),转换成

yolo_outputs:{
	box_confidence(19,19,5,1) 置信度
	box_xy(19,19,5,2) 方框的xy坐标
	box_wh(19,19,5,2) 方框的宽度w和高度h
	box_class_probs(19,19,5,80) 方框每个锚框的每个物体种类的预测概率
}

ps: 将model当作参数传入后,在批量predict的时候,就不用重复加载了

def batchPredict():
    # 获得模型
    yolo_model = tf.keras.models.load_model("model_data/yolo.h5")
    for i in range(1,121):
        num_fill = int(len("0000") - len(str(1))) + 1
        filename = str(i).zfill(num_fill) + ".jpg"
        print("当前文件:" + str(filename))

        # 开始预测
        predict(yolo_model=yolo_model,
                image_file=filename,
                is_show_info=False,
                is_plot=False)

    print("预测完成")

一些其他的问题

tf.boolean_mask

tf.boolean_mask(
	tensor,
	mask=,
	axis=None,
	name,
)

boolean_mask如果axis不写,则从第一个维度开始
boolean_mask每次执行的结果,放进一个数组

tf.image.non_max_suppression

tf.image.non_max_suppression(
	boxes, 由边框对角线的两个坐标(y1,x1,y2,x2)组成
	scores, 每个边框的分数
	max_output_size, 最多输出多少个边框
	iou_threshold=0.5, 交并比的阈值
)

tf.keras.backend.gather

传入索引值数组,获得对应的元素列表

tf.keras.backend.gather(
	reference, 原数组
	indices 索引值数组
)
var = tf.keras.backend.variable([[1, 2, 3], [4, 5, 6]])
var_gathered = tf.keras.backend.gather(var, [0,1,0])
输出:
var_gathered =  array([[1., 2., 3.],
				       [4., 5., 6.],
				       [1., 2., 3.]], dtype=float32)

ValueError: bad marshal data (unknown type code)

load_model时候发生错误,参考

关于吴恩达车辆识别代码出错的问题

yolo_head出现错误

def yolo_head(feats, anchors, num_classes):
    """Convert final layer features to bounding box parameters.

    Parameters
    ----------
    feats : tensor
        Final convolutional layer features.
    anchors : array-like
        Anchor box widths and heights.
    num_classes : int
        Number of target classes.

    Returns
    -------
    box_xy : tensor
        x, y box predictions adjusted by spatial location in conv layer.
    box_wh : tensor
        w, h box predictions adjusted by anchors and conv spatial resolution.
    box_conf : tensor
        Probability estimate for whether each box contains any object.
    box_class_pred : tensor
        Probability distribution estimate for each box over class labels.
    """
    num_anchors = len(anchors)
    # Reshape to batch, height, width, num_anchors, box_params.
    anchors_tensor = K.reshape(K.variable(anchors), [1, 1, 1, num_anchors, 2])
    # Static implementation for fixed models.
    # TODO: Remove or add option for static implementation.
    # _, conv_height, conv_width, _ = K.int_shape(feats)
    # conv_dims = K.variable([conv_width, conv_height])

    # Dynamic implementation of conv dims for fully convolutional model.
    conv_dims = K.shape(feats)[1:3]  # assuming channels last
    # In YOLO the height index is the inner most iteration.
    conv_height_index = K.arange(0, stop=conv_dims[0])
    conv_width_index = K.arange(0, stop=conv_dims[1])
    conv_height_index = K.tile(conv_height_index, [conv_dims[1]])

    # TODO: Repeat_elements and tf.split doesn't support dynamic splits.
    # conv_width_index = K.repeat_elements(conv_width_index, conv_dims[1], axis=0)
    conv_width_index = K.tile(K.expand_dims(conv_width_index, 0), [conv_dims[0], 1])
    conv_width_index = K.flatten(K.transpose(conv_width_index))
    conv_index = K.transpose(K.stack([conv_height_index, conv_width_index]))
    conv_index = K.reshape(conv_index, [1, conv_dims[0], conv_dims[1], 1, 2])
    conv_index = K.cast(conv_index, feats.dtype) # 原本是K.dtype(feats),但是不知道为什么报错
    
    feats = K.reshape(feats, [-1, conv_dims[0], conv_dims[1], num_anchors, num_classes + 5])
    conv_dims = K.cast(K.reshape(conv_dims, [1, 1, 1, 1, 2]), K.dtype(feats))

    # Static generation of conv_index:
    # conv_index = np.array([_ for _ in np.ndindex(conv_width, conv_height)])
    # conv_index = conv_index[:, [1, 0]]  # swap columns for YOLO ordering.
    # conv_index = K.variable(
    #     conv_index.reshape(1, conv_height, conv_width, 1, 2))
    # feats = Reshape(
    #     (conv_dims[0], conv_dims[1], num_anchors, num_classes + 5))(feats)

    box_confidence = K.sigmoid(feats[..., 4:5])
    box_xy = K.sigmoid(feats[..., :2])
    box_wh = K.exp(feats[..., 2:4])
    box_class_probs = K.softmax(feats[..., 5:])

    # Adjust preditions to each spatial grid point and anchor size.
    # Note: YOLO iterates over height index before width index.
    box_xy = (box_xy + conv_index) / conv_dims
    box_wh = box_wh * anchors_tensor / conv_dims

    return box_confidence, box_xy, box_wh, box_class_probs

你可能感兴趣的:(深度学习,深度学习)