python目標(biāo)檢測(cè)實(shí)現(xiàn)黑花屏分類(lèi)任務(wù)示例
背景
視頻幀的黑、花屏的檢測(cè)是視頻質(zhì)量檢測(cè)中比較重要的一部分,傳統(tǒng)做法是由測(cè)試人員通過(guò)肉眼來(lái)判斷視頻中是否有黑、花屏的現(xiàn)象,這種方式不僅耗費(fèi)人力且效率較低。
為了進(jìn)一步節(jié)省人力、提高效率,一種自動(dòng)的檢測(cè)方法是大家所期待的。目前,通過(guò)分類(lèi)網(wǎng)絡(luò)模型對(duì)視頻幀進(jìn)行分類(lèi)來(lái)自動(dòng)檢測(cè)是否有黑、花屏是比較可行且高效的。
然而,在項(xiàng)目過(guò)程中,視頻幀數(shù)據(jù)的收集比較困難,數(shù)據(jù)量較少,部分花屏和正常屏之間差異不夠明顯,導(dǎo)致常用的分類(lèi)算法難以滿(mǎn)足項(xiàng)目對(duì)分類(lèi)準(zhǔn)確度的要求。
因此本文嘗試了一種利用目標(biāo)檢測(cè)算法實(shí)現(xiàn)分類(lèi)的方式,幫助改善單純的分類(lèi)的算法效果不夠理想的問(wèn)題。
核心技術(shù)與架構(gòu)圖
一般分類(lèi)任務(wù)的流程如下圖,首先需要收集數(shù)據(jù),構(gòu)成數(shù)據(jù)集;
并為每一類(lèi)數(shù)據(jù)定義一個(gè)類(lèi)型標(biāo)簽,例如:0、1、2;再選擇一個(gè)合適的分類(lèi)網(wǎng)絡(luò)進(jìn)行分類(lèi)模型的訓(xùn)練,圖像分類(lèi)的網(wǎng)絡(luò)有很多,常見(jiàn)的有 VggNet, ResNet,DenseNet 等;
最后用訓(xùn)練好的模型對(duì)新的數(shù)據(jù)進(jìn)行預(yù)測(cè),輸出新數(shù)據(jù)的類(lèi)別。

目標(biāo)檢測(cè)任務(wù)的流程不同于分類(lèi)任務(wù),其在定義類(lèi)別標(biāo)簽的時(shí)候還需要對(duì)目標(biāo)位置進(jìn)行標(biāo)注;
目標(biāo)檢測(cè)的方法也有很多,例如 Fast R-CNN, SSD,YOLO 等;
模型訓(xùn)練的中間過(guò)程也比分類(lèi)模型要復(fù)雜,其輸出一般為目標(biāo)的位置、目標(biāo)置信度以及分類(lèi)結(jié)果。

