TensorFlow-YOLO_V1训练代码梳理

YOLO目标检测算法:原理、演进与实战优化 目标检测是计算机视觉的核心任务之一,其核心原理是通过算法自动识别图像中的物体位置和类别。YOLO(You Only Look Once)作为典型的单阶段检测算法,采用全卷积网络架构和网格化预测机制,将检测任务转化为端到端的回归问题,实现了速度与精度的平衡。在技术实现上,YOLO通过多尺度预测、锚框机制和损失函数优化等关键技术,显著提升了小物体检测和定位精度。该算法在工业质检、自动驾驶、医疗影像等领域有广泛应用,特别是YOLOv8等最新版本通过模型压缩和部署优化,可在边缘设备实现100+FPS的实时检测。掌握 阅读详情

TensorFlow-YOLO_V1训练代码梳理,相信看过后对yolo-v1的整个训练流程都一目了然了。

硬件:NVIDIA-GTX1080

软件:Windows7、python3.6.5、tensorflow-gpu-1.4.0

一、基础知识

1、yolo-v1的loss目标分类不是21类,而是20+2类,其中20是目标分类,2是背景和目标的二分类

2、total_loss = 1.0*class_loss + 1.0*object_loss + 0.5*noobject_loss + 5.0*coord_loss

      class_loss:20类分类误差(有目标情况下)

      object_loss:目标分类误差

      noobject_loss:非目标分类误差

      coord_loss:边框回归误差(有目标情况下)

二、数据和预训练模型下载

1、数据是voc2007-trainval,存放路径是data/pascal_voc/VOCdevkit/VOC2007

2、预训练模型:https://pan.baidu.com/s/1517r5zxY01vQgrEJUqGEfw (xh45) ,存放路径是data/weights

三、代码展示(做了详细注释)

1、train.py

import os
import argparse
import datetime
import tensorflow as tf
import yolo.config as cfg
from yolo.yolo_net import YOLONet
from utils.timer import Timer
from utils.pascal_voc import pascal_voc

slim = tf.contrib.slim


class Solver(object):

    def __init__(self, net, data):
        self.net = net
        self.data = data

        # 预训练模型文件
        self.weights_file = cfg.WEIGHTS_FILE
        # 最大迭代次数
        self.max_iter = cfg.MAX_ITER
        # 初始学习率
        self.initial_learning_rate = cfg.LEARNING_RATE
        # 学习率下降间隔数
        self.decay_steps = cfg.DECAY_STEPS
        # 学习率下降系数
        self.decay_rate = cfg.DECAY_RATE
        # 是否按照阶梯状下降
        self.staircase = cfg.STAIRCASE
        # 多少次迭代显示一次
        self.summary_iter = cfg.SUMMARY_ITER
        # 多少次迭代保存一次模型
        self.save_iter = cfg.SAVE_ITER
        # 模型保存路径
        self.output_dir = os.path.join(
            cfg.OUTPUT_DIR, datetime.datetime.now().strftime('%Y_%m_%d_%H_%M'))
        if not os.path.exists(self.output_dir):
            os.makedirs(self.output_dir)

        # 保存一次cfg配置文件
        self.save_cfg()

        self.variable_to_restore = tf.global_variables()
        self.saver = tf.train.Saver(self.variable_to_restore, max_to_keep=None)
        
        # 保存模型路径
        self.ckpt_file = os.path.join(self.output_dir, 'yolo')
        self.summary_op = tf.summary.merge_all()
        self.writer = tf.summary.FileWriter(self.output_dir, flush_secs=60)

        # decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
        # if staircase is True, int(global_step / decay_steps)
        self.global_step = tf.train.create_global_step()
        self.learning_rate = tf.train.exponential_decay(
            self.initial_learning_rate, self.global_step, self.decay_steps,
            self.decay_rate, self.staircase, name='learning_rate')

        # 模型优化器
        self.optimizer = tf.train.GradientDescentOptimizer(
            learning_rate=self.learning_rate)
        self.train_op = slim.learning.create_train_op(
            self.net.total_loss, self.optimizer, global_step=self.global_step)

        # 定义sess
        gpu_options = tf.GPUOptions()
        config = tf.ConfigProto(gpu_options=gpu_options)
        self.sess = tf.Session(config=config)
        self.sess.run(tf.global_variables_initializer())

        # 载入预训练模型
        if self.weights_file is not None:
            print('Restoring weights from: ' + self.weights_file)
            self.saver.restore(self.sess, self.weights_file)

        self.writer.add_graph(self.sess.graph)

    def train(self):

        train_timer = Timer()
        load_timer = Timer()

        for step in range(1, self.max_iter + 1):

            load_timer.tic()
            images, labels = self.data.get()
            load_timer.toc()
            feed_dict = {self.net.images: images,
                         self.net.labels: labels}

            if step % self.summary_iter == 0:
                # 每过summary_iter * 10次,打印一次输出结果
                if step % (self.summary_iter * 10) == 0:

                    train_timer.tic()
                    summary_str, loss, _ = self.sess.run(
                        [self.summary_op, self.net.total_loss, self.train_op],
                        feed_dict=feed_dict)
                    train_timer.toc()

                    log_str = '{} Epoch: {}, Step: {}, Learning rate: {},Loss: {:5.3f}\nSpeed: {:.3f}s/iter, Load: {:.3f}s/iter, Remain: {}'.format(
                        datetime.datetime.now().strftime('%m-%d %H:%M:%S'),
                        self.data.epoch,
                        int(step),
                        round(self.learning_rate.eval(session=self.sess), 6),
                        loss,
                        train_timer.average_time,
                        load_timer.average_time,
                        train_timer.remain(step, self.max_iter))
                    print(log_str)

                else:
                    train_timer.tic()
                    summary_str, _ = self.sess.run(
                        [self.summary_op, self.train_op],
                        feed_dict=feed_dict)
                    train_timer.toc()

                self.writer.add_summary(summary_str, step)

            else:
                train_timer.tic()
                self.sess.run(self.train_op, feed_dict=feed_dict)
                train_timer.toc()

            # 保存模型
            if step % self.save_iter == 0:
                print('{} Saving checkpoint file to: {}'.format(
                    datetime.datetime.now().strftime('%m-%d %H:%M:%S'),
                    self.output_dir))
                self.saver.save(
                    self.sess, self.ckpt_file, global_step=self.global_step)

    def save_cfg(self):

        with open(os.path.join(self.output_dir, 'config.txt'), 'w') as f:
            cfg_dict = cfg.__dict__
            for key in sorted(cfg_dict.keys()):
                if key[0].isupper():
                    cfg_str = '{}: {}\n'.format(key, cfg_dict[key])
                    f.write(cfg_str)


