[Технический блог] Введение в алгоритм обнаружения целей R-CNN

искусственный интеллект

Введение в алгоритм обнаружения целей R-CNN

Автор: Гао Ючжо.

Введение в обнаружение объектов

Задача Object Detection — найти все интересующие объекты (объекты) на изображении и определить их категорию и местонахождение. Существует четыре основных категории задач по распознаванию изображений в компьютерном зрении: 1. Классификация-Классификация: Решить задачу "что?", то есть по данной картинке или видео определить какая категория мишеней в ней содержится. 2. Location-Location: Решить задачу «где?», то есть определить местонахождение цели. 3. Обнаружение-обнаружение: решить проблему "что? где?", то есть определить местоположение цели и узнать, что это за цель. 4. Сегментация-сегментация: она разделена на сегментацию экземпляра (уровень экземпляра) и сегментацию сцены (уровень сцены) для решения проблемы «какой цели или сцены принадлежит каждый пиксель».OD.png

Классификация существующих алгоритмов обнаружения объектов

1.Двухэтапный алгоритм обнаружения целей Во-первых, сначала создается предложение области (RP) (поле предварительного выбора, которое может содержать объекты для проверки), а затем образцы классифицируются с помощью сверточной нейронной сети. Задача: Извлечение признаков -> Генерация RP -> Регрессия классификации/местоположения. Распространенными двухэтапными алгоритмами обнаружения целей являются: R-CNN, SPP-Net, Fast R-CNN, Faster R-CNN и R-FCN и т. д.

2. Одноэтапный алгоритм обнаружения цели Без RP функции извлекаются непосредственно в сети для прогнозирования классификации и местоположения объектов. Задача: Извлечение признаков -> Регрессия классификации/местоположения. Распространенными одноэтапными алгоритмами обнаружения целей являются: OverFeat, YOLOv1, YOLOv2, YOLOv3, SSD и RetinaNet.

Эта статья представит классические алгоритмы в следующих разделах.R-CNNИ дайте соответствующую реализацию кода.

R-CNN

R-CNN (Области с функциями CNN) является важной вехой в применении методов CNN к задачам обнаружения объектов. Благодаря хорошей производительности извлечения признаков и классификации CNN преобразование проблемы обнаружения целей реализуется с помощью метода RegionProposal.Алгоритм разбит на четыре шага:

  1. Создайте область-кандидат из исходного изображения (предложение RoI)
  2. Введите регион-кандидат в CNN для извлечения признаков
  3. Отправьте функцию в детектор SVM каждой категории, чтобы определить, принадлежит ли она этой категории.
  4. Точная целевая область получается регрессией границ

Прямая схема алгоритма выглядит следующим образом (цифры на рисунке соответствуют указанным выше четырем шагам):RCNN_.pngДалее мы также объясним порядок вышеупомянутых четырех шагов.Построение модели, после чего мы объясним, как это сделатьобучение модели. Но прежде чем мы начнем с описанных выше особенностей, давайте кратко разберемся с набором данных, который мы будем использовать в обучении.

Введение в наборы данных

Наборы данных, использованные в исходной статье: 1. ImageNet ILSVC (большая библиотека распознавания)10 миллионов изображений, 1000 категорий.2. PASCAL VOC 2007 (меньшая библиотека обнаружения)Десять тысяч изображений, 20 категорий.Во время обучения используйте библиотеку распознавания для предварительного обучения, а затем используйте библиотеку обнаружения для настройки параметров и оценки влияния модели на библиотеку обнаружения.

Из-за большой емкости исходного набора данных время обучения модели может достигать десятков часов. Для упрощения обучения мы заменили обучающий набор данных. Как и в оригинальной статье, данные, которые мы используем, состоят из двух частей: 1. Содержит17 категорийцветочные картинки 2. Содержит2 категориицветочная картинка.

Мы будем использовать данные 17 категорий для предварительной подготовки модели в будущем, использовать данные 2 категорий для точной настройки, чтобы получить окончательную модель прогнозирования, и оценить ее на изображении 2 категорий.

Построение модели

шаг первый