由于分類(lèi)算法依賴(lài)于一定量的數(shù)據(jù),在項(xiàng)目實(shí)踐中,數(shù)據(jù)量較少或圖像類(lèi)間差異較小時(shí),傳統(tǒng)分類(lèi)算法效果不一定能滿(mǎn)足項(xiàng)目需求。這時(shí),不妨考慮用目標(biāo)檢測(cè)的方式來(lái)做 ‘分類(lèi)’。
接下來(lái)以 Yolov5 為例來(lái)介紹如何將目標(biāo)檢測(cè)框架用于實(shí)現(xiàn)單純的分類(lèi)任務(wù)。
技術(shù)實(shí)現(xiàn)
除了分類(lèi)之外,目標(biāo)檢測(cè)還可以從自然圖像中的大量預(yù)定義類(lèi)別中識(shí)別出目標(biāo)實(shí)例的位置。
大家可能會(huì)考慮目標(biāo)檢測(cè)模型用于分類(lèi)是不是過(guò)于繁瑣或者用目標(biāo)檢測(cè)框架來(lái)做單純的分類(lèi)對(duì)代碼的修改比較復(fù)雜。
這里,我們將用一種非常簡(jiǎn)單的方式直接在數(shù)據(jù)標(biāo)注和輸出內(nèi)容上稍作修改就能實(shí)現(xiàn)單純的分類(lèi)了。接下來(lái)將介紹一下具體實(shí)現(xiàn)方法:
1.數(shù)據(jù)的標(biāo)注
實(shí)現(xiàn)目標(biāo)檢測(cè)時(shí),需要對(duì)數(shù)據(jù)中的目標(biāo)進(jìn)行標(biāo)注,這一過(guò)程是十分繁瑣的。但在用于純粹的分類(lèi)上可以將這一繁瑣過(guò)程簡(jiǎn)單化,無(wú)需手動(dòng)標(biāo)注,直接將整張圖作為我們的目標(biāo),目標(biāo)中心也就是圖像的中心點(diǎn)。
只需讀取整張圖像,獲得其長(zhǎng)、寬以及中心點(diǎn)的坐標(biāo)就可以完成標(biāo)注了。并定義好類(lèi)別標(biāo)簽,正常屏為 0,花屏為:1,黑屏為 2。具體實(shí)現(xiàn)如下:
OBJECT_DICT = {"Normalscreen": 0, "Colorfulscreen": 1, "Blackscreen": 2}
def parse_json_file(image_path):
imageName = os.path.basename(image_path).split('.')[0]
img = cv2.imread(image_path)
size = img.shape
label = image_path.split('/')[4].split('\\')[0]
label = OBJECT_DICT.get(label)
imageWidth = size[0]
imageHeight = size[1]
label_dict = {}
xmin, ymin = (0, 0)
xmax, ymax = (imageWidth, imageHeight)
xcenter = (xmin + xmax) / 2
xcenter = xcenter / float(imageWidth)
ycenter = (ymin + ymax) / 2
ycenter = ycenter / float(imageHeight)
width = ((xmax - xmin) / float(imageWidth))
heigt = ((ymax - ymin) / float(imageHeight))
label_dict.update({label: [str(xcenter), str(ycenter), str(width), str(heigt)]})
label_dict = sorted(label_dict.items(), key=lambda x: x[0])
return imageName, label_dict
2.訓(xùn)練過(guò)程
該過(guò)程與目標(biāo)檢測(cè)的訓(xùn)練過(guò)程一致,不需要進(jìn)行大的修改,只需要根據(jù)數(shù)據(jù)集的特性對(duì)參數(shù)進(jìn)行調(diào)整。
# 加載數(shù)據(jù),獲取訓(xùn)練集、測(cè)試集圖片路徑
with open(opt.data) as f:
data_dict = yaml.load(f, Loader=yaml.FullLoader)
with torch_distributed_zero_first(rank):
check_dataset(data_dict)
train_path = data_dict['train']
test_path = data_dict['val']
Number_class, names = (1, ['item']) if opt.single_cls else (int(data_dict['nc']), data_dict['names'])
# 創(chuàng)建模型
model = Model(opt.cfg, ch=3, nc=Number_class).to(device)
# 學(xué)習(xí)率的設(shè)置
lf = lambda x: ((1 + math.cos(x * math.pi / epochs)) / 2) * (1 - hyp['lrf']) + hyp['lrf']
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)
# 訓(xùn)練
for epoch in range(start_epoch, epochs):
model.train()
3.損失的計(jì)算
損失由三部分組成,邊框損失,目標(biāo)損失,分類(lèi)損失,具體如下:
def compute_loss(p, targets, model):
device = targets.device
loss_cls, loss_box, loss_obj = torch.zeros(1, device=device), torch.zeros(1, device=device), torch.zeros(1, device=device)
tcls, tbox, indices, anchors = build_targets(p, targets, model)
h = model.hyp
# 定義損失函數(shù)
BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.Tensor([h['cls_pw']])).to(device)
BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.Tensor([h['obj_pw']])).to(device)
cp, cn = smooth_BCE(eps=0.0)
# 損失
nt = 0
np = len(p)
balance = [4.0, 1.0, 0.4] if np == 3 else [4.0, 1.0, 0.4, 0.1]
for i, pi in enumerate(p):
image, anchor, gridy, gridx = indices[i]
tobj = torch.zeros_like(pi[..., 0], device=device)
n = image.shape[0]
if n:
nt += n # 計(jì)算目標(biāo)
ps = pi[anchor, image, gridy, gridx]
pxy = ps[:, :2].sigmoid() * 2. - 0.5
pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]
predicted_box = torch.cat((pxy, pwh), 1).to(device) giou = bbox_iou(predicted_box.T, tbox[i], x1y1x2y2=False, CIoU=True)
loss_box += (1.0 - giou).mean()
tobj[image, anchor, gridy, gridx] = (1.0 - model.gr) + model.gr * giou.detach().clamp(0).type(tobj.dtype)
if model.nc > 1:
t = torch.full_like(ps[:, 5:], cn, device=device)
t[range(n), tcls[i]] = cp
loss_cls += BCEcls(ps[:, 5:], t)
loss_obj += BCEobj(pi[..., 4], tobj) * balance[i]
s = 3 / np
loss_box *= h['giou'] * s
loss_obj *= h['obj'] * s * (1.4 if np == 4 else 1.)
loss_cls *= h['cls'] * s
bs = tobj.shape[0]
loss = loss_box + loss_obj + loss_cls
return loss * bs, torch.cat((loss_box, loss_obj, loss_cls, loss)).detach()
4.對(duì)輸出內(nèi)容的處理
進(jìn)行預(yù)測(cè)時(shí),會(huì)得到所有檢測(cè)到的目標(biāo)的位置(x,y,w,h),objectness 置信度和分類(lèi)結(jié)果。由于最終目的是對(duì)整張圖進(jìn)行分類(lèi),可以忽略位置信息,重點(diǎn)考慮置信度和分類(lèi)結(jié)果:將檢測(cè)到的目標(biāo)類(lèi)別作為分類(lèi)結(jié)果,如果同時(shí)檢測(cè)出多個(gè)目標(biāo),可以將置信度最大的目標(biāo)的類(lèi)別作為分類(lèi)結(jié)果。代碼如下:
def detect(opt,img):
out, source, weights, view_img, save_txt, imgsz = \
opt.output, img, opt.weights, opt.view_img, opt.save_txt, opt.img_size
device = select_device(opt.device)
half = device.type != 'cpu'
model = experimental.attempt_load(weights, map_location=device)
imgsz = check_img_size(imgsz, s=model.stride.max())
if half:
model.half()
img = letterbox(img)[0]
img = img[:, :, ::-1].transpose(2, 0, 1)
img = np.ascontiguousarray(img)
img_warm = torch.zeros((1, 3, imgsz, imgsz), device=device)
_ = model(img_warm.half() if half else img_warm) if device.type != 'cpu' else None
img = torch.from_numpy(img).to(device)
img = img.half() if half else img.float()
img /= 255.0
if img.ndimension() == 3:
img = img.unsqueeze(0)
pred = model(img, augment=opt.augment)[0]
# 應(yīng)用非極大值抑制
pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes, agnostic=opt.agnostic_nms)
# 處理檢測(cè)的結(jié)果
for i, det in enumerate(pred):
if det is not None and len(det):
det[:, :4] = scale_coords(img.shape[2:], det[:, :4], img.shape).round()
all_conf = det[:, 4]
if len(det[:, -1]) > 1:
ind = torch.max(all_conf, 0)[1]
c = torch.take(det[:, -1], ind)
detect_class = int(c)
else:
for c in det[:, -1]:
detect_class = int(c)
return detect_class
效果展示
為了將視頻幀進(jìn)行黑、花屏分類(lèi),測(cè)試人員根據(jù)經(jīng)驗(yàn)將屏幕分為正常屏(200 張)、花屏(200 張)和黑屏(200 張)三類(lèi),其中正常屏幕標(biāo)簽為 0,花屏的標(biāo)簽為 1,黑屏的標(biāo)簽為 2。