def update_config_paths(data_dir, weights_file):
    cfg.DATA_PATH = data_dir
    cfg.PASCAL_PATH = os.path.join(data_dir, 'pascal_voc')
    cfg.CACHE_PATH = os.path.join(cfg.PASCAL_PATH, 'cache')
    cfg.OUTPUT_DIR = os.path.join(cfg.PASCAL_PATH, 'output')
    cfg.WEIGHTS_DIR = os.path.join(cfg.PASCAL_PATH, 'weights')

    cfg.WEIGHTS_FILE = os.path.join(cfg.WEIGHTS_DIR, weights_file)


def main():
    '''
    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', default="YOLO_small.ckpt", type=str)
    parser.add_argument('--data_dir', default="data", type=str)
    parser.add_argument('--threshold', default=0.2, type=float)
    parser.add_argument('--iou_threshold', default=0.5, type=float)
    parser.add_argument('--gpu', default='', type=str)
    args = parser.parse_args()

    if args.gpu is not None:
        cfg.GPU = args.gpu

    if args.data_dir != cfg.DATA_PATH:
        update_config_paths(args.data_dir, args.weights)

    os.environ['CUDA_VISIBLE_DEVICES'] = cfg.GPU
    '''
    yolo = YOLONet()
    pascal = pascal_voc('train')

    solver = Solver(yolo, pascal)

    print('Start training ...')
    solver.train()
    print('Done training.')


if __name__ == '__main__':

    # python train.py --weights YOLO_small.ckpt --gpu 0
    main()

2、utils/pascal_voc.py

import os
import xml.etree.ElementTree as ET
import numpy as np
import cv2
import pickle
import copy
import yolo.config as cfg