Часть потока алгоритма, которую мы хотим завершить на этом шаге, отмечена цифрами, как показано на рисунке ниже:RCNN_1.pngиспользуется в R-CNNалгоритм выборочного поискавыполнятьregion proposal. Алгоритм сначала инициализирует исходную область с помощью метода сегментации изображения на основе графа, то есть делит изображение на множество мелких частей. Затем используйте жадную стратегию, чтобы вычислить сходство каждых двух соседних регионов, а затем каждый раз объединяйте два наиболее похожих блока, пока не останется только одно полное изображение. и сохраните блоки изображения, включая объединенные блоки изображения, генерируемые каждый раз в процессе, как окончательныйНабор RoI (область интереса). Подробный алгоритм алгоритма выглядит следующим образом:selective_search.pngДля слияния областей используются различные стратегии. Если просто принять одну стратегию, легко по ошибке объединить разнородные области. Например, когда рассматривается только текстура, области разных цветов легко объединяются по ошибке. Выборочный поиск использует три стратегии разнообразия для увеличения количества областей-кандидатов, чтобы гарантировать отзыв:

  • Несколько цветовых пространств с учетом RGB, оттенков серого, HSV и их вариантов
  • Множественные показатели сходства, учитывающие как цветовое сходство, текстуру, размер, перекрытие и т. д.
  • Инициализируйте исходную область, изменив порог, чем больше порог, тем меньше разделенная область.

Многие платформы машинного обучения имеют встроенные реализации операций выборочного поиска.

Шаг 2

Часть потока алгоритма, которую мы хотим завершить на этом шаге, отмечена цифрами, как показано на рисунке ниже:RCNN_2.pngНа шаге 1 получаемалгоритм выборочного поискаСгенерированоregion proposals, но размер каждого предложения в основном непостоянен, учитывая, чтоregion proposalsвпоследствии вступить вConvNetизвлечение признаков вregion proposalsСкорректирован на единообразный и последовательныйConvNetСтандартные размеры архитектуры. Соответствующий код реализован следующим образом:

import matplotlib.patches as mpatches
# Clip Image
def clip_pic(img, rect):
    x = rect[0]
    y = rect[1]
    w = rect[2]
    h = rect[3]
    x_1 = x + w
    y_1 = y + h
    # return img[x:x_1, y:y_1, :], [x, y, x_1, y_1, w, h]   
    return img[y:y_1, x:x_1, :], [x, y, x_1, y_1, w, h]

#Resize Image
def resize_image(in_image, new_width, new_height, out_image=None, resize_mode=cv2.INTER_CUBIC):
    img = cv2.resize(in_image, (new_width, new_height), resize_mode)
    if out_image:
        cv2.imwrite(out_image, img)
    return img

def image_proposal(img_path):
    img = cv2.imread(img_path)
    img_lbl, regions = selective_search(
                       img, scale=500, sigma=0.9, min_size=10)
    candidates = set()
    images = []
    vertices = []
    for r in regions:
        # excluding same rectangle (with different segments)
        if r['rect'] in candidates:
            continue
        # excluding small regions
        if r['size'] < 220:
            continue
        if (r['rect'][2] * r['rect'][3]) < 500:
            continue
        # resize to 227 * 227 for input
        proposal_img, proposal_vertice = clip_pic(img, r['rect'])
        # Delete Empty array
        if len(proposal_img) == 0:
            continue
        # Ignore things contain 0 or not C contiguous array
        x, y, w, h = r['rect']
        if w == 0 or h == 0:
            continue
        # Check if any 0-dimension exist
        [a, b, c] = np.shape(proposal_img)
        if a == 0 or b == 0 or c == 0:
            continue
        resized_proposal_img = resize_image(proposal_img,224, 224)
        candidates.add(r['rect'])
        img_float = np.asarray(resized_proposal_img, dtype="float32")
        images.append(img_float)
        vertices.append(r['rect'])
    return images, vertices

Выберем картинку, чтобы проверить действие алгоритма выборочного поиска

img_path = './17flowers/jpg/7/image_0591.jpg' 
imgs, verts = image_proposal(img_path)
fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
img = skimage.io.imread(img_path)
ax.imshow(img)
for x, y, w, h in verts:
    rect = mpatches.Rectangle((x, y), w, h, fill=False, edgecolor='red', linewidth=1)
    ax.add_patch(rect)
plt.show()

