) k/ b* E' g' N 深度学习和目标检测系列教程 8-300:目标检测常见的标注工具LabelImg和将xml文件提取图像信息, K% l6 f; I' m# H
图像标注主要用于创建数据集进行图片的标注。本篇博客将推荐一款非常实用的图片标注工具LabelImg,重点介绍其安装使用过程。如果想简单点,请直接下载打包版(下载地址见结尾),无需编译,直接打开即可! 7 S! ]. H4 f L2 c) N" `% H) L n f9 c
3 m2 X& D* R9 z& S
感谢原作者对Github的贡献,博主发现软件已经更新,可以关注最新版本。这个工具是一个用 Python 和 Qt 编写的完整的图形界面。最有意思的是,它的标注信息可以直接转换成XML文件,这和PASCAL VOC和ImageNet使用的XML是一样的。 - W' G$ C7 T3 h- m9 A * L5 `9 M |7 { P F& H n* |9 l4 i, i4 V* g: b* a% v
附注。作者在5月份更新了代码,现在最新版本号是1.3.0,博主亲测,源码在Windows 10和Ubuntu 16.04上正常运行。8 h9 V9 ?/ B% n4 M: w5 ^% g
2 w$ {9 N( o* u2 [
( u4 E, j% t* p; f$ R
具体的安装查看Github教程:https://github.com/wkentaro/labelme/#installation% y9 Q7 f% v7 j$ Q3 h: f
( ^+ Q2 ?$ {# M, v8 d- W
. e# x2 z N( p/ ~
在原作者的github下载源码:https://github.com/tzutalin/labelImg ^& z; P0 f$ ~7 M/ z$ R。解压名为labelImg-master的文件夹,进入当前目录的命令行窗口,输入如下语句依次打开软件。 " \+ i E8 u" V' V3 f9 T$ G9 `& N& e % A' T. g1 E, }8 X# k# s2 q( F' A* F9 C: c, j: V% D C) S
python labelImg.py + U+ a' y' E) h( [4 Q% ]1 ; O. [5 X9 e8 [ 0 Y9 g8 {! v5 g ' e. t7 R0 T8 N V3 ]" B2 W% Z; N; Z$ ~3 ~5 S, U2 E
# {/ W0 ~& H5 y7 @' T
具体使用4 @8 W% { z( j# K r1 a+ C9 y% `
修改默认的XML文件保存位置,使用快捷键“Ctrl+R”,更改为自定义位置,这里的路径一定不能包含中文,否则不会保存。 " @! j, Y; R0 Y5 h4 S& z `5 ]( E$ M! c' J! h7 W) {
8 J. ^' E/ V9 B) l9 K. u
使用notepad++打开源文件夹中的data/predefined_classes.txt,修改默认分类,如person、car、motorcycle这三个分类。( b" c9 @0 l' t" N8 C* `0 V; o9 Q8 s4 i
; }/ m/ M, T- f X2 I |/ Y
) l3 ?4 T. o' R. K" |2 G% u2 F& d
“打开目录”打开图片文件夹,选择第一张图片开始标注,用“创建矩形框”或“Ctrl+N”启动框,点击结束框,双击选择类别。完成一张图片点击“保存”保存后,XML文件已经保存到本地了。单击“下一张图片”转到下一张图片。" r" @0 L+ d* k* ~9 F0 Q
# \" C+ E1 `1 E. X+ x- Q, z1 T
4 ` G5 _: e: k" {& p% Z贴标过程可以随时返回修改,保存的文件会覆盖上一个。8 t( q; i+ I! E$ v- ]8 B
$ Y2 P5 R% L" V' A
5 G7 T# q# A) ^: b
完成注解后,打开XML文件,发现和PASCAL VOC格式一样。 , G: Z9 c3 z, Z+ K9 ` - e2 d" |2 l4 K/ x5 d: f9 s7 b: d1 I- K5 V0 z8 H
将xml文件提取图像信息: [' G7 z8 s" i- r: @2 n
下面列举如何将xml文件提取图像信息,图片保存到image文件夹,xml保存标注内容。图片和标注的文件名字一样的。 0 [# a1 j. w2 t4 A 8 z+ d" ?$ r, |" H+ [. D! X+ m2 T& U2 c) _
" c2 {8 `0 m2 Y+ F) s6 e0 G
! Q3 X! t; \5 f' v* d下面是images图片中的一个。 ; I2 [9 J. X3 E9 ` % \& b6 E4 k$ Z: n7 Z* d0 Y# V: f. e% ]4 v4 F
下面是对应的xml文件。. |5 @% t, s2 C; \1 p' l
# B6 W5 M8 e! W
3 Z. l0 N0 ^0 F. o% w6 D<annotation>( a# q8 U* y1 N |, Q+ a* K
<folder>train</folder> ) R% e9 \7 y- g9 l8 t <filename>apple_30.jpg</filename>' {! G- A0 G' p! A
<path>C:\tensorflow1\models\research\object_detection\images\train\apple_30.jpg</path> 7 e5 D8 {4 E5 g# n <source>0 V0 B1 y. s K H
<database>Unknown</database> ! R1 L2 x: u) y: y4 e# e </source> ' B' d6 [, C! e7 N. E <size>: ]2 A% j: t$ l( p& ~
<width>800</width>% T+ y( v, D* j, t
<height>800</height>% Z, n* K& S. c5 Q; v
<depth>3</depth> # ~# l; k0 f+ V0 ~2 y% | </size># [- n- H2 z* R8 f
<segmented>0</segmented> 0 L- P0 q% b: w' R6 ?1 v' b <object> - F0 s0 `/ S+ _* p <name>apple</name> % F) c) {9 p! \& c <pose>Unspecified</pose>. k/ `! z, K( u, C
<truncated>0</truncated>4 Q2 y' S. M, S0 _/ x9 A2 {
<difficult>0</difficult> a3 b4 Q$ d/ r6 @
<bndbox> ( P8 e) R% B* k) |) A! Q2 p2 @ <xmin>254</xmin>0 f0 e7 C8 Z) O1 D% a# C, t& t
<ymin>163</ymin>4 C; u/ _& ?/ J) a7 [1 Y; M1 I
<xmax>582</xmax>5 V4 R( ?3 k. _
<ymax>487</ymax># _3 ?8 J) X' `- D; {
</bndbox> . w0 D. R0 M& X </object>9 W- B; F6 A* t
<object>5 }6 Q9 K$ F6 j/ M3 ~2 ]
<name>apple</name>; n) I4 i r* y a
<pose>Unspecified</pose> 5 P7 `. N# `! j- } <truncated>0</truncated> ! Y, r( |/ F% U A4 V* G( D& i <difficult>0</difficult> C8 g5 t- ?: k) _6 L* I" C
<bndbox>2 S" W$ e, m( l' y1 P& [2 }
<xmin>217</xmin> / w3 ?2 u O, x0 v n <ymin>448</ymin> 4 r/ {5 X% I4 I$ |9 K <xmax>535</xmax> 0 J. a( r7 K+ T @ <ymax>713</ymax> 1 u- A' W' a4 c, }) w& k </bndbox> " |3 q- q, i- |4 D! g/ ~* S </object> - g/ A, A2 e* M) ^" E1 { <object> ( g/ U4 C9 ]7 Q4 }; T7 r <name>apple</name>% C; ]' F( r# v7 V( O% c( C
<pose>Unspecified</pose>2 p1 n+ g5 B3 s) ?2 E% A& ~
<truncated>1</truncated> - y0 Y5 A% t6 B+ T6 h <difficult>0</difficult> 3 \" f# V) z- p) q- `8 [, W0 B' P <bndbox>. Y& A4 B5 L4 `
<xmin>603</xmin> 5 F. m. }6 P, b8 ` <ymin>470</ymin> ; r( R- A/ J3 \8 l, V <xmax>800</xmax> 3 d3 E, X" F. s <ymax>716</ymax>5 w# P2 `3 Y: o q3 f/ G- u) Q: n
</bndbox> ) m: y7 E$ o# N( u. S* I </object> ! c6 A- ^, |) f' b0 K4 e' Z2 x <object>$ G8 g e7 b. y& G B1 _0 |: r
<name>apple</name> 0 K3 V8 C7 N W/ W0 {6 U' t <pose>Unspecified</pose>% r4 K" G6 X3 H6 `4 l
<truncated>0</truncated> U! T6 Y, [5 f" S( z
<difficult>0</difficult> 6 W% K* L, ]5 |8 ?. p2 f <bndbox>5 H/ T' _/ s! w- i) h o2 J! b# `
<xmin>468</xmin>; Y# U" n6 D* c' q; ^3 \. P0 u- V! {! e
<ymin>179</ymin>% u5 I; O3 Z3 W: M, H& N* n* q
<xmax>727</xmax>( x, e0 _8 _# T9 ~
<ymax>467</ymax> / q; k! F$ r% g+ Z ?7 x/ p2 M </bndbox>; {6 F$ R* G& ]1 s' p( U
</object> 3 l$ S( T, t- |9 y <object>( E7 v2 y8 ^" ^+ v- P ]
<name>apple</name>1 p* i: z1 y) b" u
<pose>Unspecified</pose> ' Q- i0 o% Z$ u/ ^% M <truncated>1</truncated> # s! s8 _# X4 i/ D <difficult>0</difficult>5 l2 \+ i. k3 U+ r
<bndbox>, w4 l2 L1 L' ]
<xmin>1</xmin> 7 R% I! `' h: y* q9 d <ymin>63</ymin>& O( ^4 ^. S5 V Z( }" T. L5 Z/ J4 ~: b
<xmax>308</xmax> - C) L* W3 x: h9 R3 Q A t <ymax>414</ymax>, p+ I! v: v) u5 S* ^
</bndbox> ; K& i" G4 \0 O& G# A( n </object>) T4 [9 W6 r! z8 `: }% G5 j
</annotation> * r' ?3 N6 n4 l6 w1 D% N) ?. K7 R$ n# c7 M% Y
27 S ?8 O+ g9 f' p6 |. S, o& C
3 3 y" C9 E" L4 t7 ^4/ z- O1 L+ h: L3 p8 ]
58 \' O2 ^. I7 a+ H
6; F b8 C9 o2 ]6 N5 Y; C; `
7 $ L2 ?8 l0 L% |( |. l8 1 D( B, |4 W" h( h' l+ Q J9 ( p$ A- S! l( b: @7 e102 t/ M8 \0 H: ~8 P6 e9 ~" K' k
11 0 W2 I8 E: a% l9 j, l, Y$ N/ U12: N8 Q9 o8 q6 D0 J/ o
13 * e8 A6 U9 n: M& x1 N4 N$ d+ p14! Z2 ^6 ^ U$ f, Z9 S* e, J
15 : H# f* x. G4 W16! n6 P+ l; R6 |8 t
178 v% O! V" Q# f# V
185 J( G8 g8 _, C
19 7 l( _1 B2 L4 f/ W2 u- f9 F20 6 \6 Y& i J# m21 ! R. ], ~! {$ q4 w* A22 t4 u, o7 ?! {4 ], s2 Z/ Q' ~
23- ^8 o. a! O$ U
24% ?/ K* r( S0 Z1 p* i- |$ }
25( v' t8 E; k& Y: t) _9 A1 Z& w, M
26 e" O h& i! L
273 s! t3 L* N, f' ~. j
28 $ A9 }7 u5 |+ z }( G6 z29 U5 Z5 }1 w& h' l" D7 H8 b7 F' A
30+ n$ X2 O2 C2 k. a0 ^9 h3 P+ L+ B
31 2 z, |, e- o- ?3 Y+ w: m2 w32 # }& W) o8 P0 w: G5 L33 5 ~( b' ^$ f$ Z34 . p; ]9 J- Q+ F# \" d, i w35' x X3 u2 b# e: f& E4 Z
368 F9 ~3 |7 J L1 B5 ?
37 0 R1 F3 c! }2 {7 Y' a' v8 O38 $ ~: }, a% c1 Z- n! j39/ R7 f! J5 D9 z" {5 Y
40: h- H1 p1 s( B, | T L+ m
41 9 Y* U/ t- s. H; M" Y42 : j8 Z1 V9 I ]; M5 F3 }439 v* k. w* l' y0 z
44) k3 v. c5 A2 A7 A0 v
45+ ]( a& P' _* B# N t
46 k. a& L' |+ z" E- b' X
476 Q4 ~0 N3 K' B8 z* E1 Z
48 6 G- a) {3 W9 T% S4 P" y6 |4 F49, Z8 x; a! T! N- V5 F& q7 b
502 C: Z F6 h* e1 F p8 B4 z" o( G) s
51+ M+ h# M& T3 `/ u ?* f( t
52 . V5 K* ? I" f+ [2 }1 Y4 X6 m: C539 u* d1 i0 z6 J3 j: G
54 & ^ [+ O2 Q( k2 k; f$ P# @3 \55 ) I5 ~3 n% h" i$ V5 u$ @* u56 ; A4 ]' C! `" [- a* |9 Y+ D# b57 5 P3 ~2 n+ f# ]* [0 _58, M9 W$ x. N$ w8 J/ K- A
59 4 h7 f& c- s m& b; v60# j+ ~9 c5 d# O
61 ( A- F" a+ s, |( P62 0 a2 M/ r6 {' G! J% |+ f" ?7 d: w6 r63 , s( |& x9 Q1 C4 q0 ?641 o% H1 R* ?, u; M+ V% {
65# {8 M# k" Z# W/ J) U2 T
66 ' v. g ?9 r+ o3 V' e4 k4 D67# z8 l1 s; ]/ ~! { V+ E
68 & h9 H9 Y: D; D1 d697 r1 N. l! j+ h* k5 m
70 * v0 ^, Z9 E: `* D* t9 D2 V71 ! s* J6 Y0 a6 D8 H- w: p# R72: o+ I3 ?5 }+ m7 F6 {# w
73. J0 l# K( M S6 U( F
74 + O& @# ~7 @, J7 M% l. n1 Q# ^将xml文件提取图像信息,主要使用xml和opencv,基于torch提取,代码比较凌乱。 ) Y4 \/ K0 e1 p/ n% ?* H2 h$ b& R, w# l0 a9 A% g
7 L; L' o r, T! L; h
import os 0 C1 d+ @) t. ^0 Dimport numpy as np . L6 i @3 Y9 Q! d/ Pimport cv2& A' g$ j$ o0 A1 y; G' l
import torch! U0 ^5 t- H d7 @
import matplotlib.patches as patches9 i+ x0 }$ m) d; M
import albumentations as A $ a0 A6 w( s. H! h& \from albumentations.pytorch.transforms import ToTensorV2$ }3 u7 C6 O0 ?& C% Q% ? ^1 V/ f
from matplotlib import pyplot as plt w; B3 W' I1 X& E- bfrom torch.utils.data import Dataset, }+ Z8 E& O& I& L
from xml.etree import ElementTree as et% L7 ?( E/ g- x( l' u* g- k7 s
from torchvision import transforms as torchtrans, c ]: C# t& h q- e; w
9 ^; ~! n) w2 e1 {4 z5 _/ T# _0 j1 K8 D6 P$ z
# defining the files directory and testing directory* C$ @8 h5 S" z7 K2 k6 V
train_image_dir = 'train/train/image' * e1 }5 @. g6 v0 s' U% Ptrain_xml_dir = 'train/train/xml'0 B4 [; o4 I& m
# test_image_dir = 'test/test/image'& Z# P2 t$ m- S! B+ \, B4 s
# test_xml_dir = 'test/test/xml') e) r& z, i1 v; t
2 x, v) Y# e& M, ?/ j
( i# M5 f) x- `- i
class FruitImagesDataset(Dataset): 3 t3 ?1 w% ~$ \0 L6 ^9 o. o Y4 t$ R: b' y, z7 Y" f0 j, ]3 s
" G' r4 L* r" ]# o) ] def __init__(self, image_dir, xml_dir, width, height, transforms=None):" D1 V4 u9 O2 J! ]0 P. n+ l/ c( N. s, P
self.transforms = transforms p6 J+ j) k& o6 a( w( A: U, u
self.image_dir = image_dir * T, H' d$ |4 ?& p" S self.xml_dir = xml_dir/ o! }/ A, }: a$ l$ P7 {. W% U
self.height = height% m) L% S% x6 k
self.width = width: M2 R! H$ y+ C% {
( A9 X% J+ K0 p
$ P$ z( a* x. y6 }5 X
# sorting the images for consistency, k2 K: f6 G# Z+ E+ b
# To get images, the extension of the filename is checked to be jpg: N4 o. ^2 Z& m5 x" p, u8 F, L* Q
self.imgs = [image for image in os.listdir(self.image_dir) 1 k* G) r( N4 T" f if image[-4:] == '.jpg'] / o8 D2 o, ~/ p# d1 u4 | }7 H self.xmls = [xml for xml in os.listdir(self.xml_dir)9 I( r' y0 f3 h1 {( [
if xml[-4:] == '.xml']+ |1 S" p1 U( R6 ]8 K& J6 \
. F! S- V2 i2 h0 l. o0 x& v% }2 }' W
# classes: 0 index is reserved for background+ M7 D* C( ~5 d3 @% G4 d
self.classes = ['apple', 'banana', 'orange'] 6 _( T- P) I0 Q9 Y& E: Z2 J/ d5 [2 n( H- B6 l! Y, R
/ a5 d5 C, J: s. z: n
def __getitem__(self, idx):/ P* v7 i# G# O% m. ]# F
K2 s0 ?% {! O2 a7 n6 x) R$ @# R0 P( s& S
img_name = self.imgs[idx] K4 k& o* o$ ]% P; O
image_path = os.path.join(self.image_dir, img_name) 7 Z6 `$ W0 e: r# _- f 8 g {' X0 F' P6 n$ {) R 0 Z/ E; D! W4 v- s* A # reading the images and converting them to correct size and color, K9 \" U& p* {3 g- W. ]
img = cv2.imread(image_path)" a$ L% F* S/ U! T+ C8 _
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB).astype(np.float32)% e2 S, X9 j( o. c2 [+ X
img_res = cv2.resize(img_rgb, (self.width, self.height), cv2.INTER_AREA) : B2 r# [. y L z& U # diving by 255& w3 Y' @& U$ l2 \, x
img_res /= 255.0$ D' J* w! q- V% b; ~
9 P. l+ c- b5 H ^( P9 e( Z 2 |9 W$ ~4 l3 f: f. s; U7 U # annotation file , s, u0 T- Y- h |* i1 t* S annot_filename = img_name[:-4] + '.xml' 2 r9 N, C' p# T" [6 D" I2 ` annot_file_path = os.path.join(self.xml_dir, annot_filename) ( _4 [8 U7 E& Z# M# K/ r , B a+ J* A: S4 V" H+ ] ' R( ]4 ^. b. a6 N0 e) U2 H6 c boxes = [] : @! @+ I' @# d& @' Q8 c0 A labels = [] ) J, }* [- ~; v7 Q- H3 C tree = et.parse(annot_file_path) & K, M2 G& g, K4 J; H% X! l root = tree.getroot() & B* V3 Z5 `+ D* t4 I + |& \ I# x) j \/ E5 M: j. G* `1 ?9 g# x; o
# cv2 image gives size as height x width+ m. i0 R9 i R9 l
wt = img.shape[1] 3 L1 Y9 W* Z6 o# G+ u, C ht = img.shape[0]$ V6 h, ^- w/ v$ r
6 e' y& K+ A k; f, {% I - v- ] @( s3 I0 W+ n # box coordinates for xml files are extracted and corrected for image size given9 S. f* I# `0 |! m2 s
for member in root.findall('object'): " ^4 D& |6 f% O+ l- W& P% L labels.append(self.classes.index(member.find('name').text)) 0 e. J; M. Y5 W( I. { " M- }6 v9 }- W \3 C! v/ t& I * K9 Z, g- W% E # bounding box/ ]6 f5 L7 `9 ^
xmin = int(member.find('bndbox').find('xmin').text) 2 ^$ z* t( q$ m) V$ D% Z xmax = int(member.find('bndbox').find('xmax').text), i& M8 F, n7 @- B4 E, w
2 |) ]: A @+ ~* Q4 L
. N) e/ r6 |0 P, c9 B ymin = int(member.find('bndbox').find('ymin').text) 2 d# X7 K6 Z0 M ymax = int(member.find('bndbox').find('ymax').text)4 s0 c0 S/ u/ M/ p2 P% g
( a+ `% L2 C: r0 K1 C 9 F9 D' a; K8 c' ~* d C, J6 S xmin_corr = (xmin / wt) * self.width. V& L; a9 V1 `8 \3 p
xmax_corr = (xmax / wt) * self.width / a; @+ \, n4 j ymin_corr = (ymin / ht) * self.height' I4 M! p7 l+ b( z2 O0 j
ymax_corr = (ymax / ht) * self.height . U) O. Z7 ^* c% R$ a boxes.append([xmin_corr, ymin_corr, xmax_corr, ymax_corr])$ U" i7 f1 R. y% B; ~" B
5 f! }* X, E/ }
( M7 b. O5 Y/ h% F
# convert boxes into a torch.Tensor 9 U# a# b$ @6 d- ?& J boxes = torch.as_tensor(boxes, dtype=torch.float32) # w" D1 V+ f% m* q" J) _2 q9 z' v3 T9 S
3 J) x" F$ W# N" e* ?- ? # getting the areas of the boxes% |( P$ V2 Q. T( D+ @' A% N# W
area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0]) X+ T( B; A2 {7 P# F$ T
, q& z+ R" `, r! Y7 T5 u, J0 P
}- X7 f- o) c. S$ T
# suppose all instances are not crowd 8 u/ q5 o* B) u8 n% L iscrowd = torch.zeros((boxes.shape[0],), dtype=torch.int64) Z. }8 c9 y0 ?4 p+ W* @$ j2 ?% W2 C9 | U