class pascal_voc(object):
    def __init__(self, phase, rebuild=False):
        self.devkil_path = os.path.join(cfg.PASCAL_PATH, 'VOCdevkit')
        # 数据路径
        self.data_path = os.path.join(self.devkil_path, 'VOC2007')
        # 数据文件保存路径
        self.cache_path = cfg.CACHE_PATH
        # 训练批次
        self.batch_size = cfg.BATCH_SIZE
        # 训练图像大小(448x448)
        self.image_size = cfg.IMAGE_SIZE
        # 单元块个数
        self.cell_size = cfg.CELL_SIZE
        # 类别
        self.classes = cfg.CLASSES
        # {'aeroplane': 0, 'bicycle': 1...}
        self.class_to_ind = dict(zip(self.classes, range(len(self.classes))))
        # 图像是否翻转(数据增强)
        self.flipped = cfg.FLIPPED
        # train or val
        self.phase = phase
        # 判断数据是否已生成,如果没有则重新生成
        self.rebuild = rebuild
        
        self.cursor = 0
        self.epoch = 1

        # 数据标签
        self.gt_labels = None
        self.prepare()

    def get(self):
        images = np.zeros(
            (self.batch_size, self.image_size, self.image_size, 3))
        labels = np.zeros(
            (self.batch_size, self.cell_size, self.cell_size, 25))
        count = 0
        while count < self.batch_size:
            imname = self.gt_labels[self.cursor]['imname']
            flipped = self.gt_labels[self.cursor]['flipped']
            images[count, :, :, :] = self.image_read(imname, flipped)
            labels[count, :, :, :] = self.gt_labels[self.cursor]['label']
            count += 1
            self.cursor += 1
            # 当所有数据都训练一次(1 epoch)时,数据打乱再训练
            if self.cursor >= len(self.gt_labels):
                np.random.shuffle(self.gt_labels)
                self.cursor = 0
                self.epoch += 1
        return images, labels

    # 图像归一化
    def image_read(self, imname, flipped=False):
        image = cv2.imread(imname)
        image = cv2.resize(image, (self.image_size, self.image_size))
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)
        image = (image / 255.0) * 2.0 - 1.0
        if flipped:
            image = image[:, ::-1, :]
        return image

    # flip_gt_labels + gt_labels
    def prepare(self):
        gt_labels = self.load_labels()
        if self.flipped:
            print('Appending horizontally-flipped training examples ...')
            gt_labels_cp = copy.deepcopy(gt_labels)
            for idx in range(len(gt_labels_cp)):
                gt_labels_cp[idx]['flipped'] = True
                # 左右镜像需要将坐标x倒序排列,但是这里只是倒序了顺序,坐标值值未变
                gt_labels_cp[idx]['label'] =\
                    gt_labels_cp[idx]['label'][:, ::-1, :]
                for i in range(self.cell_size):
                    for j in range(self.cell_size):
                        if gt_labels_cp[idx]['label'][i, j, 0] == 1:
                            # 倒序后,坐标值需做相应镜像
                            gt_labels_cp[idx]['label'][i, j, 1] = \
                                self.image_size - 1 -\
                                gt_labels_cp[idx]['label'][i, j, 1]
            gt_labels += gt_labels_cp
        np.random.shuffle(gt_labels)
        self.gt_labels = gt_labels
        return gt_labels

    def load_labels(self):
        cache_file = os.path.join(
            self.cache_path, 'pascal_' + self.phase + '_gt_labels.pkl')

        if os.path.isfile(cache_file) and not self.rebuild:
            print('Loading gt_labels from: ' + cache_file)
            with open(cache_file, 'rb') as f:
                gt_labels = pickle.load(f)
            return gt_labels

        print('Processing gt_labels from: ' + self.data_path)

        if not os.path.exists(self.cache_path):
            os.makedirs(self.cache_path)

        if self.phase == 'train':
            txtname = os.path.join(
                self.data_path, 'ImageSets', 'Main', 'trainval.txt')
        else:
            txtname = os.path.join(
                self.data_path, 'ImageSets', 'Main', 'test.txt')

        # image_index所有图片的图片名
        with open(txtname, 'r') as f:
            self.image_index = [x.strip() for x in f.readlines()]

        gt_labels = []
        for index in self.image_index:
            label, num = self.load_pascal_annotation(index)
            if num == 0:
                continue
            imname = os.path.join(self.data_path, 'JPEGImages', index + '.jpg')
            gt_labels.append({'imname': imname,
                              'label': label,
                              'flipped': False})

        print('Saving gt_labels to: ' + cache_file)
        with open(cache_file, 'wb') as f:
            pickle.dump(gt_labels, f)
        return gt_labels

    def load_pascal_annotation(self, index):
        """
        Load image and bounding boxes info from XML file in the PASCAL VOC
        format.
        """

        imname = os.path.join(self.data_path, 'JPEGImages', index + '.jpg')
        im = cv2.imread(imname)
        h_ratio = 1.0 * self.image_size / im.shape[0]
        w_ratio = 1.0 * self.image_size / im.shape[1]
        
        # 数据置零,所有标签全为0
        # 25: [0] for IOU*obj/noobj(有目标时一般为1), [1:4] for bbox, [5:25] for prob(条件概率0 or 1)
        label = np.zeros((self.cell_size, self.cell_size, 25))
        filename = os.path.join(self.data_path, 'Annotations', index + '.xml')
        tree = ET.parse(filename)
        objs = tree.findall('object')

        for obj in objs:
            bbox = obj.find('bndbox')
            # 确保边框相对图像尺寸的有效性,且坐标相对与训练图像尺寸
            # x_min448 / x_min = image_size / im.shape[0]
            x1 = max(min((float(bbox.find('xmin').text) - 1) * w_ratio, self.image_size - 1), 0)
            y1 = max(min((float(bbox.find('ymin').text) - 1) * h_ratio, self.image_size - 1), 0)
            x2 = max(min((float(bbox.find('xmax').text) - 1) * w_ratio, self.image_size - 1), 0)
            y2 = max(min((float(bbox.find('ymax').text) - 1) * h_ratio, self.image_size - 1), 0)

            # 标签和边框信息,此时的边框暂未做单元格的归一化!归一化在yolo_net.py中取boxes时
            cls_ind = self.class_to_ind[obj.find('name').text.lower().strip()] # 小写去特殊字符
            boxes = [(x2 + x1) / 2.0, (y2 + y1) / 2.0, x2 - x1, y2 - y1]

            # 判断该中心点在哪个单元格里
            # x / x_mid448 = cell_size / image_size
            x_ind = int(boxes[0] * self.cell_size / self.image_size)
            y_ind = int(boxes[1] * self.cell_size / self.image_size)

            # 只能接受最先提供给label的一类物体,后面重复的则忽略,伏笔!!!
            if label[y_ind, x_ind, 0] == 1:
                continue

            # 根据python-opencv图像排列原则,shape[0]表示高,shape[1],所以这里y在前,x在后
            # [0] for IOU*obj/noobj(有目标时一般为1)
            label[y_ind, x_ind, 0] = 1
            # [1:4] for bbox
            label[y_ind, x_ind, 1:5] = boxes
            # [5:25] for prob(条件概率0 or 1)
            label[y_ind, x_ind, 5 + cls_ind] = 1

        return label, len(objs)

3、utils/timer.py

import time
import datetime


class Timer(object):
    '''
    A simple timer.
    '''

    def __init__(self):
        self.init_time = time.time()
        self.total_time = 0.
        self.calls = 0
        self.start_time = 0.
        self.diff = 0.
        self.average_time = 0.
        self.remain_time = 0.

    def tic(self):
        # using time.time instead of time.clock because time time.clock
        # does not normalize for multithreading
        self.start_time = time.time()

    def toc(self, average=True):
        self.diff = time.time() - self.start_time
        self.total_time += self.diff
        self.calls += 1
        self.average_time = self.total_time / self.calls
        if average:
            return self.average_time
        else:
            return self.diff

    def remain(self, iters, max_iters):
        if iters == 0:
            self.remain_time = 0
        else:
            self.remain_time = (time.time() - self.init_time) * \
                (max_iters - iters) / iters
        return str(datetime.timedelta(seconds=int(self.remain_time)))

4、yolo/yolo_net.py

import numpy as np
import tensorflow as tf
import yolo.config as cfg

slim = tf.contrib.slim

