場景描述
概述
息壤·科研助手是一款適用于高校科研使用場景的一站式科研實訓平臺,可調度各種類型的計算資源,支持一鍵部署、隨時隨地、無需配置開箱即用。科研助手支持用戶實現AI教學場景的模型訓練、推理、調優等。
實踐內容
鋼筋是建筑業的重要材料,龐大的數量、工地現場環境復雜以及人工點驗錯漏等現實因素為鋼筋點驗工作制造了難度,那么如何才能快速且準確地完成對于整個建筑施工過程極為重要的鋼筋點驗工作環節呢?本次實踐內容為“AI數鋼筋”——通過人工智能技術實現鋼筋數量統計。所謂“AI數鋼筋”就是,通過多目標檢測機器視覺方法以實現鋼筋數量智能統計,達到提高勞動效率和鋼筋數量統計精確性的效果。目標檢測算法通過與攝像頭結合,可以實現自動鋼筋計數,再結合人工修改少量誤檢的方式,可以智能、高效地完成鋼筋計數任務。
步驟簡介
教程包括如下步驟:
- 應用商城選購:在科研助手的應用商城中選購自己所需的教學與實踐鏡像
- 創建開發機:購買開發機,選擇所需的資源規格
- 訪問開發機并開始實踐:在開發機中進行相應的教學與實踐
- 獲取結果:將實踐生成的內容保存至本地
實踐步驟
應用商城選購
步驟1:進入科研助手控制臺,點擊【找應用】:
步驟2:在【找應用】中篩選【教學與實踐】類型:
步驟3:選擇自己所需的教學與實踐內容,我們以“鋼筋計數模型訓練教學”為例進行實操。
創建開發機
步驟1:選擇應用后,會跳轉至【創建開發機】頁面。
在購買過程中,
【主機規格】請按需選擇
【存儲配置】-【科研文件】如有外部持久化需求,請按需選擇
【主機規格】本次案例需要GPU資源,請選擇GPU卡,
推薦配置:
廈門4 gn3.m1.12gb CPU: 10核 內存: 20GB
揚州7 gn3.m1.12gb CPU: 10核 內存: 20GB
【鏡像框架】默認已選擇,無需修改
步驟2:點擊【確認訂單】,完成開發機創建并啟動。
登錄開發機
步驟1:購買完成后,可以看見開發機狀態顯示為【啟動中】,等待新創建的開發機狀態進入到【運行中】,然后點擊右側操作欄【打開】;
步驟2:點擊【打開】跳轉到開發機,在Jupyter主頁面中默認打開了教學與實踐的內容,雙擊左側已內置的notebook【鋼筋計數模型訓練.ipynb】打開:
教學與實踐
實踐說明:
步驟1:在教學與實踐中,我們將每個操作做了一一的拆解和注釋,在每一個步驟中,又分為三個子步驟:
子步驟1:選擇對應的代碼塊
子步驟2:點擊箭頭,將運行選定的代碼塊中的內容。提示:代碼塊運行的狀態會持續存在,例如定義了一個變量,他再后續的代碼塊中執行也是持續存在的。
子步驟3:左側的[ ]符號中會顯示執行的狀態,空為未執行,“*”為執行中,數字為已完成執行后的編號。
步驟2:等待第一個代碼塊執行完成后,其他步驟以此類推,將該教學與實踐中的剩余代碼塊按順序執行完成.
您也可以點擊如下雙箭頭按鈕,讓程序一次性運行;
依次執行后,可以看到第九個代碼塊正在執行模型訓練,我們可以實時查看它的訓練進度。
步驟3:完成模型訓練后,可在頁面左側的文件瀏覽器中下載生成的模型到本地電腦
模型保存在目錄:/home/rebar_count/model_snapshots
以下是對教學內容的任務解析:
任務一:環境與數據準備
1、將本實踐中所需要的鋼筋數據集下載
輸入:
import os
if not os.path.exists('./rebar_count'):
? ?print('Downloading code and datasets...')
? ?os.system("wget -N -nv //jiangsu-10.zos.daliqc.cn/bucket-7262/rebar_count.zip")
? ?os.system("unzip rebar_count.zip;")
? ?if os.path.exists('./rebar_count'):
? ? ? ?print('Download code and datasets success')
? ?else:
? ? ? ?print('Download code and datasets failed, please check the download url is valid or not.')
else:
? ?print('./rebar_count already exists')
2、加載需要的python模塊
輸入:
import os
import sys
import cv2
import time
import random
import torch
import numpy as np
from PIL import Image, ImageDraw
import xml.etree.ElementTree as ET
from datetime import datetime
from collections import OrderedDict
import torch.optim as optim
import torch.utils.data as data
import torch.backends.cudnn as cudnn
sys.path.insert(0, './rebar_count/src')
from rebar_count.src.data import VOCroot, VOC_Config, AnnotationTransform, VOCDetection, detection_collate, BaseTransform, preproc
from models.RFB_Net_vgg import build_net
from layers.modules import MultiBoxLoss
from layers.functions import Detect, PriorBox
from utils.visualize import *
from utils.nms_wrapper import nms
from utils.timer import Timer
import matplotlib.pyplot as plt
%matplotlib inline
ROOT_DIR = os.getcwd()
seed = 0
cudnn.benchmark = False
cudnn.deterministic = True
torch.manual_seed(seed) ? ? ? ? ? ?# 為CPU設置隨機種子
torch.cuda.manual_seed_all(seed) ? # 為所有GPU設置隨機種子
random.seed(seed)
np.random.seed(seed)
os.environ['PYTHONHASHSEED'] = str(seed) ?# 設置hash隨機種子
任務二:查看訓練數據樣例
1.查看訓練數據樣例
輸入:
def read_xml(xml_path):
? ?'''讀取xml標簽'''
? ?tree = ET.parse(xml_path)
? ?root = tree.getroot()
? ?boxes = []
? ?labels = []
? ?for element in root.findall('object'):
? ? ? ?label = element.find('name').text
? ? ? ?if label == 'steel':
? ? ? ? ? ?bndbox = element.find('bndbox')
? ? ? ? ? ?xmin = bndbox.find('xmin').text
? ? ? ? ? ?ymin = bndbox.find('ymin').text
? ? ? ? ? ?xmax = bndbox.find('xmax').text
? ? ? ? ? ?ymax = bndbox.find('ymax').text
? ? ? ? ? ?boxes.append([xmin, ymin, xmax, ymax])
? ? ? ? ? ?labels.append(label)
? ?return np.array(boxes, dtype=np.float64), labels
2.顯示原圖和標注框
輸入:
train_img_dir = './rebar_count/datasets/VOC2007/JPEGImages'
train_xml_dir = './rebar_count/datasets/VOC2007/Annotations'
files = os.listdir(train_img_dir)
files.sort()
for index, file_name in enumerate(files[:2]):
? img_path = os.path.join(train_img_dir, file_name)
? xml_path = os.path.join(train_xml_dir, file_name.split('.jpg')[0] + '.xml')
? boxes, labels = read_xml(xml_path)
? img = Image.open(img_path)
? ?
? resize_scale = 2048.0 / max(img.size)
? img = img.resize((int(img.size[0] * resize_scale), int(img.size[1] * resize_scale)))
? boxes *= resize_scale
? ?
? plt.figure(figsize=(img.size[0]/100.0, img.size[1]/100.0))
? plt.subplot(2,1,1)
? plt.imshow(img)
? ?
? img = img.convert('RGB')
? img = np.array(img)
? img = img.copy()
? for box in boxes:
? ? ? xmin, ymin, xmax, ymax = box.astype(int)
? ? ? cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), thickness=3)
? ?
? plt.subplot(2,1,2)
? plt.imshow(img)
plt.show()
輸出:
任務三:模型訓練
1.定義訓練超參,模型、日志保存路徑
輸入:
# 定義訓練超參
num_classes = 2 ?# 數據集中只有 steel 一個標簽,加上背景,所以總共有2個類
max_epoch = 25 ?# 默認值為1,調整為大于20的值,訓練效果更佳
batch_size = 4
ngpu = 1
initial_lr = 0.01
img_dim = 416 ?# 模型輸入圖片大小
train_sets = [('2007', 'trainval')] ?# 指定訓練集
cfg = VOC_Config
rgb_means = (104, 117, 123) ?# ImageNet數據集的RGB均值
save_folder = './rebar_count/model_snapshots' ?# 指定訓練模型保存路徑
if not os.path.exists(save_folder):
? ?os.mkdir(save_folder)
? ?
log_path = os.path.join('./rebar_count/logs', datetime.now().isoformat()) ?# 指定日志保存路徑
if not os.path.exists(log_path):
? ?os.makedirs(log_path)
2.構建模型,定義優化器及損失函數
輸入:
net = build_net('train', img_dim, num_classes=num_classes)
if ngpu > 1:
? ?net = torch.nn.DataParallel(net)
net.cuda() ?# 本案例代碼只能在GPU上訓練
cudnn.benchmark = True
optimizer = optim.SGD(net.parameters(), lr=initial_lr,
? ? ? ? ? ? ? ? ? ? ?momentum=0.9, weight_decay=0) ?# 定義優化器
criterion = MultiBoxLoss(num_classes,
? ? ? ? ? ? ? ? ? ? ? ? overlap_thresh=0.4,
? ? ? ? ? ? ? ? ? ? ? ? prior_for_matching=True,
? ? ? ? ? ? ? ? ? ? ? ? bkg_label=0,
? ? ? ? ? ? ? ? ? ? ? ? neg_mining=True,
? ? ? ? ? ? ? ? ? ? ? ? neg_pos=3,
? ? ? ? ? ? ? ? ? ? ? ? neg_overlap=0.3,
? ? ? ? ? ? ? ? ? ? ? ? encode_target=False) ?# 定義損失函數
priorbox = PriorBox(cfg)
with torch.no_grad():
? ?priors = priorbox.forward()
? ?priors = priors.cuda()
3.定義自定義學習率函數
輸入:
def adjust_learning_rate(optimizer, gamma, epoch, step_index, iteration, epoch_size):
? ?"""
? 自適應學習率
? """
? ?if epoch < 11:
? ? ? ?lr = 1e-8 + (initial_lr-1e-8) * iteration / (epoch_size * 10)
? ?else:
? ? ? ?lr = initial_lr * (gamma ** (step_index))
? ?for param_group in optimizer.param_groups:
? ? ? ?param_group['lr'] = lr
? ?return lr
4.定義訓練函數
輸入:
def train():
? ?"""
? 模型訓練函數,每10次迭代打印一次日志,20個epoch之后,每個epoch保存一次模型
? """
? ?net.train()
? ?loc_loss = 0
? ?conf_loss = 0
? ?epoch = 0
? ?print('Loading dataset...')
? ?dataset = VOCDetection(VOCroot, train_sets, preproc(img_dim, rgb_means, p=0.0), AnnotationTransform())
? ?epoch_size = len(dataset) // batch_size
? ?max_iter = max_epoch * epoch_size
? ?stepvalues = (25 * epoch_size, 35 * epoch_size)
? ?step_index = 0
? ?start_iter = 0
? ?lr = initial_lr
? ?for iteration in range(start_iter, max_iter):
? ? ? ?if iteration % epoch_size == 0:
? ? ? ? ? ?if epoch > 20:
? ? ? ? ? ? ? ?torch.save(net.state_dict(), os.path.join(save_folder, 'epoch_' +
? ? ? ? ? ? ? ? ? ? ? ? ? repr(epoch).zfill(3) + '_loss_'+ '%.4f' % loss.item() + '.pth'))
? ? ? ? ? ?batch_iterator = iter(data.DataLoader(dataset, batch_size,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?shuffle=True, num_workers=1, collate_fn=detection_collate))
? ? ? ? ? ?loc_loss = 0
? ? ? ? ? ?conf_loss = 0
? ? ? ? ? ?epoch += 1
? ? ? ?load_t0 = time.time()
? ? ? ?if iteration in stepvalues:
? ? ? ? ? ?step_index += 1
? ? ? ?lr = adjust_learning_rate(optimizer, 0.2, epoch, step_index, iteration, epoch_size)
? ? ? ?images, targets = next(batch_iterator)
? ? ? ?images = Variable(images.cuda())
? ? ? ?targets = [Variable(anno.cuda()) for anno in targets]
? ? ? ?# forward
? ? ? ?t0 = time.time()
? ? ? ?out = net(images)
? ? ? ?# backprop
? ? ? ?optimizer.zero_grad()
? ? ? ?loss_l, loss_c = criterion(out, priors, targets)
? ? ? ?loss = loss_l + loss_c
? ? ? ?loss.backward()
? ? ? ?optimizer.step()
? ? ? ?t1 = time.time()
? ? ? ?loc_loss += loss_l.item()
? ? ? ?conf_loss += loss_c.item()
? ? ? ?load_t1 = time.time()
? ? ? ?if iteration % 10 == 0:
? ? ? ? ? ?print('Epoch:' + repr(epoch) + ' || epochiter: ' + repr(iteration % epoch_size) + '/' + repr(epoch_size)
? ? ? ? ? ? ? ? ?+ '|| Totel iter ' +
? ? ? ? ? ? ? ? ?repr(iteration) + ' || L: %.4f C: %.4f||' % (
? ? ? ? ? ? ? ?loss_l.item(),loss_c.item()) +
? ? ? ? ? ? ? ?'Batch time: %.4f sec. ||' % (load_t1 - load_t0) + 'LR: %.8f' % (lr))
? ?torch.save(net.state_dict(), os.path.join(save_folder, 'epoch_' +
? ? ? ? ? ? ? repr(epoch).zfill(3) + '_loss_'+ '%.4f' % loss.item() + '.pth'))
- 開始訓練,每個epoch訓練耗時約60秒,這里共運行25個epoch,耗時較久,請耐心等待。
輸入:
t1 = time.time()
print('開始訓練,本次訓練總共需%d個epoch,每個epoch訓練耗時約60秒' % max_epoch)
train()
print('training cost %.2f s' % (time.time() - t1))
輸出:
開始訓練,本次訓練總共需25個epoch,每個epoch訓練耗時約60秒
Loading dataset...
Epoch:1 || epochiter: 0/50|| Totel iter 0 || L: 3.5865 C: 4.3866||Batch time: 4.4935 sec. ||LR: 0.00000001
Epoch:1 || epochiter: 10/50|| Totel iter 10 || L: 4.1511 C: 3.8391||Batch time: 1.0780 sec. ||LR: 0.00020001
.....#中間內容省略#
Epoch:25 || epochiter: 30/50|| Totel iter 1230 || L: 0.3628 C: 0.5933||Batch time: 1.1411 sec. ||LR: 0.01000000
Epoch:25 || epochiter: 40/50|| Totel iter 1240 || L: 0.6436 C: 0.6199||Batch time: 1.5384 sec. ||LR: 0.01000000
training cost 1556.92 s
任務四:模型推理
1.已完成訓練,下面開始測試模型,首先需定義目標檢測類
輸入:
cfg = VOC_Config
img_dim = 416
rgb_means = (104, 117, 123)
priorbox = PriorBox(cfg)
with torch.no_grad():
priors = priorbox.forward()
if torch.cuda.is_available():
priors = priors.cuda()
class ObjectDetector:
"""
定義目標檢測類
"""
def __init__(self, net, detection, transform, num_classes=num_classes, thresh=0.01, cuda=True):
self.net = net
self.detection = detection
self.transform = transform
self.num_classes = num_classes
self.thresh = thresh
self.cuda = torch.cuda.is_available()
def predict(self, img):
_t = {'im_detect': Timer(), 'misc': Timer()}
scale = torch.Tensor([img.shape[1], img.shape[0],
img.shape[1], img.shape[0]])
with torch.no_grad():
x = self.transform(img).unsqueeze(0)
if self.cuda:
x = x.cuda()
scale = scale.cuda()
_t['im_detect'].tic()
out = net(x) # forward pass
boxes, scores = self.detection.forward(out, priors)
detect_time = _t['im_detect'].toc()
boxes = boxes[0]
scores = scores[0]
# scale each detection back up to the image
boxes *= scale
boxes = boxes.cpu().numpy()
scores = scores.cpu().numpy()
_t['misc'].tic()
all_boxes = [[] for _ in range(num_classes)]
for j in range(1, num_classes):
inds = np.where(scores[:, j] > self.thresh)[0]
if len(inds) == 0:
all_boxes[j] = np.zeros([0, 5], dtype=np.float32)
continue
c_bboxes = boxes[inds]
c_scores = scores[inds, j]
c_dets = np.hstack((c_bboxes, c_scores[:, np.newaxis])).astype(
np.float32, copy=False)
keep = nms(c_dets, 0.2, force_cpu=False)
c_dets = c_dets[keep, :]
all_boxes[j] = c_dets
nms_time = _t['misc'].toc()
total_time = detect_time + nms_time
return all_boxes, total_time
2.定義推理網絡,并加載前面訓練的loss最低的模型
輸入:
trained_models = os.listdir(os.path.join(ROOT_DIR, './rebar_count/model_snapshots')) # 模型文件所在目錄
lowest_loss = 9999
best_model_name = ''
for model_name in trained_models:
if not model_name.endswith('pth'):
continue
loss = float(model_name.split('_loss_')[1].split('.pth')[0])
if loss < lowest_loss:
lowest_loss = loss
best_model_name = model_name
best_model_path = os.path.join(ROOT_DIR, './rebar_count/model_snapshots', best_model_name)
print('loading model from', best_model_path)
net = build_net('test', img_dim, num_classes) # 加載模型
state_dict = torch.load(best_model_path)
new_state_dict = OrderedDict()
for k, v in state_dict.items():
head = k[:7]
if head == 'module.':
name = k[7:]
else:
name = k
new_state_dict[name] = v
net.load_state_dict(new_state_dict)
net.
print('Finish load model!')
if torch.cuda.is_available():
net = net.cuda()
cudnn.benchmark = True
else:
net = net.cpu()
detector = Detect(num_classes, 0, cfg)
transform = BaseTransform(img_dim, rgb_means, (2, 0, 1))
object_detector = ObjectDetector(net, detector, transform)
輸出:
loading model from /home/./rebar_count/model_snapshots/epoch_024_loss_0.9113.pth
Finish load model!
- 測試圖片,輸出每條鋼筋的位置和圖片中鋼筋總條數
輸入:
test_img_dir = r'./rebar_count/datasets/test_dataset' # 待預測的圖片目錄
files = os.listdir(test_img_dir)
files.sort()
for i, file_name in enumerate(files[:2]):
image_src = cv2.imread(os.path.join(test_img_dir, file_name))
detect_bboxes, tim = object_detector.predict(image_src)
image_draw = image_src.copy()
rebar_count = 0
for class_id, class_collection in enumerate(detect_bboxes):
if len(class_collection) > 0:
for i in range(class_collection.shape[0]):
if class_collection[i, -1] > 0.6:
pt = class_collection[i]
cv2.circle(image_draw, (int((pt[0] + pt[2]) * 0.5), int((pt[1] + pt[3]) * 0.5)), int((pt[2] - pt[0]) * 0.5 * 0.6), (255, 0, 0), -1)
rebar_count += 1
cv2.putText(image_draw, 'rebar_count: %d' % rebar_count, (25, 50), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 3)
plt.figure(i, figsize=(30, 20))
plt.imshow(image_draw)
plt.show()
輸出:
從推理結果可以看到,模型能較為精準的對鋼筋進行計數。