為了進(jìn)一步說(shuō)明該方法的有效性,我們將基于 Yolov5 的 ‘分類(lèi)’ 效果與 ResNet 分類(lèi)效果做了對(duì)比。根據(jù)測(cè)試人員對(duì) ResNet 分類(lèi)效果的反饋來(lái)看,ResNet 模型容易將正常屏與花屏錯(cuò)誤分類(lèi),例如,下圖被測(cè)試人員定義為正常屏:

ResNet 的分類(lèi)結(jié)果為 1,即為花屏,顯然,這不是我們想要的結(jié)果。

基于 Yolov5 的分類(lèi)結(jié)果為 0,即為正常屏,這是我們所期待的結(jié)果。

同時(shí),通過(guò)對(duì)一批測(cè)試數(shù)據(jù)的分類(lèi)效果來(lái)看,Yolov5 的分類(lèi)效果比 ResNet 的分類(lèi)準(zhǔn)確度更高,ResNet 的分類(lèi)準(zhǔn)確率為 88%,而基于 Yolov5 的分類(lèi)準(zhǔn)確率高達(dá) 97%。
總結(jié)
對(duì)于較小數(shù)據(jù)集的黑、花屏的分類(lèi)問(wèn)題,采用 Yolov5 來(lái)實(shí)現(xiàn)分類(lèi)相較于 ResNet 的分類(lèi)效果會(huì)更好一些。當(dāng)我們?cè)谧鰣D像分類(lèi)任務(wù)時(shí),純粹的分類(lèi)算法不能達(dá)到想要的效果時(shí),不妨嘗試一下用目標(biāo)檢測(cè)框架來(lái)分類(lèi)吧!雖然過(guò)程稍微復(fù)雜一些,但可能會(huì)有不錯(cuò)的效果。
目前目標(biāo)檢測(cè)框架有很多,用它們完成分類(lèi)任務(wù)的處理方式大致和本文所描述的類(lèi)似,可以根據(jù)數(shù)據(jù)集的特征選擇合適目標(biāo)檢測(cè)架構(gòu)來(lái)實(shí)現(xiàn)分類(lèi)。
本文主要介紹了如何將現(xiàn)有的目標(biāo)檢測(cè)框架直接用于單純的圖像分類(lèi)任務(wù),當(dāng)然,為了使得結(jié)構(gòu)更簡(jiǎn)潔,也可以將目標(biāo)檢測(cè)中的分類(lèi)網(wǎng)絡(luò)提取出來(lái)用于分類(lèi),更多關(guān)于python目標(biāo)檢測(cè)黑花屏分類(lèi)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python中的條件判斷語(yǔ)句與循環(huán)語(yǔ)句用法小結(jié)
這篇文章主要介紹了Python中的條件判斷語(yǔ)句與循環(huán)語(yǔ)句用法小結(jié),條件語(yǔ)句和循環(huán)語(yǔ)句是Python程序流程控制的基礎(chǔ),需要的朋友可以參考下2016-03-03
python使用python-docx處理word的方法示例
本文介紹了python-docx模塊,用于自動(dòng)化操作Word文檔,包括創(chuàng)建、寫(xiě)入和讀取Word文檔等方法,具有一定的參考價(jià)值,感興趣的可以了解一下2025-01-01
Python多線(xiàn)程編程(六):可重入鎖RLock
這篇文章主要介紹了Python多線(xiàn)程編程(六):可重入鎖RLock,本文直接給出使用實(shí)例,然后講解如何使用RLock避免死鎖,需要的朋友可以參考下2015-04-04
使用Python實(shí)現(xiàn)將數(shù)據(jù)寫(xiě)入Excel工作表
在數(shù)據(jù)處理和報(bào)告生成等工作中,Excel?表格是一種常見(jiàn)且廣泛使用的工具,本文中將介紹如何使用?Python?寫(xiě)入數(shù)據(jù)到?Excel?表格,并提供更高效和準(zhǔn)確的?Excel?表格數(shù)據(jù)寫(xiě)入方案,需要的可以參考下2024-01-01
利用Python實(shí)現(xiàn)Windows下的鼠標(biāo)鍵盤(pán)模擬的實(shí)例代碼
本篇文章主要介紹了利用Python實(shí)現(xiàn)Windows下的鼠標(biāo)鍵盤(pán)模擬的實(shí)例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
Python3使用requests模塊實(shí)現(xiàn)顯示下載進(jìn)度的方法詳解
這篇文章主要介紹了Python3使用requests模塊實(shí)現(xiàn)顯示下載進(jìn)度的方法,結(jié)合實(shí)例形式分析了Python3中requests模塊的配置、使用及顯示進(jìn)度條類(lèi)的相關(guān)定義方法,需要的朋友可以參考下2019-02-02
python實(shí)現(xiàn)超簡(jiǎn)單端口轉(zhuǎn)發(fā)的方法
這篇文章主要介紹了python實(shí)現(xiàn)超簡(jiǎn)單端口轉(zhuǎn)發(fā)的方法,實(shí)例分析了Python同構(gòu)socket實(shí)現(xiàn)端口轉(zhuǎn)發(fā)的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03