class YOLONet(object):

    def __init__(self, is_training=True):
        self.classes = cfg.CLASSES
        self.num_class = len(self.classes)
        self.image_size = cfg.IMAGE_SIZE
        self.cell_size = cfg.CELL_SIZE
        self.boxes_per_cell = cfg.BOXES_PER_CELL
        self.output_size = (self.cell_size * self.cell_size) *\
            (self.num_class + self.boxes_per_cell * 5)
        self.scale = 1.0 * self.image_size / self.cell_size
        self.boundary1 = self.cell_size * self.cell_size * self.num_class
        self.boundary2 = self.boundary1 +\
            self.cell_size * self.cell_size * self.boxes_per_cell

        # 有目标的惩罚系数
        self.object_scale = cfg.OBJECT_SCALE
        # 无目标的惩罚系数
        self.noobject_scale = cfg.NOOBJECT_SCALE
        # 类别的惩罚系数
        self.class_scale = cfg.CLASS_SCALE
        # 边框的惩罚系数
        self.coord_scale = cfg.COORD_SCALE

        # 学习率
        self.learning_rate = cfg.LEARNING_RATE
        # 训练批次
        self.batch_size = cfg.BATCH_SIZE
        self.alpha = cfg.ALPHA

        self.offset = np.transpose(np.reshape(np.array(
            [np.arange(self.cell_size)] * self.cell_size * self.boxes_per_cell),
            (self.boxes_per_cell, self.cell_size, self.cell_size)), (1, 2, 0))

        self.images = tf.placeholder(
            tf.float32, [None, self.image_size, self.image_size, 3],
            name='images')
        self.logits = self.build_network(
            self.images, num_outputs=self.output_size, alpha=self.alpha,
            is_training=is_training)

        if is_training:
            self.labels = tf.placeholder(
                tf.float32,
                [None, self.cell_size, self.cell_size, 5 + self.num_class])
            self.loss_layer(self.logits, self.labels)
            self.total_loss = tf.losses.get_total_loss()
            tf.summary.scalar('total_loss', self.total_loss)

    def build_network(self,
                      images,
                      num_outputs,
                      alpha,
                      keep_prob=0.5,
                      is_training=True,
                      scope='yolo'):
        with tf.variable_scope(scope):
            with slim.arg_scope(
                [slim.conv2d, slim.fully_connected],
                activation_fn=leaky_relu(alpha),
                weights_regularizer=slim.l2_regularizer(0.0005),
                weights_initializer=tf.truncated_normal_initializer(0.0, 0.01)
            ):
                net = tf.pad(
                    images, np.array([[0, 0], [3, 3], [3, 3], [0, 0]]),
                    name='pad_1')
                net = slim.conv2d(
                    net, 64, 7, 2, padding='VALID', scope='conv_2')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_3')
                net = slim.conv2d(net, 192, 3, scope='conv_4')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_5')
                net = slim.conv2d(net, 128, 1, scope='conv_6')
                net = slim.conv2d(net, 256, 3, scope='conv_7')
                net = slim.conv2d(net, 256, 1, scope='conv_8')
                net = slim.conv2d(net, 512, 3, scope='conv_9')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_10')
                net = slim.conv2d(net, 256, 1, scope='conv_11')
                net = slim.conv2d(net, 512, 3, scope='conv_12')
                net = slim.conv2d(net, 256, 1, scope='conv_13')
                net = slim.conv2d(net, 512, 3, scope='conv_14')
                net = slim.conv2d(net, 256, 1, scope='conv_15')
                net = slim.conv2d(net, 512, 3, scope='conv_16')
                net = slim.conv2d(net, 256, 1, scope='conv_17')
                net = slim.conv2d(net, 512, 3, scope='conv_18')
                net = slim.conv2d(net, 512, 1, scope='conv_19')
                net = slim.conv2d(net, 1024, 3, scope='conv_20')
                net = slim.max_pool2d(net, 2, padding='SAME', scope='pool_21')
                net = slim.conv2d(net, 512, 1, scope='conv_22')
                net = slim.conv2d(net, 1024, 3, scope='conv_23')
                net = slim.conv2d(net, 512, 1, scope='conv_24')
                net = slim.conv2d(net, 1024, 3, scope='conv_25')
                net = slim.conv2d(net, 1024, 3, scope='conv_26')
                net = tf.pad(
                    net, np.array([[0, 0], [1, 1], [1, 1], [0, 0]]),
                    name='pad_27')
                net = slim.conv2d(
                    net, 1024, 3, 2, padding='VALID', scope='conv_28')
                net = slim.conv2d(net, 1024, 3, scope='conv_29')
                net = slim.conv2d(net, 1024, 3, scope='conv_30')
                net = tf.transpose(net, [0, 3, 1, 2], name='trans_31')
                net = slim.flatten(net, scope='flat_32')
                net = slim.fully_connected(net, 512, scope='fc_33')
                net = slim.fully_connected(net, 4096, scope='fc_34')
                net = slim.dropout(
                    net, keep_prob=keep_prob, is_training=is_training,
                    scope='dropout_35')
                net = slim.fully_connected(
                    net, num_outputs, activation_fn=None, scope='fc_36')
        return net

    def calc_iou(self, boxes1, boxes2, scope='iou'):
        """calculate ious
        Args:
          boxes1: 5-D tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL, 4]  ====> (x_center, y_center, w, h)
          boxes2: 5-D tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL, 4] ===> (x_center, y_center, w, h)
        Return:
          iou: 4-D tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
        """
        with tf.variable_scope(scope):
            # transform (x_center, y_center, w, h) to (x1, y1, x2, y2)
            boxes1_t = tf.stack([boxes1[..., 0] - boxes1[..., 2] / 2.0,
                                 boxes1[..., 1] - boxes1[..., 3] / 2.0,
                                 boxes1[..., 0] + boxes1[..., 2] / 2.0,
                                 boxes1[..., 1] + boxes1[..., 3] / 2.0],
                                axis=-1)

            boxes2_t = tf.stack([boxes2[..., 0] - boxes2[..., 2] / 2.0,
                                 boxes2[..., 1] - boxes2[..., 3] / 2.0,
                                 boxes2[..., 0] + boxes2[..., 2] / 2.0,
                                 boxes2[..., 1] + boxes2[..., 3] / 2.0],
                                axis=-1)

            # calculate the left up point & right down point
            lu = tf.maximum(boxes1_t[..., :2], boxes2_t[..., :2])
            rd = tf.minimum(boxes1_t[..., 2:], boxes2_t[..., 2:])

            # intersection
            intersection = tf.maximum(0.0, rd - lu)
            inter_square = intersection[..., 0] * intersection[..., 1]

            # calculate the boxs1 square and boxs2 square
            square1 = boxes1[..., 2] * boxes1[..., 3]
            square2 = boxes2[..., 2] * boxes2[..., 3]

            union_square = tf.maximum(square1 + square2 - inter_square, 1e-10)

        #tf.clip_by_value限制输出值范围(0.0,1.0)
        return tf.clip_by_value(inter_square / union_square, 0.0, 1.0)

    def loss_layer(self, predicts, labels, scope='loss_layer'):
        with tf.variable_scope(scope):
            # 提取输出类别条件概率 (batch, 7, 7, 20)
            predict_classes = tf.reshape(
                predicts[:, :self.boundary1],
                [self.batch_size, self.cell_size, self.cell_size, self.num_class])
            # 提取输出各边框的置信度IOU (batch, 7, 7, 2)
            predict_scales = tf.reshape(
                predicts[:, self.boundary1:self.boundary2],
                [self.batch_size, self.cell_size, self.cell_size, self.boxes_per_cell])
            # 提取输出各边框 (batch, 7, 7, 2, 4)
            predict_boxes = tf.reshape(
                predicts[:, self.boundary2:],
                [self.batch_size, self.cell_size, self.cell_size, self.boxes_per_cell, 4])

            # 提取标签边框的置信度IOU (batch, 7, 7, 1)
            response = tf.reshape(
                labels[..., 0],
                [self.batch_size, self.cell_size, self.cell_size, 1])
            
            # 提取标签边框 (batch, 7, 7, 1, 4)
            boxes = tf.reshape(
                labels[..., 1:5],
                [self.batch_size, self.cell_size, self.cell_size, 1, 4])
            # 标签边框维度4复制了boxes_per_cell次,(batch, 7, 7, 1, 4)->(batch, 7, 7, 2, 4)
            # 边框归一化在这里,和yolo的voc_label生成的数据格式一致!!!
            # x_train = x_mid448 / 448(此时这里的坐标并不是相对于各自单元格!!!)
            # w_train = w_box / 448
            boxes = tf.tile(boxes, [1, 1, 1, self.boxes_per_cell, 1]) / self.image_size

            # 提取标签类别条件概率 (batch, 7, 7, 20)
            classes = labels[..., 5:]

            # x_offset(1x7x7x2)
            offset = tf.reshape(
                tf.constant(self.offset, dtype=tf.float32),
                [1, self.cell_size, self.cell_size, self.boxes_per_cell])
            # x_offset(1x7x7x2)->(batch x 7x7x2)
            offset = tf.tile(offset, [self.batch_size, 1, 1, 1])
            # y_offset(batch x 7x7x2)
            offset_tran = tf.transpose(offset, (0, 2, 1, 3))

            # 输出边框相对于各自单元格左上角的偏移量,计算后和yolo标签一一对应,为了计算iou!
            predict_boxes_tran = tf.stack(
                [(predict_boxes[..., 0] + offset) / self.cell_size,
                 (predict_boxes[..., 1] + offset_tran) / self.cell_size,
                 tf.square(predict_boxes[..., 2]),
                 tf.square(predict_boxes[..., 3])], axis=-1)

            # 计算的结果用作标签的边框置信度(IOU),在线计算,而实际的标签边框置信度IOU(response),是用作有无目标的过滤器
            iou_predict_truth = self.calc_iou(predict_boxes_tran, boxes)

            # calculate I tensor [BATCH_SIZE, CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
            # 求出各自边框中的最大值(batch, 7, 7, 2)->(batch, 7, 7, 1)
            object_mask = tf.reduce_max(iou_predict_truth, 3, keep_dims=True)
            # 标签置信度(batch, 7, 7, 2),其中2的值分别为0和标签置信度,0对应预测min(prebbox_labbbox_iou0, prebbox_labbbox_iou1)
            # 意思是,预测的2个边框,和标签2个边框的iou,值大者预测置信度对应标签的置信度,值小者预测置信度对应标签置信度0
            # 作用是,与其相乘,无目标的值都为0,留下有目标的
            object_mask = tf.cast((iou_predict_truth >= object_mask), tf.float32) * response

            # calculate no_I tensor [CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
            # 和object_mask相反,值大者预测置信度对应标签的(1-置信度 = 0),值小者预测置信度对应标签置信度1
            # 作用是,与其相乘,有目标的值都为0,留下无目标的
            noobject_mask = tf.ones_like(
                object_mask, dtype=tf.float32) - object_mask

            # 标签边框相对于各自单元格左上角的偏移量,计算后和训练输出边框一一对应!
            boxes_tran = tf.stack(
                [boxes[..., 0] * self.cell_size - offset,
                 boxes[..., 1] * self.cell_size - offset_tran,
                 tf.sqrt(boxes[..., 2]),
                 tf.sqrt(boxes[..., 3])], axis=-1)


            ##########################calculate loss###################
            # 目标中心点落在某个单元格内,这个单元格才有标签,其他情况没有,没有时计算的iou_predict_truth肯定为0
            
            # class_loss 1.0(只分类20类目标:有目标,则区分20类,需要计算损失;若是不在20类中的背景,就不需要计算损失)
            # (n,7,7,1)*(n,7,7,20) = (n,7,7,20)
            # 含有目标的单元格才有类别损失,其他都不需要回传损失,也就不需要计算,默认网格只出现一种类别
            class_delta = response * (predict_classes - classes)
            class_loss = tf.reduce_mean(
                tf.reduce_sum(tf.square(class_delta), axis=[1, 2, 3]),
                name='class_loss') * self.class_scale

            # object_loss 1.0(区分目标和背景,是目标则计算损失)
            # (n,7,7,2)*(n,7,7,2) = (n,7,7,2)
            # 负责检测box的标签边框置信度是在线计算的IOU,无目标的都为0
            object_delta = object_mask * (predict_scales - iou_predict_truth)
            object_loss = tf.reduce_mean(
                tf.reduce_sum(tf.square(object_delta), axis=[1, 2, 3]),
                name='object_loss') * self.object_scale

            # noobject_loss 0.5(区分目标和背景,是背景则计算损失)
            # 若是背景,iou_predict_truth所有值都为0,减不减都无所谓
            noobject_delta = noobject_mask * predict_scales
            noobject_loss = tf.reduce_mean(
                tf.reduce_sum(tf.square(noobject_delta), axis=[1, 2, 3]),
                name='noobject_loss') * self.noobject_scale

            # coord_loss 5.0(坐标误差)
            # 负责检测的才有位置损失,其他的都不需回传损失,也就不需要计算
            coord_mask = tf.expand_dims(object_mask, 4)
            boxes_delta = coord_mask * (predict_boxes - boxes_tran)
            coord_loss = tf.reduce_mean(
                tf.reduce_sum(tf.square(boxes_delta), axis=[1, 2, 3, 4]),
                name='coord_loss') * self.coord_scale

            tf.losses.add_loss(class_loss)
            tf.losses.add_loss(object_loss)
            tf.losses.add_loss(noobject_loss)
            tf.losses.add_loss(coord_loss)

            tf.summary.scalar('class_loss', class_loss)
            tf.summary.scalar('object_loss', object_loss)
            tf.summary.scalar('noobject_loss', noobject_loss)
            tf.summary.scalar('coord_loss', coord_loss)

            tf.summary.histogram('boxes_delta_x', boxes_delta[..., 0])
            tf.summary.histogram('boxes_delta_y', boxes_delta[..., 1])
            tf.summary.histogram('boxes_delta_w', boxes_delta[..., 2])
            tf.summary.histogram('boxes_delta_h', boxes_delta[..., 3])
            tf.summary.histogram('iou', iou_predict_truth)