1.pngполучить единый размерproposals, его можно ввести вConvNetВыполните извлечение признаков. мы тутConvNetИспользуемая модель сетевой архитектурыAlexNet. Конкретная структура его сети выглядит следующим образом:

import tflearn
from tflearn.layers.core import input_data, dropout, fully_connected
from tflearn.layers.conv import conv_2d, max_pool_2d
from tflearn.layers.normalization import local_response_normalization
from tflearn.layers.estimator import regression

# Building 'AlexNet'
def create_alexnet(num_classes, restore = True):
    # Building 'AlexNet'
    network = input_data(shape=[None, 224, 224, 3])
    network = conv_2d(network, 96, 11, strides=4, activation='relu')
    network = max_pool_2d(network, 3, strides=2)
    network = local_response_normalization(network)
    network = conv_2d(network, 256, 5, activation='relu')
    network = max_pool_2d(network, 3, strides=2)
    network = local_response_normalization(network)
    network = conv_2d(network, 384, 3, activation='relu')
    network = conv_2d(network, 384, 3, activation='relu')
    network = conv_2d(network, 256, 3, activation='relu')
    network = max_pool_2d(network, 3, strides=2)
    network = local_response_normalization(network)
    network = fully_connected(network, 4096, activation='tanh')
    network = dropout(network, 0.5)
    network = fully_connected(network, 4096, activation='tanh')
    network = dropout(network, 0.5)
    network = fully_connected(network, num_classes, activation='softmax', restore=restore)
    network = regression(network, optimizer='momentum',
                         loss='categorical_crossentropy',
                         learning_rate=0.001)
    return network

Пока мы закончилиConvNetчасть архитектуры, черезConvNetМы можемproposalизвлечено вfeature map.

Шаг третий и четвертый

Часть потока алгоритма, которую мы хотим завершить на этом шаге, отмечена цифрами, как показано на рисунке ниже:image.pngполучить каждыйproposalизвлеченный изfeature mapПосле этого мы можем ввести его вSVMs(Стоит отметить, что количество SVM-классификаторов не является уникальным. Нам нужно обучить SVM для каждой категории классификации. В соответствии с нашим набором данных последняя категория цветов, которую нужно классифицировать, — это две категории, поэтому на данный момент наше количество SVM для 2) вклассификационная дискриминация. Для выделенных выше положительных примеров (нефоновых)proposalпоследующий ввод вBbox regточно настройте bbox в и выведите окончательный прогноз ограничивающей рамки. Теперь, когда мы знаем весь поток алгоритма, давайте перейдем к обучению модели.

обучение модели

Обучение модели R-CNN делится на два этапа:

  1. инициализацияConvNetи предварительно обученный с большим набором данныхпредварительно обученная модель,существуетпредварительно обученная модельтонкая настройка с небольшим набором данных и получение окончательногоConvNet.
  2. Вводим картинку в модель, через полученную на первом шагеConvNetИзвлечь каждое предложениеfeature map,использоватьfeature mapтренировать нашихSVM классификатораиРегрессор Bbox reg. (ПроцессConvNetне участвует в обучении, т.е.ConvNetизпараметры остаются прежними)

Сначала на больших наборах данныхпредварительная подготовка, ОбучениеВведите Хзаисходное изображение,правильная метка YзаКлассификация исходного изображения. Соответствующий код выглядит следующим образом:

import codecs

def load_data(datafile, num_class, save=False, save_path='dataset.pkl'):
    fr = codecs.open(datafile, 'r', 'utf-8')
    train_list = fr.readlines()
    labels = []
    images = []
    for line in train_list:
        tmp = line.strip().split(' ')
        fpath = tmp[0]
        img = cv2.imread(fpath)
        img = resize_image(img, 224, 224)
        np_img = np.asarray(img, dtype="float32")
        images.append(np_img)

        index = int(tmp[1])
        label = np.zeros(num_class)
        label[index] = 1
        labels.append(label)
    if save:
        pickle.dump((images, labels), open(save_path, 'wb'))
    fr.close()
    return images, labels

