《我的PaddlePaddle學習之路》筆記九——使用VOC資料集的實現目標檢測
目錄
前言
目標檢測的使用範圍很廣,比如我們使用相機拍照時,要正確檢測人臉的位置,從而做進一步處理,比如美顏等等。在目標檢測的深度學習領域上,從2014年到2016年,先後出現了R-CNN,Fast R-CNN, Faster R-CNN, ION, HyperNet, SDP-CRC, YOLO,G-CNN, SSD等神經網路模型,使得目標檢測不管是在準確度上,還是速度上都有很大提高,幾乎可以達到實時檢測。
VOC資料集
VOC資料集介紹
PASCAL VOC挑戰賽是視覺物件的分類識別和檢測的一個基準測試,提供了檢測演算法和學習效能的標準影象註釋資料集和標準的評估系統。
PASCAL VOC圖片集包括20個目錄:
- 人類; 動物(鳥、貓、牛、狗、馬、羊);
- 交通工具(飛機、自行車、船、公共汽車、小轎車、摩托車、火車);
- 室內(瓶子、椅子、餐桌、盆栽植物、沙發、電視)。
這些類別在data/label_list
檔案中都有列出來,但這個檔案中多了一個類別,就是背景(background)
下載VOC資料集
可以通過以下命令下載資料集
# 切換到專案的資料目錄
cd data
# 下載2007年的訓練資料
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtrainval_06-Nov-2007.tar
# 下載2007年的測試資料
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2007/VOCtest_06-Nov-2007. tar
# 下載2012年的訓練資料
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
解壓資料集
下載完成之後,要解壓資料集到當前目錄
tar xvf VOCtest_06-Nov-2007.tar
tar xvf VOCtrainval_06-Nov-2007.tar
tar xvf VOCtrainval_11-May-2012.tar
解壓之後會得到一個目錄,其中我們實質只用到Annotations(標註檔案)
和JPEGImages(影象檔案)
下的檔案。
VOCdevkit
|____VOC2007
| |____Annotations(標註檔案)
| |____JPEGImages(影象檔案)
| |____ImageSets
| |____SegmentationClass
| |____SegmentationObject
|
|____VOC2012
|____Annotations(標註檔案)
|____JPEGImages(影象檔案)
|____ImageSets
|____SegmentationClass
|____SegmentationObject
生成影象列表
我們要編寫一個程式data/prepare_voc_data.py
,把這些資料生成一個影象列表,就像之前的影象列表差不多,每一行對應的是影象的路徑和標籤。這次有點不同的是對應的不是int
型別的label了,是一個xml
的標註檔案。其部分程式碼片段如下:
def prepare_filelist(devkit_dir, years, output_dir):
trainval_list = []
test_list = []
# 獲取兩個年份的資料
for year in years:
trainval, test = walk_dir(devkit_dir, year)
trainval_list.extend(trainval)
test_list.extend(test)
# 打亂訓練資料
random.shuffle(trainval_list)
# 儲存訓練影象列表
with open(os.path.join(output_dir, 'trainval.txt'), 'w') as ftrainval:
for item in trainval_list:
ftrainval.write(item[0] + ' ' + item[1] + '\n')
# 儲存測試影象列表
with open(os.path.join(output_dir, 'test.txt'), 'w') as ftest:
for item in test_list:
ftest.write(item[0] + ' ' + item[1] + '\n')
if __name__ == '__main__':
# 資料存放的位置
devkit_dir = 'VOCdevkit'
# 資料的年份
years = ['2007', '2012']
prepare_filelist(devkit_dir, years, '.')
通過上面的程式,就可以生成一個影象列表,列表片段如下:
VOCdevkit/VOC2007/JPEGImages/000001.jpg VOCdevkit/VOC2007/Annotations/000001.xml
VOCdevkit/VOC2007/JPEGImages/000002.jpg VOCdevkit/VOC2007/Annotations/000002.xml
VOCdevkit/VOC2007/JPEGImages/000003.jpg VOCdevkit/VOC2007/Annotations/000003.xml
VOCdevkit/VOC2007/JPEGImages/000004.jpg VOCdevkit/VOC2007/Annotations/000004.xml
資料集的操作就到這裡了
資料預處理
在之前的文章中可以知道,訓練和測試的資料都是一個reader資料格式,所以我們要對我們的VOC資料集做一些處理。跟之前最大的不同是這次的標籤不是簡單的int
或者是一個字串,而是一個標註XML
檔案。而且訓練的影象大小必須是統一大小的,但是實際的影象的大小是不固定的,如果改變了影象的大小,那麼影象的標註資訊就不正確了,所以對影象的大小修改同時,也要對標註資訊做對應的變化。
獲取標註資訊的程式碼片段:
# 儲存列表的結構: label | xmin | ymin | xmax | ymax | difficult
if mode == 'train' or mode == 'test':
# 儲存每個標註框
bbox_labels = []
# 開始讀取標註資訊
root = xml.etree.ElementTree.parse(label_path).getroot()
# 查詢每個標註的資訊
for object in root.findall('object'):
# 每個標註框的資訊
bbox_sample = []
# start from 1
bbox_sample.append(
float(
settings.label_list.index(
object.find('name').text)))
bbox = object.find('bndbox')
difficult = float(object.find('difficult').text)
# 獲取標註資訊,並計算比例儲存
bbox_sample.append(
float(bbox.find('xmin').text) / img_width)
bbox_sample.append(
float(bbox.find('ymin').text) / img_height)
bbox_sample.append(
float(bbox.find('xmax').text) / img_width)
bbox_sample.append(
float(bbox.find('ymax').text) / img_height)
bbox_sample.append(difficult)
# 將整個框的資訊儲存
bbox_labels.append(bbox_sample)
獲取了標註資訊並計算儲存了標註資訊,然後根據影象的原始大小和標註資訊的比例,可以裁剪影象的標註資訊對應的影象。
def crop_image(img, bbox_labels, sample_bbox, image_width, image_height):
'''
裁剪影象
:param img: 影象
:param bbox_labels: 所有的標註資訊
:param sample_bbox: 對應一個的標註資訊
:param image_width: 影象原始的寬
:param image_height: 影象原始的高
:return:裁剪好的影象和其對應的標註資訊
'''
sample_bbox = clip_bbox(sample_bbox)
xmin = int(sample_bbox.xmin * image_width)
xmax = int(sample_bbox.xmax * image_width)
ymin = int(sample_bbox.ymin * image_height)
ymax = int(sample_bbox.ymax * image_height)
sample_img = img[ymin:ymax, xmin:xmax]
sample_labels = transform_labels(bbox_labels, sample_bbox)
return sample_img, sample_labels
然後使用這些影象就可以使用訓練或者測試要使用的reader的了,程式碼片段如下:
def reader():
img = Image.fromarray(img)
# 設定影象大小
img = img.resize((settings.resize_w, settings.resize_h),
Image.ANTIALIAS)
img = np.array(img)
if mode == 'train':
mirror = int(random.uniform(0, 2))
if mirror == 1:
img = img[:, ::-1, :]
for i in xrange(len(sample_labels)):
tmp = sample_labels[i][1]
sample_labels[i][1] = 1 - sample_labels[i][3]
sample_labels[i][3] = 1 - tmp
if len(img.shape) == 3:
img = np.swapaxes(img, 1, 2)
img = np.swapaxes(img, 1, 0)
img = img.astype('float32')
img -= settings.img_mean
img = img.flatten()
if mode == 'train' or mode == 'test':
if mode == 'train' and len(sample_labels) == 0: continue
yield img.astype('float32'), sample_labels
elif mode == 'infer':
yield img.astype('float32')
return reader
最後通過呼叫PaddlePaddle的藉口就可以生成訓練和測試使用的最終reader
,程式碼如下:
# 建立訓練資料
train_reader = paddle.batch(
data_provider.train(data_args, train_file_list),
batch_size=cfg.TRAIN.BATCH_SIZE)
# 建立測試資料
dev_reader = paddle.batch(
data_provider.test(data_args, dev_file_list),
batch_size=cfg.TRAIN.BATCH_SIZE)
SSD神經網路
SSD原理
SSD使用一個卷積神經網路實現“端到端”的檢測:輸入為原始影象,輸出為檢測結果,無需藉助外部工具或流程進行特徵提取、候選框生成等。論文中SSD使用VGG16作為基礎網路進行影象特徵提取。但SSD對原始VGG16網路做了一些改變:
- 將最後的fc6、fc7全連線層變為卷積層,卷積層引數通過對原始fc6、fc7引數取樣得到。
- 將pool5層的引數由2x2-s2(kernel大小為2x2,stride size為2)更改為3x3-s1-p1(kernel大小為3x3,stride size為1,padding size為1)。
- 在conv4_3、conv7、conv8_2、conv9_2、conv10_2及pool11層後面接了priorbox層,priorbox層的主要目的是根據輸入的特徵圖(feature map)生成一系列的矩形候選框。
下圖為模型(輸入影象尺寸:300x300)的總體結構:
圖1. SSD 網路結構
圖中每個矩形盒子代表一個卷積層,最後兩個矩形框分別表示彙總各卷積層輸出結果和後處理階段。在預測階段,網路會輸出一組候選矩形框,每個矩形包含:位置和類別得分。圖中倒數第二個矩形框即表示網路的檢測結果的彙總處理。由於候選矩形框數量較多且很多矩形框重疊嚴重,這時需要經過後處理來篩選出質量較高的少數矩形框,主要方法有非極大值抑制(Non-maximum Suppression)。
從SSD的網路結構可以看出,候選矩形框在多個特徵圖(feature map)上生成,不同的feature map具有的感受野不同,這樣可以在不同尺度掃描影象,相對於其他檢測方法可以生成更豐富的候選框,從而提高檢測精度;另一方面SSD對VGG16的擴充套件部分以較小的代價實現對候選框的位置和類別得分的計算,整個過程只需要一個卷積神經網路完成,所以速度較快。
SSD程式碼介紹
如上介紹所說,SSD使用VGG16作為基礎網路進行影象特徵提取
# 卷積神經網路
def conv_group(stack_num, name_list, input, filter_size_list, num_channels,
num_filters_list, stride_list, padding_list,
common_bias_attr, common_param_attr, common_act):
conv = input
in_channels = num_channels
for i in xrange(stack_num):
conv = paddle.layer.img_conv(
name=name_list[i],
input=conv,
filter_size=filter_size_list[i],
num_channels=in_channels,
num_filters=num_filters_list[i],
stride=stride_list[i],
padding=padding_list[i],
bias_attr=common_bias_attr,
param_attr=common_param_attr,
act=common_act)
in_channels = num_filters_list[i]
return conv
# VGG神經網路
def vgg_block(idx_str, input, num_channels, num_filters, pool_size,
pool_stride, pool_pad):
layer_name = "conv%s_" % idx_str
stack_num = 3
name_list = [layer_name + str(i + 1) for i in xrange(3)]
conv = conv_group(stack_num, name_list, input, [3] * stack_num,
num_channels, [num_filters] * stack_num,
[1] * stack_num, [1] * stack_num, default_bias_attr,
get_param_attr(1, default_l2regularization),
paddle.activation.Relu())
pool = paddle.layer.img_pool(
input=conv,
pool_size=pool_size,
num_channels=num_filters,
pool_type=paddle.pooling.CudnnMax(),
stride=pool_stride,
padding=pool_pad)
return conv, pool
將最後的fc6、fc7全連線層變為卷積層,卷積層引數通過對原始fc6、fc7引數取樣得到:
fc7 = conv_group(stack_num, ['fc6', 'fc7'], pool5, [3, 1], 512, [1024] *
stack_num, [1] * stack_num, [1, 0], default_bias_attr,
get_param_attr(1, default_l2regularization),
paddle.activation.Relu())
將pool5層的引數由2x2-s2(kernel大小為2x2,stride size為2)更改為3x3-s1-p1(kernel大小為3x3,stride size為1,padding size為1):
def mbox_block(layer_idx, input, num_channels, filter_size, loc_filters,
conf_filters):
mbox_loc_name = layer_idx + "_mbox_loc"
mbox_loc = paddle.layer.img_conv(
name=mbox_loc_name,
input=input,
filter_size=filter_size,
num_channels=num_channels,
num_filters=loc_filters,
stride=1,
padding=1,
bias_attr=default_bias_attr,
param_attr=get_param_attr(1, default_l2regularization),
act=paddle.activation.Identity())
mbox_conf_name = layer_idx + "_mbox_conf"
mbox_conf = paddle.layer.img_conv(
name=mbox_conf_name,
input=input,
filter_size=filter_size,
num_channels=num_channels,
num_filters=conf_filters,
stride=1,
padding=1,
bias_attr=default_bias_attr,
param_attr=get_param_attr(1, default_l2regularization),
act=paddle.activation.Identity())
return mbox_loc, mbox_conf
最後要獲取到訓練和預測使用到的損失函式和檢查輸出層
if mode == 'train' or mode == 'eval':
bbox = paddle.layer.data(
name='bbox', type=paddle.data_type.dense_vector_sequence(6))
loss = paddle.layer.multibox_loss(
input_loc=loc_loss_input,
input_conf=conf_loss_input,
priorbox=mbox_priorbox,
label=bbox,
num_classes=cfg.CLASS_NUM,
overlap_threshold=cfg.NET.MBLOSS.OVERLAP_THRESHOLD,
neg_pos_ratio=cfg.NET.MBLOSS.NEG_POS_RATIO,
neg_overlap=cfg.NET.MBLOSS.NEG_OVERLAP,
background_id=cfg.BACKGROUND_ID,
name="multibox_loss")
paddle.evaluator.detection_map(
input=detection_out,
label=bbox,
overlap_threshold=cfg.NET.DETMAP.OVERLAP_THRESHOLD,
background_id=cfg.BACKGROUND_ID,
evaluate_difficult=cfg.NET.DETMAP.EVAL_DIFFICULT,
ap_type=cfg.NET.DETMAP.AP_TYPE,
name="detection_evaluator")
return loss, detection_out
elif mode == 'infer':
return detection_out
訓練模型
訓練流程圖
訓練的流程圖:
建立訓練器
建立訓練器,程式碼片段如下:
# 建立優化方法
optimizer = paddle.optimizer.Momentum(
momentum=cfg.TRAIN.MOMENTUM,
learning_rate=cfg.TRAIN.LEARNING_RATE,
regularization=paddle.optimizer.L2Regularization(
rate=cfg.TRAIN.L2REGULARIZATION),
learning_rate_decay_a=cfg.TRAIN.LEARNING_RATE_DECAY_A,
learning_rate_decay_b=cfg.TRAIN.LEARNING_RATE_DECAY_B,
learning_rate_schedule=cfg.TRAIN.LEARNING_RATE_SCHEDULE)
# 通過神經網路模型獲取損失函式和額外層
cost, detect_out = vgg_ssd_net.net_conf('train')
# 通過損失函式建立訓練引數
parameters = paddle.parameters.create(cost)
# 如果有訓練好的模型,可以使用訓練好的模型再訓練
if not (init_model_path is None):
assert os.path.isfile(init_model_path), 'Invalid model.'
parameters.init_from_tar(gzip.open(init_model_path))
# 建立訓練器
trainer = paddle.trainer.SGD(cost=cost,
parameters=parameters,
extra_layers=[detect_out],
update_equation=optimizer)
開始訓練
有了訓練器,我們才可以開始訓練。如果單純讓它訓練,沒做一些資料儲存處理,這種訓練是沒有意義的,所以我們要定義一個訓練事件,讓它在訓練過程中儲存我們需要的模型引數,同時輸出一些日誌資訊,方便我們檢視訓練的效果,訓練事件的程式碼片段:
# 定義訓練事件
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 1 == 0:
print "\nPass %d, Batch %d, TrainCost %f, Detection mAP=%f" % \
(event.pass_id,
event.batch_id,
event.cost,
event.metrics['detection_evaluator'])
else:
sys.stdout.write('.')
sys.stdout.flush()
if isinstance(event, paddle.event.EndPass):
with gzip.open('../models/params_pass.tar.gz', 'w') as f:
trainer.save_parameter_to_tar(f)
result = trainer.test(reader=dev_reader, feeding=feeding)
print "\nTest with Pass %d, TestCost: %f, Detection mAP=%g" % \
(event.pass_id,
result.cost,
result.metrics['detection_evaluator'])
最後就可以進行訓練了,訓練的程式碼為:
# 開始訓練
trainer.train(
reader=train_reader,
event_handler=event_handler,
num_passes=cfg.TRAIN.NUM_PASS,
feeding=feeding)
具體呼叫方法如下,train_file_list
為訓練資料;dev_file_list
為測試資料;data_args
為資料集的設定;init_model_path
為初始化模型引數,在第三章CIFAR彩色影象識別我們就談到SSD神經網路很容易發生浮點異常,所以我們要一個預訓練的模型來提供初始化訓練引數,筆者使用的是PaddlePaddle官方提供的預訓練的模型:
if __name__ == "__main__":
# 初始化PaddlePaddle
paddle.init(use_gpu=True, trainer_count=2)
# 設定資料引數
data_args = data_provider.Settings(
data_dir='../data',
label_file='../data/label_list',
resize_h=cfg.IMG_HEIGHT,
resize_w=cfg.IMG_WIDTH,
mean_value=[104, 117, 124])
# 開始訓練
train(
train_file_list='../data/trainval.txt',
dev_file_list='../data/test.txt',
data_args=data_args,
init_model_path='../models/vgg_model.tar.gz')
在訓練過程中會輸出以下訓練日誌:
Pass 0, Batch 0, TrainCost 17.445816, Detection mAP=0.000000
...................................................................................................
Pass 0, Batch 100, TrainCost 8.544815, Detection mAP=2.871136
...................................................................................................
Pass 0, Batch 200, TrainCost 7.434404, Detection mAP=3.337185
...................................................................................................
Pass 0, Batch 300, TrainCost 7.404398, Detection mAP=7.070700
...................................................................................................
Pass 0, Batch 400, TrainCost 7.023655, Detection mAP=3.080483
評估模型
我們訓練好的模型之後,在使用模式進行預測,可以對模型進行評估。評估模型的方法跟訓練是使用到的Test是一樣的,只是我們專門把它提取處理,用於評估模型而已。
同樣是要先建立訓練器,程式碼片段如下:
# 通過神經網路模型獲取損失函式和額外層
cost, detect_out = vgg_ssd_net.net_conf(mode='eval')
# 檢查模型模型路徑是否正確
assert os.path.isfile(model_path), 'Invalid model.'
# 通過訓練好的模型生成引數
parameters = paddle.parameters.Parameters.from_tar(gzip.open(model_path))
# 建立優化方法
optimizer = paddle.optimizer.Momentum()
# 建立訓練器
trainer = paddle.trainer.SGD(cost=cost,
parameters=parameters,
extra_layers=[detect_out],
update_equation=optimizer)
然後是去掉訓練過程,只留下Test部分,所得的程式碼片段如下:
# 定義資料層之間的關係
feeding = {'image': 0, 'bbox': 1}
# 生成要訓練的資料
reader = paddle.batch(
data_provider.test(data_args, eval_file_list), batch_size=batch_size)
# 獲取測試結果
result = trainer.test(reader=reader, feeding=feeding)
# 列印模型的測試資訊
print "TestCost: %f, Detection mAP=%g" % \
(result.cost, result.metrics['detection_evaluator'])
具體呼叫方法如下,可以看到使用的的資料集還是我們在訓練時候使用到的測試資料:
if __name__ == "__main__":
paddle.init(use_gpu=True, trainer_count=2)
# 設定資料引數
data_args = data_provider.Settings(
data_dir='../data',
label_file='../data/label_list',
resize_h=cfg.IMG_HEIGHT,
resize_w=cfg.IMG_WIDTH,
mean_value=[104, 117, 124])
# 開始評估
eval(eval_file_list='../data/test.txt',
batch_size=4,
data_args=data_args,
model_path='../models/params_pass.tar.gz')
評估模型輸出的日誌如下:
TestCost: 7.185788, Detection mAP=1.07462
預測資料
預測並儲存預測結果
獲得模型引數之後,就可以使用它來做目標檢測了,比如我們要把下面這張影象做目標檢測:
預測的程式碼片段如下:
# 通過網路模型獲取輸出層
detect_out = vgg_ssd_net.net_conf(mode='infer')
# 檢查模型路徑是否正確
assert os.path.isfile(model_path), 'Invalid model.'
# 載入訓練好的引數
parameters = paddle.parameters.Parameters.from_tar(gzip.open(model_path))
# 或預測器
inferer = paddle.inference.Inference(
output_layer=detect_out, parameters=parameters)
# 獲取預測資料
reader = data_provider.infer(data_args, eval_file_list)
all_fname_list = [line.strip() for line in open(eval_file_list).readlines()]
# 獲取預測原始結果
infer_res = inferer.infer(input=infer_data)
獲得預測結果之後,我們可以將預測的結果儲存的一個檔案中,儲存這些檔案方便之後使用這些資料:
# 獲取影象的idx
img_idx = int(det_res[0])
# 獲取影象的label
label = int(det_res[1])
# 獲取預測的得分
conf_score = det_res[2]
# 獲取目標的框
xmin = det_res[3] * img_w[img_idx]
ymin = det_res[4] * img_h[img_idx]
xmax = det_res[5] * img_w[img_idx]
ymax = det_res[6] * img_h[img_idx]
# 將預測結果寫入到檔案中
fout.write(fname_list[img_idx] + '\t' + str(label) + '\t' + str(
conf_score) + '\t' + str(xmin) + ' ' + str(ymin) + ' ' + str(xmax) +
' ' + str(ymax))
fout.write('\n')
具體呼叫方法,eval_file_list
是要預測的資料的路徑檔案,save_path
儲存預測結果的路徑,resize_h
和resize_w
指定影象的寬和高,batch_size
只能設定為1,否則會資料丟失,model_path
模型的路徑,threshold
是篩選最低得分。
if __name__ == "__main__":
paddle.init(use_gpu=True, trainer_count=2)
# 設定資料引數
data_args = data_provider.Settings(
data_dir='../images',
label_file='../data/label_list',
resize_h=cfg.IMG_HEIGHT,
resize_w=cfg.IMG_WIDTH,
mean_value=[104, 117, 124])
# 開始預測,batch_size只能設定為1,否則會資料丟失
infer(
eval_file_list='../images/infer.txt',
save_path='../images/infer.res',
data_args=data_args,
batch_size=1,
model_path='../models/params_pass.tar.gz',
threshold=0.3)
預測的結果會儲存在images/infer.res
中,每一行對應的是一個目標框,格式為:影象的路徑 分類的標籤 目標框的得分 xmin ymin xmax ymax
,每個影象可以有多個類別,所以會有多個框。
infer/00001.jpg 7 0.7000513 287.25091552734375 265.18829345703125 599.12451171875 539.6732330322266
infer/00002.jpg 7 0.53912574 664.7453212738037 240.53946733474731 1305.063714981079 853.0169785022736
infer/00002.jpg 11 0.6429965 551.6539978981018 204.59033846855164 1339.9816703796387 843.807926774025
infer/00003.jpg 12 0.7647844 133.20248904824257 45.33928334712982 413.9954067468643 266.06680154800415
infer/00004.jpg 12 0.66517526 117.327481508255 251.13083073496819 550.8465766906738 665.4091544151306
顯示畫出的框
有了以上的預測檔案,並不能很直觀看到預測的結果,我們可以編寫一個程式,讓它在原影象上畫上預測出來的框,這樣就更直接看到結果了。核心程式碼如下:
# 讀取每張影象
for img_path in all_img_paht:
im = cv2.imread('../images/' + img_path)
# 為每張影象畫上所有的框
for label_1 in all_labels:
label_img_path = label_1[0]
# 判斷是否是統一路徑
if img_path == label_img_path:
xmin, ymin, xmax, ymax = label_1[3].split(' ')
# 型別轉換
xmin = float(xmin)
ymin = float(ymin)
xmax = float(xmax)
ymax = float(ymax)
# 畫框
cv2.rectangle(im, (int(xmin), int(ymin)), (int(xmax), int(ymax)), (0, 255, 0), 3)
# 儲存畫好的影象
names = img_path.strip().split('/')
name = names[len(names)-1]
cv2.imwrite('../images/result/%s' % name, im)
最後通過在入口呼叫該方法就可以,程式碼如下:
if __name__ == '__main__':
# 預測的影象路徑檔案
img_path_list = '../images/infer.txt'
# 預測結果的檔案路徑
result_data_path = '../images/infer.res'
# 儲存畫好的影象路徑
save_path = '../images/result'
show(img_path_list, result_data_path, save_path)
畫好的影象如下:
專案程式碼
參考資料
相關推薦
《Cocos2d學習之路》九、資料儲存的幾種方式和基本使用
年前已經把這部分東西學完了,但是後面出現了一個bug,緊接著公司組織關係變更,搬家到新的地方上班等事情,忙完接著就回家過年了。終於,年後開始上班了,抽出空來把blog寫一下。 cocos2dx中資料儲存的幾種方式 1、userdefault 這個類似於android中
《我的PaddlePaddle學習之路》筆記九——使用VOC資料集的實現目標檢測
目錄 前言 目標檢測的使用範圍很廣,比如我們使用相機拍照時,要正確檢測人臉的位置,從而做進一步處理,比如美顏等等。在目標檢測的深度學習領域上,從2014年到2016年,先後出現了R-CNN,Fast R-CNN, Faster R-CNN, I
《我的PaddlePaddle學習之路》筆記七——車牌端到端的識別
目錄 前言 車牌識別的應用場景有很多,比如在停車場。通過車牌識別登記入庫和出庫的車輛的情況,並計算該車停留時間,然後折算費用。還可以在公路上識別來往的車輛,方便交警的檢查等等。接下來我們就是使用PaddlePaddle來做一個車牌識別,我們直接
《我的PaddlePaddle學習之路》筆記十二——視覺化工具VisualDL的使用
目錄 前言 VisualDL是一個面向深度學習任務設計的視覺化工具,包含了scalar、引數分佈、模型結構、影象視覺化等功能。可以這樣說:“所見即所得”。我們可以藉助VisualDL來觀察我們訓練的情況,方便我們對訓練的模型進行分析,改善模型的
我的學習之路_第二十九章_bootstrap
柵格系統 學習之路 大屏幕 支持 響應式 入門 顯示效果 oot name bootstrap 內置了html,css,js插件為一體的前端框架 響應式布局: 設計一套頁面就可以使用於很多現實設備 bootstrap: 1.入門(響應式布局的容器) 1.先進入jQu
我的學習之路_第二章_接口/多態
nal interface implement 類型轉換 ace pri 類名 提高 表現 接口 (1)當抽象類中的方法都是抽象的時候,java就提供了一種新的表現形式:接口。接口是功能的集合 接口不能創建對象 (2)格式: 父接口: public interface
我的學習之路_第八章_map集合
val arc 特點 nbsp 方法名 shuf 數據結構 鏈表結構 靜態 【Map集合】 Map集合中常用方法: 1: 返回值:value值 put(K key, V value) 往Map集合中添加元素,如果key值重復,那麽將返回被覆蓋的value值.
我的學習之路_第十一章_字符流
之間 才會 fileutil output keys 返回 ont 讀取數據 一個 【字符流】 IO流的分類 ★字節流 操作的文件不是文本文件 字節輸入流: InputStream 抽象類 FileInputStream操作文件的字節輸入流 字節輸出流: OutputStr
我的學習之路_第十七章_JavaUtils
包含 string 和數 類的方法 objc 基本數據類型 通過 序列 setprop 【BeanUtils工具類】 javaBaen : Java和數據庫所對應關系實體類 表(Utils)-->類(User) 表中的列-->類中字段(屬性) 表中的行-->
我的學習之路_第十八章_SQL語句
之路 唯一約束 一個表 database pda eat 三種方式 rac 條件過濾 SQL語句 啟動數據庫: net start mysql 關閉數據庫: net stop mysql 登錄數據庫 : cmd-->命令行-->mysql--&g
我的學習之路(一)SQL盲註學習篇
網絡安全 dvwa sql盲註 我的學習之路,現在零基礎,是一個小白,請各位大牛批評指正!寫下這篇,是對自己的一個思路的整理,僅供參考。 Dvwa中登錄進入,首先在DVWA Security中設置等級為low,然後進入SQL Injection(blind),隨意輸入一個數字進行抓包,然後找
初識vue.js,我的學習之路(一)
自動打開 下一步 鏡像 分享圖片 bpa demo 中間 前端技術 width 在以前做項目時經常是新建一些html、css、等一些文件,但在接觸了vue.js之後我發現我已經有點看不懂前端了,這對於我這麽一個菜鳥來說實在是很苦逼的事情。現在的前端技術都離不開
初識vue.js,我的學習之路(三)
學習 定義 我想 red spa pos console gpo con vue之自定義指令 像v-if、v-show、等這些都是系統指令,比如說我想寫一個v-color的指令,但是系統是沒有這個指令的,如果我們直接這樣寫肯定會報錯,所以我們下面就需要自定義指令
Day2----Python學習之路筆記(2)
cell 數據類型的轉換 編碼格式 python3 () shel 不能 索引 png 學習路線: Day1 Day2 Day3 Day4 Day5 ...待續 一、簡單回顧一下昨天的內容 1. 昨天了解到了一些編碼的知識 1.1
Day1----Python學習之路筆記(1)
文件名 常見 python3 3.2 HP lob 計算機硬件 至少 數字 學習路線 Day1 Day2 Day3 Day4 Day5 ...待續 一、了解開發語言 1、高級語言:Python,Java,C++,C#,PHP,
Vue學習之路(九) --- 非父元件之間的通訊
1. 非vuex實現 非父元件之間的通訊 原理是:在父元件或者全域性建立一個事件倉儲eventHub,然後通過$emit 和 $on 實現通訊 1.1 通過在window上繫結eventHub的Vue例項物件,具體實現方法如下: 目錄結構
Netty學習之路(九)-JBoss Marshalling編解碼
JBoss Marshalling 是一個Java物件序列化包,對JDK預設的序列化框架進行了優化,但又保持跟java.io.Serializable介面的相容,同時增加了一些可調的引數和附加的特性。 Marshalling開發環境準備 下載相關的Marshalling類庫:地址,將
Linux 學習之路(九):特殊許可權及終端
特殊許可權及SUID xargs find /etc -size +1M -exec echo {} >> /tmp/etc.largefiles\; find /etc -size +1M | xargs echo >> /tmp/et
Python小白學習之路(九)—【字串格式化】【百分號方式】【format方式】
寫在前面: 最近的事情好像有很多。李詠的離去,讓我覺得很突然,彷彿印象中就是主持節目的他,看著他和哈文的愛情,很是感動。離去,沒有什麼抱怨,只是遺憾。 總會感慨,時光的流逝。 好像真的很快,轉眼間,我都這麼大了。 最近,劉某人總說的一句話,小時候雖然很窮,但是很快樂 而現在,不僅僅是窮,而且還不快樂 想想還真
Nginx學習之路(九)Nginx中的事件驅動過程詳解-----connection事件的註冊過程
在上一篇文章Nginx學習之路(八)Nginx中的事件驅動過程詳解-----以listenfd註冊過程為例中舉了listenfd的註冊過程來說明事件驅動中的事件註冊過程,這是一個簡單的過程,今天來說明下當瀏覽器發起一個http請求時,nginx是如何將這個事件註冊到epoll