def leaky_relu(alpha):
    def op(inputs):
        return tf.nn.leaky_relu(inputs, alpha=alpha, name='leaky_relu')
    return op

5、yolo/config.py

import os

#
# path and dataset parameter
#

DATA_PATH = 'data'

PASCAL_PATH = os.path.join(DATA_PATH, 'pascal_voc')

CACHE_PATH = os.path.join(PASCAL_PATH, 'cache')

OUTPUT_DIR = os.path.join(PASCAL_PATH, 'output')

WEIGHTS_DIR = os.path.join(PASCAL_PATH, 'weights')

# WEIGHTS_FILE = None
WEIGHTS_FILE = os.path.join(DATA_PATH, 'weights', 'YOLO_small.ckpt')

CLASSES = ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus',
           'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse',
           'motorbike', 'person', 'pottedplant', 'sheep', 'sofa',
           'train', 'tvmonitor']

FLIPPED = True


#
# model parameter
#

IMAGE_SIZE = 448

CELL_SIZE = 7

BOXES_PER_CELL = 2

ALPHA = 0.1

DISP_CONSOLE = False

OBJECT_SCALE = 1.0
NOOBJECT_SCALE = 0.5
CLASS_SCALE = 1.0
COORD_SCALE = 5.0


#
# solver parameter
#