def train(network, X, Y, save_model_path):
    # Training
    model = tflearn.DNN(network, checkpoint_path='model_alexnet',
                        max_checkpoints=1, tensorboard_verbose=2, tensorboard_dir='output')
    if os.path.isfile(save_model_path + '.index'):
        model.load(save_model_path)
        print('load model...')
    for _ in range(5):
        model.fit(X, Y, n_epoch=1, validation_set=0.1, shuffle=True,
                  show_metric=True, batch_size=64, snapshot_step=200,
                  snapshot_epoch=False, run_id='alexnet_oxflowers17') # epoch = 1000
        # Save the model
        model.save(save_model_path)
        print('save model...')
        
X, Y = load_data('./train_list.txt', 17)
net = create_alexnet(17)
train(net, X, Y,'./pre_train_model/model_save.model')

позжепредварительно обученная модельвыше, используя тонкую настройку небольшого набора данных. Эта часть метода обучения отличается от предыдущей двумя моментами: 1.входитьСоздано с использованием предложения регионаRoIвместо исходного изображения. 2. Для каждой области интересаправильная метка Y, мы вычисляем разницу между RoI и наземной правдой (метка диапазона объекта обнаружения, отмеченная исходным изображением)IOU (пересечение над союзом)Чтобы убедиться. IoUРасчетКак показано ниже:

image.pngМожно видеть, что значение IoU равно ∈ [0,1], и чем больше значение, тем меньше разрыв между RoI и истинной реальностью. Области-кандидаты с IoU больше 0,5 определяются как положительные образцы, а остальные — как отрицательные образцы. Код для расчета IoU выглядит следующим образом:

# IOU Part 1
def if_intersection(xmin_a, xmax_a, ymin_a, ymax_a, xmin_b, xmax_b, ymin_b, ymax_b):
    if_intersect = False
    if xmin_a < xmax_b <= xmax_a and (ymin_a < ymax_b <= ymax_a or ymin_a <= ymin_b < ymax_a):
        if_intersect = True
    elif xmin_a <= xmin_b < xmax_a and (ymin_a < ymax_b <= ymax_a or ymin_a <= ymin_b < ymax_a):
        if_intersect = True
    elif xmin_b < xmax_a <= xmax_b and (ymin_b < ymax_a <= ymax_b or ymin_b <= ymin_a < ymax_b):
        if_intersect = True
    elif xmin_b <= xmin_a < xmax_b and (ymin_b < ymax_a <= ymax_b or ymin_b <= ymin_a < ymax_b):
        if_intersect = True
    else:
        return if_intersect
    if if_intersect:
        x_sorted_list = sorted([xmin_a, xmax_a, xmin_b, xmax_b])
        y_sorted_list = sorted([ymin_a, ymax_a, ymin_b, ymax_b])
        x_intersect_w = x_sorted_list[2] - x_sorted_list[1]
        y_intersect_h = y_sorted_list[2] - y_sorted_list[1]
        area_inter = x_intersect_w * y_intersect_h
        return area_inter


# IOU Part 2
def IOU(ver1, vertice2):
    # vertices in four points
    vertice1 = [ver1[0], ver1[1], ver1[0]+ver1[2], ver1[1]+ver1[3]]
    area_inter = if_intersection(vertice1[0], vertice1[2], vertice1[1], vertice1[3], vertice2[0], vertice2[2], vertice2[1], vertice2[3])
    if area_inter:
        area_1 = ver1[2] * ver1[3]
        area_2 = vertice2[4] * vertice2[5]
        iou = float(area_inter) / (area_1 + area_2 - area_inter)
        return iou
    return False

Перед использованием небольшого набора данных для тонкой настройки, давайте завершим чтение соответствующих данных тренировок (этикетки набора ROI, соответствующие изображения, метки кадров и т. Д.). В следующем коде мы читаем и сохраняем его для SVM тренировка. и целевые данные регрессии.

# Read in data and save data for Alexnet
def load_train_proposals(datafile, num_clss, save_path, threshold=0.5, is_svm=False, save=False):
    fr = open(datafile, 'r')
    train_list = fr.readlines()
    # random.shuffle(train_list)
    for num, line in enumerate(train_list):
        labels = []
        images = []
        rects = []
        tmp = line.strip().split(' ')
        # tmp0 = image address
        # tmp1 = label
        # tmp2 = rectangle vertices
        img = cv2.imread(tmp[0])
        # 选择搜索得到候选框
        img_lbl, regions = selective_search(
                               img, scale=500, sigma=0.9, min_size=10)
        candidates = set()
        ref_rect = tmp[2].split(',')
        ref_rect_int = [int(i) for i in ref_rect]
        Gx = ref_rect_int[0]
        Gy = ref_rect_int[1]
        Gw = ref_rect_int[2]
        Gh = ref_rect_int[3]
        for r in regions:
            # excluding same rectangle (with different segments)
            if r['rect'] in candidates:
                continue
            # excluding small regions
            if r['size'] < 220:
                continue
            if (r['rect'][2] * r['rect'][3]) < 500:
                continue
            # 截取目标区域
            proposal_img, proposal_vertice = clip_pic(img, r['rect'])
            # Delete Empty array
            if len(proposal_img) == 0:
                continue
            # Ignore things contain 0 or not C contiguous array
            x, y, w, h = r['rect']
            if w == 0 or h == 0:
                continue
            # Check if any 0-dimension exist
            [a, b, c] = np.shape(proposal_img)
            if a == 0 or b == 0 or c == 0:
                continue
            resized_proposal_img = resize_image(proposal_img, 224, 224)
            candidates.add(r['rect'])
            img_float = np.asarray(resized_proposal_img, dtype="float32")
            images.append(img_float)
            # IOU
            iou_val = IOU(ref_rect_int, proposal_vertice)
            # x,y,w,h作差,用于boundingbox回归
            rects.append([(Gx-x)/w, (Gy-y)/h, math.log(Gw/w), math.log(Gh/h)])
            # propasal_rect = [proposal_vertice[0], proposal_vertice[1], proposal_vertice[4], proposal_vertice[5]]
            # print(iou_val)
            # labels, let 0 represent default class, which is background
            index = int(tmp[1])
            if is_svm:
                # iou小于阈值,为背景,0
                if iou_val < threshold:
                    labels.append(0)
                else:
                     labels.append(index)
            else:
                label = np.zeros(num_clss + 1)
                if iou_val < threshold:
                    label[0] = 1
                else:
                    label[index] = 1
                labels.append(label)


        if is_svm:
            ref_img, ref_vertice = clip_pic(img, ref_rect_int)
            resized_ref_img = resize_image(ref_img, 224, 224)
            img_float = np.asarray(resized_ref_img, dtype="float32")
            images.append(img_float)
            rects.append([0, 0, 0, 0])
            labels.append(index)
        view_bar("processing image of %s" % datafile.split('\\')[-1].strip(), num + 1, len(train_list))

        if save:
            if is_svm:
                # strip()去除首位空格
                np.save((os.path.join(save_path, tmp[0].split('/')[-1].split('.')[0].strip()) + '_data.npy'), [images, labels, rects])
            else:
                # strip()去除首位空格
                np.save((os.path.join(save_path, tmp[0].split('/')[-1].split('.')[0].strip()) + '_data.npy'),
                        [images, labels])
    print(' ')
    fr.close()
    
# load data
def load_from_npy(data_set):
    images, labels = [], []
    data_list = os.listdir(data_set)
    # random.shuffle(data_list)
    for ind, d in enumerate(data_list):
        i, l = np.load(os.path.join(data_set, d),allow_pickle=True)
        images.extend(i)
        labels.extend(l)
        view_bar("load data of %s" % d, ind + 1, len(data_list))
    print(' ')
    return images, labels

import math
import sys
#Progress bar 
def view_bar(message, num, total):
    rate = num / total
    rate_num = int(rate * 40)
    rate_nums = math.ceil(rate * 100)
    r = '\r%s:[%s%s]%d%%\t%d/%d' % (message, ">" * rate_num, " " * (40 - rate_num), rate_nums, num, total,)
    sys.stdout.write(r)
    sys.stdout.flush()

Когда приведенная выше модель готова, мы можем начать этап тонкой настройки обучения, соответствующий код выглядит следующим образом:

def fine_tune_Alexnet(network, X, Y, save_model_path, fine_tune_model_path):
    # Training
    model = tflearn.DNN(network, checkpoint_path='rcnn_model_alexnet',
                        max_checkpoints=1, tensorboard_verbose=2, tensorboard_dir='output_RCNN')
    if os.path.isfile(fine_tune_model_path + '.index'):
        print("Loading the fine tuned model")
        model.load(fine_tune_model_path)
    elif os.path.isfile(save_model_path + '.index'):
        print("Loading the alexnet")
        model.load(save_model_path)
    else:
        print("No file to load, error")
        return False

    model.fit(X, Y, n_epoch=1, validation_set=0.1, shuffle=True,
              show_metric=True, batch_size=64, snapshot_step=200,
              snapshot_epoch=False, run_id='alexnet_rcnnflowers2')
    # Save the model
    model.save(fine_tune_model_path)
        
data_set = './data_set'
if len(os.listdir('./data_set')) == 0:
    print("Reading Data")
    load_train_proposals('./fine_tune_list.txt', 2, save=True, save_path=data_set)
print("Loading Data")
X, Y = load_from_npy(data_set)
restore = False
if os.path.isfile('./fine_tune_model/fine_tune_model_save.model' + '.index'):
    restore = True
    print("Continue fine-tune")
# three classes include background
net = create_alexnet(3, restore=restore)
fine_tune_Alexnet(net, X, Y, './pre_train_model/model_save.model', './fine_tune_model/fine_tune_model_save.model')

Шаг 2

На этом этапе мы будем тренироватьсяSVMsиBbox regПронумеровано, как показано ниже:image.pngВо-первых, мы извлекаем карту функций из модели CNN, используемой здесь на шаге 1, обратите внимание на ту, которая используется здесь.ConvNetПо сравнению с предыдущим обучением последний слой softmax отсутствует, потому что в настоящее время нам нужны функции, извлеченные из RoI, а слой softmax требуется в обучении для классификации. Соответствующий код выглядит следующим образом:

def create_alexnet():
    # Building 'AlexNet'
    network = input_data(shape=[None, 224, 224, 3])
    network = conv_2d(network, 96, 11, strides=4, activation='relu')
    network = max_pool_2d(network, 3, strides=2)
    network = local_response_normalization(network)
    network = conv_2d(network, 256, 5, activation='relu')
    network = max_pool_2d(network, 3, strides=2)
    network = local_response_normalization(network)
    network = conv_2d(network, 384, 3, activation='relu')
    network = conv_2d(network, 384, 3, activation='relu')
    network = conv_2d(network, 256, 3, activation='relu')
    network = max_pool_2d(network, 3, strides=2)
    network = local_response_normalization(network)
    network = fully_connected(network, 4096, activation='tanh')
    network = dropout(network, 0.5)
    network = fully_connected(network, 4096, activation='tanh')
    network = regression(network, optimizer='momentum',
                         loss='categorical_crossentropy',
                         learning_rate=0.001)
    return network

Нам нужно обучить SVM для каждой категории классификации. Что мы собираемся в конечном итоге классифицироватьКатегории цветов две категории, поэтому нам нужно обучитьКоличество SVM - 2. используется для обучения SVMвходитьИзвлечено из RoIfeature map, используемые метки имеют в общей сложностиn+1 категории (+1 фон), что соответствует нашему набору данных на данный момент, метки имеют в общей сложноститри категории. Соответствующий код выглядит следующим образом:

from sklearn import svm
from sklearn.externals import joblib

# Construct cascade svms
def train_svms(train_file_folder, model):
    files = os.listdir(train_file_folder)
    svms = []
    train_features = []
    bbox_train_features = []
    rects = []
    for train_file in files:
        if train_file.split('.')[-1] == 'txt':
            X, Y, R = generate_single_svm_train(os.path.join(train_file_folder, train_file))
            Y1 = []
            features1 = []
            features_hard = []
            for ind, i in enumerate(X):
                # extract features 提取特征
                feats = model.predict([i])
                train_features.append(feats[0])
                # 所有正负样本加入feature1,Y1
                if Y[ind]>=0:
                    Y1.append(Y[ind])
                    features1.append(feats[0])
                    # 对与groundtruth的iou>0.5的加入boundingbox训练集
                    if Y[ind]>0:
                        bbox_train_features.append(feats[0])
                view_bar("extract features of %s" % train_file, ind + 1, len(X))

            clf = svm.SVC(probability=True)

            clf.fit(features1, Y1)
            print(' ')
            print("feature dimension")
            print(np.shape(features1))
            svms.append(clf)
            # 将clf序列化,保存svm分类器
            joblib.dump(clf, os.path.join(train_file_folder, str(train_file.split('.')[0]) + '_svm.pkl'))

    # 保存boundingbox回归训练集
    np.save((os.path.join(train_file_folder, 'bbox_train.npy')),
            [bbox_train_features, rects])
    return svms