GPU = ''

LEARNING_RATE = 0.0001

DECAY_STEPS = 30000

DECAY_RATE = 0.1

STAIRCASE = True

BATCH_SIZE = 45

MAX_ITER = 15000

SUMMARY_ITER = 10

SAVE_ITER = 1000


#
# test parameter
#

THRESHOLD = 0.2

IOU_THRESHOLD = 0.5

四、测试结果展示

1、测试程序参考https://blog.youkuaiyun.com/samylee/article/details/85263143

2、将生成的yolo-1000.data-00000-of-00001及yolo-1000.index文件拷贝到my_model下(参考)

3、修改代码

weight_file = "model/YOLO_small.ckpt"

修改为

weight_file = 'my_model/yolo-1000'

4、训练1000步的效果

五、参考

https://github.com/hizhangp/yolo_tensorflow

 

任何问题请加唯一QQ2258205918(名称samylee)!

或唯一VX:samylee_csdn

YOLO+3D点云融合:从环境搭建到模型训练的完整实践指南 如果你正在寻找一个能让你在毕业设计、学术论文或工程项目中脱颖而出的技术方向,那么“YOLO + 3D点云”这个组合绝对值得你投入时间。它不仅是当前计算机视觉领域的热点,更是自动驾驶、机器人、工业检测等前沿应用的核心技术栈。这篇文章不会空谈概念,而是直接切入核心:这个方向到底能做什么?需要什么硬件门槛?从零开始如何复现一个顶会级的工作思路?看完你就能知道,它是否值得成为你攻克毕设、冲击高区论文的利器。 简单来说,YOLO负责在2D图像中快速、准确地“看到”并框出物体,而3D点云则提供了物体的深度、形状和空间位 阅读详情

相关推荐

YOLO目标检测实战:从环境搭建到模型部署全流程指南

目标检测是计算机视觉的核心任务,旨在识别图像中物体的类别并定位其位置。其核心原理在于通过深度学习模型对图像进行特征提取与回归分析,实现端到端的预测。这项技术的核心价值在于平衡速度与精度,为实时应用提供可能。在众多实现方案中,YOLO(You Only Look Once)系列算法因其独特的单阶段检测架构脱颖而出,它将检测任务重构为单一的回归问题,通过单次前向传播即可输出边界框、置信度及类别信息,显著提升了推理效率。这使得YOLO在自动驾驶、视频监控、工业质检等对实时性要求高的应用场景中成为首选。本文聚焦于Y

weixin_34268579的博客 340

YOLOV1代码实现

YOLO代码实现

DARLINGB的博客 812

踩坑实录:用Mx-yolov3给K210训练模型时,我遇到的5个‘坑’及解决办法

本文详细记录了使用Mx-yolov3为K210训练目标检测模型时遇到的5个常见问题及解决方案,包括环境配置、数据准备、训练报错、模型转换和部署优化。特别强调了版本兼容性、数据预处理技巧和性能优化方法,帮助开发者避开常见陷阱,提升模型训练效率。

weixin_30562507的博客 372

【目标检测】YOLOv1代码实现之TensorFlow

一.代码资源下载:   1.代码下载: https://github.com/hizhangp/yolo_tensorflow   2.打开下载的项目,并新建文件夹data            3.下载pascal VOC 2007 数据集和 small_yolo.ckpt文件,将下载好的文件分别放入data文件夹下的pascal_voc和weight文件夹下。            数据集...

qq_36926037的博客 3382

OpenCV与YOLO实时目标检测:从原理到部署的完整实践指南

这次我们来看一个基于 OpenCV 和 YOLO 的实时目标检测项目。对于计算机视觉入门、课程设计或者毕业设计来说,这是一个非常经典且实用的起点。它不追求最前沿的算法,而是聚焦于“能不能在普通电脑上跑起来”、“代码是否清晰易懂”以及“如何从单张图片检测扩展到实时视频流处理”。如果你正在寻找一个能快速上手、理解原理并看到实际效果的深度学习项目,这篇文章会带你走完全程。 核心就是利用 OpenCV 的 dnn 模块加载预训练的 YOLO 模型,实现对图像和视频中物体的实时检测与框选。整个过程不依赖复杂的深度学习

weixin_26950847的博客 302

算法选型实战:从快速排序到YOLO的全面梳理

在软件开发中,算法并非抽象的考试题目,而是面向特定问题的高效决策规则。理解算法分类与原理,能帮助开发者根据数据规模、实时性和硬件约束做出正确选型。从快速排序、KMP、Dijkstra 等经典基础算法,到动态规划、模拟退火、粒子群等优化策略,再到 PID、FOC 等工程控制算法,以及反向传播、YOLO、强化学习等AI领域方法,每个算法都对应着独特的应用场景与调参要点。掌握这些技术,不仅提升系统性能,更能降低维护成本。从分类视角出发,梳理核心算法的原理、关键参数与常见误区,构建覆盖多领域的算法知识图谱。

weixin_34005090的博客 327

YOLOV1代码逻辑讲解,看我就够了!:读取自己的数据集、训练到测试

YOLOV1代码逻辑看我就够了!

weixin_50557558的博客 1535

YOLOv1-TF2:使用TensorFlow2实现YOLOv1

带有Tensorflow 2的YOLOv1 为了易于实现,我没有实现与纸张完全相同的实现。 下面介绍的内容与本文的实现方式有所不同。 骨干网。 (我使用Xception代替了本文中提到的网络。) 学习率表(我使用了tf.keras.optimizers.schedules.ExponentialDecay ) 超级参数 数据扩充 等等 。 。 。 预览 即将更新。 。 。 使用Docker构建环境 构建Docker映像 $ docker build -t ${NAME} : ${TAG} . 创建一个容器 $ docker run -d -it --gpus all --shm-size= ${PROPER_VALUE} ${NAME} : ${TAG} /bin/bash Pascal VOC数据集() 带有Pascal VOC数据集 图片数量 火车 验证 测试 帕斯卡VOC

【深度学习】YOLO_V1 复现(使用Tensorflow实现)

论文地址:https://arxiv.org/pdf/1506.02640.pdf 本文所包含代码GitHub地址:https://github.com/shankezh/DL_HotNet_Tensorflow 如果对机器学习有兴趣,不仅仅满足将深度学习模型当黑盒模型使用的,想了解为何机器学习可以训练拟合最佳模型,可以看我过往的博客,使用数学知识推导了机器学习中比较经典的案例,并且使用了py...

shankezh的博客 4732

Xilinx FPGA MultiBoot实战:7Series与UltraScale的SPI Fallback配置详解

本文深入解析了Xilinx FPGA的MultiBoot(多启动)与Fallback(回退)功能,针对7系列与UltraScale/UltraScale+系列,提供了从约束文件编写、看门狗集成到镜像烧录的完整实战配置指南。文章重点对比了两个系列在SPI Fallback配置上的关键差异与注意事项,旨在帮助工程师构建高可靠、永不宕机的FPGA系统。

weixin_29197051的博客 547

nvprof:CUDA编程性能分析工具

NVIDIA提供了具有高性能计算的GPU产品,如何检验硬件的实际性能,写出高效的CUDA代码让算力加速卡物尽其用,那可以通过其配套提供的“护城河”软件工具链来检验和帮助发挥GPU的性能:nvprof。如下,对于将8192个A,B向量进行向量加法运算,即C[i] = A[i] + B[i],当软件设置不同的blocknum, threadnum时候,CUDA执行kernel的时间是不同的。比如如下命令,可以看到程序tmp1的总体性能,下面一行是打印gpu的执行时间情况。

Briwisdom的博客 3384

经典卷积模型回顾20—YOLOv1实现猫狗检测(matlab,Tensorflow2.0)

经典卷积模型回顾20—YOLOv1实现猫狗检测(matlab,tensorflow2.0)

爱分享数据的一个人的博客 1302

Cocos Creator 3D音频定位实战:从原理到沉浸感实现

3D音频定位是游戏开发中实现沉浸感的关键技术,其核心原理基于HRTF(头部相关传输函数)模型,模拟声音在空间中的传播与双耳感知差异。这项技术通过赋予声音空间坐标、距离衰减和方向属性,将音频从背景元素提升为可交互的环境线索,极大增强了虚拟世界的真实感与玩家的空间认知。在工程实践中,开发者需要深入理解声源、听者、衰减曲线与多普勒效应等基础概念,并合理运用音频池、优先级管理与动态混音等技术手段来平衡效果与性能。对于Cocos Creator开发者而言,无论是使用引擎原生的AudioSource组件构建轻量级方案,