# Load training images
def generate_single_svm_train(train_file):
    save_path = train_file.rsplit('.', 1)[0].strip()
    if len(os.listdir(save_path)) == 0:
        print("reading %s's svm dataset" % train_file.split('\\')[-1])
        load_train_proposals(train_file, 2, save_path, threshold=0.3, is_svm=True, save=True)
    print("restoring svm dataset")
    images, labels,rects = load_from_npy_(save_path)

    return images, labels,rects

# load data
def load_from_npy_(data_set):
    images, labels ,rects= [], [], []
    data_list = os.listdir(data_set)
    # random.shuffle(data_list)
    for ind, d in enumerate(data_list):
        i, l, r = np.load(os.path.join(data_set, d),allow_pickle=True)
        images.extend(i)
        labels.extend(l)
        rects.extend(r)
        view_bar("load data of %s" % d, ind + 1, len(data_list))
    print(' ')
    return images, labels ,rects

Регрессор является линейным, и входными данными являются N пар значений, {(??,??)}?=1,2,…,?{(Pi,Gi)}i=1,2,…,N, которые являются области-кандидаты, соответственно, координаты блока и истинные координаты блока. Соответствующий код выглядит следующим образом:

from sklearn.linear_model import Ridge

#在图片上显示boundingbox
def show_rect(img_path, regions):
    fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
    img = skimage.io.imread(img_path)
    ax.imshow(img)
    for x, y, w, h in regions:
        rect = mpatches.Rectangle(
            (x, y), w, h, fill=False, edgecolor='red', linewidth=1)
        ax.add_patch(rect)
    plt.show()
    

# 训练boundingbox回归
def train_bbox(npy_path):
    features, rects = np.load((os.path.join(npy_path, 'bbox_train.npy')),allow_pickle=True)
    # 不能直接np.array(),应该把元素全部取出放入空列表中。因为features和rects建立时用的append,导致其中元素结构不能直接转换成矩阵
    X = []
    Y = []
    for ind, i in enumerate(features):
        X.append(i)
    X_train = np.array(X)

    for ind, i in enumerate(rects):
        Y.append(i)
    Y_train = np.array(Y)

    # 线性回归模型训练
    clf = Ridge(alpha=1.0)
    clf.fit(X_train, Y_train)
    # 序列化,保存bbox回归
    joblib.dump(clf, os.path.join(npy_path,'bbox_train.pkl'))
    return clf

Начните обучение классификатора SVM и регрессора коробки.

train_file_folder = './svm_train'
# 建立模型,网络
net = create_alexnet()
model = tflearn.DNN(net)
# 加载微调后的alexnet网络参数
model.load('./fine_tune_model/fine_tune_model_save.model')
# 加载/训练svm分类器 和 boundingbox回归器
svms = []
bbox_fit = []
# boundingbox回归器是否有存档
bbox_fit_exit = 0
# 加载svm分类器和boundingbox回归器
for file in os.listdir(train_file_folder):
    if file.split('_')[-1] == 'svm.pkl':
        svms.append(joblib.load(os.path.join(train_file_folder, file)))
    if file == 'bbox_train.pkl':
        bbox_fit = joblib.load(os.path.join(train_file_folder, file))
        bbox_fit_exit = 1
if len(svms) == 0:
    svms = train_svms(train_file_folder, model)
if bbox_fit_exit == 0:
    bbox_fit = train_bbox(train_file_folder)

print("Done fitting svms")

Теперь модель обучена.

Представление эффекта модели

Давайте выберем изображение и следуем порядку прямого распространения модели, чтобы увидеть, как модель работает. Сначала посмотрите на область рентабельности инвестиций, созданную предложением региона.

img_path = './2flowers/jpg/1/image_1282.jpg'  
image = cv2.imread(img_path)
im_width = image.shape[1]
im_height = image.shape[0]
# 提取region proposal
imgs, verts = image_proposal(img_path)
show_rect(img_path, verts)