weixin_30972263的博客 291

TensorFlow-YOLO_V2测试代码梳理

TensorFlow-YOLO_V2测试代码梳理,相信看过后对yolo-v2的整个流程都一目了然了。 硬件:NVIDIA-GTX1080 软件:Windows7、python3.6.5、tensorflow-gpu-1.4.0 一、基础知识 1、anchor boxes (w, h) 通过kmeans聚类方法计算。kmeans计算的是两个数值之间的距离,通过迭代距离最小形成聚类,而目标检...

samylee的博客 2318

【MFRC522】STM32与MFRC522的SPI通信优化及常见问题解析

本文深入解析了STM32与MFRC522进行SPI通信的优化方法与常见问题。从基础的硬件连接到SPI时序优化,再到关键的寄存器配置(如RFCfgReg设为0x4F以提升读卡稳定性),文章提供了详细的调试技巧和实战排坑指南,帮助开发者实现稳定高效的IC读卡应用。

yhn45678901的博客 841

HyperLynx(十五)多板仿真

在对话框的“Design file#1”下拉列表中选择“B00,Mainboard”,在其下方的“Reference designators”列表中选择元器件“J1”,然后在“Designfile#2”下拉列表中选择“BO1、RAM1”,在其下方的“Refer ence designators”列表中选择元器件“J1”,单击“Insert”按钮,为这两个元器件建立互连映射。在“Assign Models”对话框中,选择“B00 Motherboard”,将“U16.B12”设置为输出。

小幽余生不加糖 5060

YOLO_tensorflow常见问题解决:训练过程中的10个典型错误与修复方法

YOLO_tensorflow是一个基于TensorFlow实现的目标检测框架,支持完整的训练和测试流程。本文将为新手用户详细介绍训练过程中可能遇到的10个典型错误及其解决方法,帮助你顺利完成模型训练。 ## 1. 数据加载错误:找不到数据集文件 **错误表现**:训练启动时报错"FileNotFoundError: dataset not found" **修复方法**: - 确保已运行项

gitblog_00190的博客 768

全能媒体工具深度测评(下篇一):AI图文生成+全能解压+图片去文字,一键搞定素材精修

解压50多种格式,文生图,图生图

cs821984831的博客 40

TensorFlow-YOLO_V1测试代码梳理

TensorFlow-YOLO_V1测试代码梳理,相信看过后对yolo-v1的整个流程都一目了然了。 硬件:NVIDIA-GTX1080 软件:Windows7、python3.6.5、tensorflow-gpu-1.4.0 一、基础知识 一图看懂:https://download.youkuaiyun.com/download/samylee/10879852 二、代码展示(做了详细注释) 1...

samylee的博客 1386

ubuntu-25.04-desktop-amd64 工具VMware-tools 正确安装方法

摘要:Ubuntu 25.04虚拟机安装VMware Tools后无法实现主机与虚拟机间的文件复制粘贴和拖拽功能,问题可能源于工具兼容性。建议优先安装open-vm-tools套件(含open-vm-tools-desktop),而非官方VMware Tools。具体方案:通过终端执行安装命令后重启系统。该方法已实测有效解决该兼容性问题。(149字)

安徽锐锋科技-沐雨潇竹 672

【OpenFOAM】-olaFlow-算例6- waveFloatingObject

波浪作用下的浮体的刚体运动,属于流固耦合(FSI)问题

ou_no的博客 3537

TensorFlow-SSD测试代码梳理

TensorFlow-SSD测试代码梳理,相信看过后对SSD的整个流程都一目了然了。 硬件:NVIDIA-GTX1080 软件:Windows7、python3.6.5、tensorflow-gpu-1.4.0 一、基础知识 1、anchor boxes (x, y, w, h) 1.1 x和y 例如38x38特征图输出,则对应原始图像300/38 = 8x8大小,则特征图[0]的x和...

samylee的博客 1745

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

TensorFlow的学习之路--YOLO_V1学习心得

关于YOLO的TensorFlow算法,网上有很多资料,这个大神写的就非常好!https://zhuanlan.zhihu.com/p/24916786?refer=xiaoleimlnote下面梳理下学习过程以及过程中遇到的问题:     首先运行train.py程序后,运行网络框架,网络部分较好理解,输入是[batch_size,448,448,3],输出是[7*7*30]维向量,具体为什么7...

justin_kang的博客 1187

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

yolo v8.3.241:macOS CoreML 推理加速、ExecuTorch 导出稳定性提升与边缘硬件支持扩展

yolo v8.3.241 是一个以后端性能优化与兼容性增强为核心的版本更新,重点围绕 macOS 平台的 ONNX Runtime 推理加速、ExecuTorch 导出的稳定性、边缘硬件支持扩展以及可视化与文档体系的完善展开。该版本在不改变现有 CUDA 行为的前提下,为 Apple 硬件用户和嵌入式部署场景带来了明显收益。代码地址:github.com/ultralytics/ultralyticsyolo v8.3.241 是一次以性能与稳定性为导向的重要更新。

福大大架构师每日一题 1135

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

目标检测-yolo系列

接下来就是Yolo系列 1. YOLO v1 论文:You Only Look Once: Unified, Real-Time Object Detection 代码: 一. Tensorflow: hizhangp/yolo_tensorflow 二. Darknet:darknet/yolo 代码解析:YOLO源码解析 论文解析: 知乎:图解YOLO 优快云:YOLO(You Onl...

暮日落流年的博客 509

我的深度学习之路

学习之路和开篇,介绍了我对深度学习的理解认识以及学习笔记系列文章的目录和链接。

小鼻涕虫BoZ的博客 897
上一篇: TensorFlow-YOLO_V1测试代码梳理
下一篇: TensorFlow-YOLO_V2测试代码梳理
samylee
博客等级 码龄11年 585粉丝 92原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值