2.pngRoI вводится в ConvNet для получения функций и ввода в SVM и регрессор, а образцы с положительными результатами классификации SVM выбираются для регрессии ограничивающей рамки.

# 从CNN中提取RoI的特征
features = model.predict(imgs)
print("predict image:")
# print(np.shape(features))
results = []
results_label = []
results_score = []
count = 0
print(len(features))
for f in features:
    for svm in svms:
        pred = svm.predict([f.tolist()])
        # not background
        if pred[0] != 0:
            # boundingbox回归
            bbox = bbox_fit.predict([f.tolist()])
            tx, ty, tw, th = bbox[0][0], bbox[0][1], bbox[0][2], bbox[0][3]
            px, py, pw, ph = verts[count]
            gx = tx * pw + px
            gy = ty * ph + py
            gw = math.exp(tw) * pw
            gh = math.exp(th) * ph
            if gx < 0:
                gw = gw - (0 - gx)
                gx = 0
            if gx + gw > im_width:
                gw = im_width - gx
            if gy < 0:
                gh = gh - (0 - gh)
                gy = 0
            if gy + gh > im_height:
                gh = im_height - gy
            results.append([gx, gy, gw, gh])
            results_label.append(pred[0])
            results_score.append(svm.predict_proba([f.tolist()])[0][1])
    count += 1
print(results)
print(results_label)
print(results_score)
show_rect(img_path, results)

3.pngВидно, что может быть более одного кадра, и нам нужно использовать NMS (Non-Maximum Suppression) для выбора относительно оптимального результата. код показывает, как показано ниже:

results_final = []
results_final_label = []

# 非极大抑制
# 删除得分小于0.5的候选框
delete_index1 = []
for ind in range(len(results_score)):
    if results_score[ind] < 0.5:
        delete_index1.append(ind)
num1 = 0
for idx in delete_index1:
    results.pop(idx - num1)
    results_score.pop(idx - num1)
    results_label.pop(idx - num1)
    num1 += 1

while len(results) > 0:
    # 找到列表中得分最高的
    max_index = results_score.index(max(results_score))
    max_x, max_y, max_w, max_h = results[max_index]
    max_vertice = [max_x, max_y, max_x + max_w, max_y + max_h, max_w, max_h]
    # 该候选框加入最终结果
    results_final.append(results[max_index])
    results_final_label.append(results_label[max_index])
    # 从results中删除该候选框
    results.pop(max_index)
    results_label.pop(max_index)
    results_score.pop(max_index)
    # print(len(results_score))
    # 删除与得分最高候选框iou>0.5的其他候选框
    delete_index = []
    for ind, i in enumerate(results):
        iou_val = IOU(i, max_vertice)
        if iou_val > 0.5:
            delete_index.append(ind)
    num = 0
    for idx in delete_index:
        # print('\n')
        # print(idx)
        # print(len(results))
        results.pop(idx - num)
        results_score.pop(idx - num)
        results_label.pop(idx - num)
        num += 1

print("result:",results_final)
print("result label:",results_final_label)
show_rect(img_path, results_final)

image.png

Суммировать

Пока у нас есть грубая модель R-CNN. R-CNN гибко использовала более совершенные на тот момент инструменты и технологии, полностью впитала их, трансформировала в соответствии со своей логикой и, наконец, добилась большого прогресса. Но есть и очевидные недостатки:

  1. Обучение слишком громоздкое: тонкая настройка сети + обучение SVM + регрессия ограничивающей рамки, которая будет включать в себя множество неэффективных операций чтения и записи на жесткий диск.
  2. Каждая область интереса должна проходить через сеть CNN для извлечения признаков, что приводит к множеству дополнительных операций (представьте себе две области интереса с перекрывающимися частями, перекрывающиеся части эквивалентны двум операциям свертки, но теоретически их нужно выполнить только один раз).
  3. Скорость работы низкая, например, независимое извлечение признаков, использование выборочного поиска в качестве предложения по региону и т. д. требуют слишком много времени.

К счастью, эти проблемы были значительно решены в последующих версиях Fast R-CNN и Faster R-CNN.

адрес проекта

Тихо потяните Can /workspace/5…