python實(shí)現(xiàn)簡(jiǎn)單圖片物體標(biāo)注工具
本文實(shí)例為大家分享了python實(shí)現(xiàn)簡(jiǎn)單圖片物體標(biāo)注工具的具體代碼,供大家參考,具體內(nèi)容如下
# coding: utf-8
"""
物體檢測(cè)標(biāo)注小工具
基本思路:
對(duì)要標(biāo)注的圖像建立一個(gè)窗口循環(huán),然后每次循環(huán)的時(shí)候?qū)D像進(jìn)行一次復(fù)制,
鼠標(biāo)在畫(huà)面上畫(huà)框的操作、畫(huà)好的框的相關(guān)信息在全局變量中保存,
并且在每個(gè)循環(huán)中根據(jù)這些信息,在復(fù)制的圖像上重新畫(huà)一遍,然后顯示這份復(fù)制的圖像。
簡(jiǎn)化的設(shè)計(jì)過(guò)程:
1、輸入是一個(gè)文件夾的路徑,包含了所需標(biāo)注物體框的圖片。
如果圖片中標(biāo)注了物體,則生成一個(gè)相同名稱加額外后綴_bbox的文件,來(lái)保存標(biāo)注信息。
2、標(biāo)注的方式:按下鼠標(biāo)左鍵選擇物體框的左上角,松開(kāi)鼠標(biāo)左鍵選擇物體框的右下角,
按下鼠標(biāo)右鍵刪除上一個(gè)標(biāo)注好的物體框。
所有待標(biāo)注物體的類別和標(biāo)注框顏色由用戶自定義。
如果沒(méi)有定義則默認(rèn)只標(biāo)注一種物體,定義該物體名稱為Object。
3、方向鍵 ← 和 → 鍵用來(lái)遍歷圖片, ↑ 和 ↓ 鍵用來(lái)選擇當(dāng)前要標(biāo)注的物體,
Delete鍵刪除一種臟圖片和對(duì)應(yīng)的標(biāo)注信息。
自定義標(biāo)注物體和顏色的信息用一個(gè)元組表示
第一個(gè)元素表示物體名字
第二個(gè)元素表示BGR顏色的tuple或者代表標(biāo)注框坐標(biāo)的元祖
利用repr()保存和eval()讀取
"""
"""
一些說(shuō)明:
1. 標(biāo)注相關(guān)的物體標(biāo)簽文件即 .labels 結(jié)尾的文件,需要與所選文件夾添加到同一個(gè)根目錄下
一定要注意這一點(diǎn),否則無(wú)法更新標(biāo)注物體的類型標(biāo)簽,致使從始至終都只有一個(gè)默認(rèn)物體出現(xiàn)
我就是這個(gè)原因,拖了兩三天才整好,當(dāng)然也順便仔細(xì)的讀了這篇代碼。同時(shí)也學(xué)習(xí)了@staticmethod以及相應(yīng)Python的decorator的知識(shí)。
可以說(shuō),在曲折中前進(jìn)才是棒的。
2. .labels文件為預(yù)設(shè)物體標(biāo)簽文件,其內(nèi)容具體格式為:
'object1', (B, G, R)
'object2', (B, G, R)
'object3', (B, G, R)……
具體見(jiàn)文后圖片。
3. 最后生成的標(biāo)注文件,在文后會(huì)有,到時(shí)再進(jìn)行解釋。
"""
import os
import cv2
# tkinter是Python內(nèi)置的簡(jiǎn)單GUI庫(kù),實(shí)現(xiàn)打開(kāi)文件夾、確認(rèn)刪除等操作十分方便
from tkMessageBox import askyesno
# 定義標(biāo)注窗口的默認(rèn)名稱
WINDOW_NAME = 'Simple Bounding Box Labeling Tool'
# 定義畫(huà)面刷新幀率
FPS = 24
# 定義支持的圖像格式
SUPPORTED_FORMATS = ['jpg', 'jpeg', 'png']
# 定義默認(rèn)物體框的名字為Object,顏色為藍(lán)色,當(dāng)沒(méi)有用戶自定義物體時(shí),使用該物體
DEFAULT_COLOR = {'Object': (255, 0, 0)}
# 定義灰色,用于信息顯示的背景和未定義物體框的顯示
COLOR_GRAY = (192, 192, 192)
# 在圖像下方多處BAR_HEIGHT的區(qū)域,用于顯示信息
BAR_HEIGHT = 16
# 上下左右,DELETE鍵對(duì)應(yīng)的cv2.waitKey()函數(shù)的返回值
KEY_UP = 2490368
KEY_DOWN = 2621440
KEY_LEFT = 2424832
KEY_RIGHT = 2555904
KEY_DELETE = 3014656
# 空鍵用于默認(rèn)循環(huán)
KEY_EMPTY = 0
get_bbox_name = '{}.bbox'.format
# 定義物體框標(biāo)注工具類
class SimpleBBoxLabeling:
def __init__(self, data_dir, fps=FPS, windown_name=WINDOW_NAME):
self._data_dir = data_dir
self.fps = fps
self.window_name = windown_name if windown_name else WINDOW_NAME
# pt0 是正在畫(huà)的左上角坐標(biāo), pt1 是鼠標(biāo)所在坐標(biāo)
self._pt0 = None
self._pt1 = None
# 表明當(dāng)前是否正在畫(huà)框的狀態(tài)標(biāo)記
self._drawing = False
# 當(dāng)前標(biāo)注物體的名稱
self._cur_label = None
# 當(dāng)前圖像對(duì)應(yīng)的所有已標(biāo)注框
self._bboxes = []
# 如果有用戶自己定義的標(biāo)注信息則讀取,否則使用默認(rèn)的物體和顏色
label_path = '{}.labels'.format(self._data_dir)
self.label_colors = DEFAULT_COLOR if not os.path.exists(label_path) else self.load_labels(label_path)
# self.label_colors = self.load_labels(label_path)
# 獲取已經(jīng)標(biāo)注的文件列表和未標(biāo)注的文件列表
imagefiles = [x for x in os.listdir(self._data_dir) if x[x.rfind('.') + 1:].lower() in SUPPORTED_FORMATS]
labeled = [x for x in imagefiles if os.path.exists(get_bbox_name(x))]
to_be_labeled = [x for x in imagefiles if x not in labeled]
# 每次打開(kāi)一個(gè)文件夾,都自動(dòng)從還未標(biāo)注的第一張開(kāi)始
self._filelist = labeled + to_be_labeled
self._index = len(labeled)
if self._index > len(self._filelist) - 1:
self._index = len(self._filelist) - 1
# 鼠標(biāo)回調(diào)函數(shù)
def _mouse_ops(self, event, x, y, flags, param):
# 按下左鍵,坐標(biāo)為左上角,同時(shí)表示開(kāi)始畫(huà)框,改變drawing,標(biāo)記為T(mén)rue
if event == cv2.EVENT_LBUTTONDOWN:
self._drawing = True
self._pt0 = (x, y)
# 松開(kāi)左鍵,表明畫(huà)框結(jié)束,坐標(biāo)為有效較并保存,同時(shí)改變drawing,標(biāo)記為False
elif event == cv2.EVENT_LBUTTONUP:
self._drawing = False
self._pt1 = (x, y)
self._bboxes.append((self._cur_label, (self._pt0, self._pt1)))
# 實(shí)時(shí)更新右下角坐標(biāo)
elif event == cv2.EVENT_MOUSEMOVE:
self._pt1 = (x, y)
# 按下鼠標(biāo)右鍵刪除最近畫(huà)好的框
elif event == cv2.EVENT_RBUTTONUP:
if self._bboxes:
self._bboxes.pop()
# 清除所有標(biāo)注框和當(dāng)前狀態(tài)
def _clean_bbox(self):
self._pt0 = None
self._pt1 = None
self._drawing = False
self._bboxes = []
# 畫(huà)標(biāo)注框和當(dāng)前信息的函數(shù)
def _draw_bbox(self, img):
# 在圖像下方多出BAR_HEIGHT的區(qū)域,顯示物體信息
h, w = img.shape[:2]
canvas = cv2.copyMakeBorder(img, 0, BAR_HEIGHT, 0, 0, cv2.BORDER_CONSTANT, value=COLOR_GRAY)
# 正在標(biāo)注的物體信息,如果鼠標(biāo)左鍵已經(jīng)按下,則像是兩個(gè)點(diǎn)坐標(biāo),否則顯示當(dāng)前待標(biāo)注物體的名
label_msg = '{}: {}, {}'.format(self._cur_label, self._pt0, self._pt1) \
if self._drawing \
else 'Current label: {}'.format(self._cur_label)
# 顯示當(dāng)前文件名,文件個(gè)數(shù)信息
msg = '{}/{}: {} | {}'.format(self._index + 1, len(self._filelist), self._filelist[self._index], label_msg)
cv2.putText(canvas, msg, (1, h+12), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)
# 畫(huà)出已經(jīng)標(biāo)好的框和對(duì)應(yīng)名字
for label, (bpt0, bpt1) in self._bboxes:
label_color = self.label_colors[label] if label in self.label_colors else COLOR_GRAY
cv2.rectangle(canvas, bpt0, bpt1, label_color, thickness=2)
cv2.putText(canvas, label, (bpt0[0]+3, bpt0[1]+15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, label_color, 2)
# 畫(huà)正在標(biāo)注的框和對(duì)應(yīng)名字
if self._drawing:
label_color = self.label_colors[self._cur_label] if self._cur_label in self.label_colors else COLOR_GRAY
if (self._pt1[0] >= self._pt0[0]) and (self._pt1[1] >= self._pt1[0]):
cv2.rectangle(canvas, self._pt0, self._pt1, label_color, thickness=2)
cv2.putText(canvas, self._cur_label, (self._pt0[0] + 3, self._pt0[1] + 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, label_color, 2)
return canvas
# 利用repr()函數(shù)導(dǎo)出標(biāo)注框數(shù)據(jù)到文件
@staticmethod
def export_bbox(filepath, bboxes):
if bboxes:
with open(filepath, 'w') as f:
for bbox in bboxes:
line = repr(bbox) + '\n'
f.write(line)
elif os.path.exists(filepath):
os.remove(filepath)
# 利用eval()函數(shù)讀取標(biāo)注框字符串到數(shù)據(jù)
@staticmethod
def load_bbox(filepath):
bboxes = []
with open(filepath, 'r') as f:
line = f.readline().rstrip()
while line:
bboxes.append(eval(line))
line = f.readline().rstrip()
return bboxes
# 利用eval()函數(shù)讀取物體及對(duì)應(yīng)顏色信息到數(shù)據(jù)
@staticmethod
def load_labels(filepath):
label_colors = {}
with open(filepath, 'r') as f:
line = f.readline().rstrip()
while line:
label, color = eval(line)
label_colors[label] = color
line = f.readline().rstrip()
print label_colors
return label_colors
# 讀取圖像文件和對(duì)應(yīng)標(biāo)注框信息(如果有的話)
@staticmethod
def load_sample(filepath):
img = cv2.imread(filepath)
bbox_filepath = get_bbox_name(filepath)
bboxes = []
if os.path.exists(bbox_filepath):
bboxes = SimpleBBoxLabeling.load_bbox(bbox_filepath)
return img, bboxes
# 導(dǎo)出當(dāng)前標(biāo)注框信息并清空
def _export_n_clean_bbox(self):
bbox_filepath = os.sep.join([self._data_dir, get_bbox_name(self._filelist[self._index])])
self.export_bbox(bbox_filepath, self._bboxes)
self._clean_bbox()
# 刪除當(dāng)前樣本和對(duì)應(yīng)的標(biāo)注框信息
def _delete_current_sample(self):
filename = self._filelist[self._index]
filepath = os.sep.join([self._data_dir, filename])
if os.path.exists(filepath):
os.remove(filepath)
filepath = get_bbox_name(filepath)
if os.path.exists(filepath):
os.remove(filepath)
self._filelist.pop(self._index)
print('{} is deleted!'.format(filename))
# 開(kāi)始OpenCV窗口循環(huán)的方法,程序的主邏輯
def start(self):
# 之前標(biāo)注的文件名,用于程序判斷是否需要執(zhí)行一次圖像讀取
last_filename = ''
# 標(biāo)注物體在列表中的下標(biāo)
label_index = 0
# 所有標(biāo)注物體名稱的列表
labels = self.label_colors.keys()
# 帶標(biāo)注物體的種類數(shù)
n_labels = len(labels)
# 定義窗口和鼠標(biāo)回調(diào)
cv2.namedWindow(self.window_name)
cv2.setMouseCallback(self.window_name, self._mouse_ops)
key = KEY_EMPTY
# 定義每次循環(huán)的持續(xù)時(shí)間
delay = int(1000 / FPS)
# 只要沒(méi)有按下Delete鍵,就持續(xù)循環(huán)
while key != KEY_DELETE:
# 上下方向鍵選擇當(dāng)前標(biāo)注物體
if key == KEY_UP:
if label_index == 0:
pass
else:
label_index -= 1
elif key == KEY_DOWN:
if label_index == n_labels - 1:
pass
else:
label_index += 1
# 左右方向鍵選擇標(biāo)注圖片
elif key == KEY_LEFT:
# 已經(jīng)到了第一張圖片的話就不需要清空上一張
if self._index > 0:
self._export_n_clean_bbox()
self._index -= 1
if self._index < 0:
self._index = 0
elif key == KEY_RIGHT:
# 已經(jīng)到了最后一張圖片的就不需要清空上一張
if self._index < len(self._filelist) - 1:
self._export_n_clean_bbox()
self._index += 1
if self._index > len(self._filelist) - 1:
self._index = len(self._filelist) - 1
# 刪除當(dāng)前圖片和對(duì)應(yīng)標(biāo)注的信息
elif key == KEY_DELETE:
if askyesno('Delete Sample', 'Are you sure?'):
self._delete_current_sample()
key = KEY_EMPTY
continue
# 如果鍵盤(pán)操作執(zhí)行了換圖片, 則重新讀取, 更新圖片
filename = self._filelist[self._index]
if filename != last_filename:
filepath = os.sep.join([self._data_dir, filename])
img, self._bboxes = self.load_sample(filepath)
# 更新當(dāng)前標(biāo)注物體名稱
self._cur_label = labels[label_index]
# 把標(biāo)注和相關(guān)信息畫(huà)在圖片上并顯示指定的時(shí)間
canvas = self._draw_bbox(img)
cv2.imshow(self.window_name, canvas)
key = cv2.waitKey(delay)
# 當(dāng)前文件名就是下次循環(huán)的老文件名
last_filename = filename
print 'Finished!'
cv2.destroyAllWindows()
#如果退出程序,需要對(duì)當(dāng)前文件進(jìn)行保存
self.export_bbox(os.sep.join([self._data_dir, get_bbox_name(filename)]), self._bboxes)
print 'Labels updated!'
以上實(shí)現(xiàn)了工具類,當(dāng)然需要一個(gè)入口函數(shù),將工具類保存為SimpleBBoxLabeling.py,新建Run_Detect.py,寫(xiě)以下內(nèi)容:
# coding:utf-8 # tkinter是Python內(nèi)置的簡(jiǎn)單GUI庫(kù),實(shí)現(xiàn)打開(kāi)文件夾、確認(rèn)刪除等操作十分方便 from tkFileDialog import askdirectory # 導(dǎo)入創(chuàng)建的工具類 from SimpleBBoxLabeling import SimpleBBoxLabeling if __name__ == '__main__': dir_with_images = askdirectory(title='Where is the images?') labeling_task = SimpleBBoxLabeling(dir_with_images) labeling_task.start()
以下是實(shí)現(xiàn)后的效果:

需要的文件

.labels文件內(nèi)容格式

選擇文件夾

進(jìn)行標(biāo)注

生成相應(yīng)標(biāo)簽內(nèi)容

標(biāo)注結(jié)果
標(biāo)注后的文件格式為:物體,左上角(起點(diǎn))和右下角(終點(diǎn))的坐標(biāo)。
參考資料: 《深度學(xué)習(xí)與計(jì)算機(jī)視覺(jué)——算法原理、框架應(yīng)用與代碼實(shí)現(xiàn)》 葉韻(編著)
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- vue如何使用js對(duì)圖片進(jìn)行點(diǎn)擊標(biāo)注圓點(diǎn)并記錄它的坐標(biāo)
- 一款基于jQuery的圖片場(chǎng)景標(biāo)注提示彈窗特效
- 一款簡(jiǎn)單的jQuery圖片標(biāo)注效果附源碼下載
- Python tkinter實(shí)現(xiàn)圖片標(biāo)注功能(完整代碼)
- vue下如何利用canvas實(shí)現(xiàn)在線圖片標(biāo)注
- jquery.picsign圖片標(biāo)注組件實(shí)例詳解
- 在React中用canvas對(duì)圖片標(biāo)注的實(shí)現(xiàn)
- 微信小程序給圖片做動(dòng)態(tài)標(biāo)注的實(shí)例分享
相關(guān)文章
詳解Python 序列化Serialize 和 反序列化Deserialize
這篇文章主要介紹了詳解Python 序列化Serialize 和 反序列化Deserialize的相關(guān)資料,序列化是將對(duì)象狀態(tài)轉(zhuǎn)換為可保持或傳輸?shù)母袷降倪^(guò)程。與序列化相對(duì)的是反序列化,它將流轉(zhuǎn)換為對(duì)象。這兩個(gè)過(guò)程結(jié)合起來(lái),可以輕松地存儲(chǔ)和傳輸數(shù)據(jù),需要的朋友可以參考下2017-08-08
Python 日期的轉(zhuǎn)換及計(jì)算的具體使用詳解
這篇文章主要介紹了Python 日期的轉(zhuǎn)換及計(jì)算的具體使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Pytorch中torchtext終極安裝方法以及常見(jiàn)問(wèn)題
torchtext是pytorch框架中用于文本處理的,下面這篇文章主要給大家介紹了關(guān)于Pytorch中torchtext終極安裝方法以及常見(jiàn)問(wèn)題的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
python使用IP歸屬地查詢API追蹤網(wǎng)絡(luò)活動(dòng)
這篇文章主要為大家介紹了python使用IP歸屬地查詢API追蹤網(wǎng)絡(luò)活動(dòng)實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
ubuntu安裝jupyter并設(shè)置遠(yuǎn)程訪問(wèn)的實(shí)現(xiàn)
Jupyter?Notebook是Ipython的升級(jí)版,而Ipython可以說(shuō)是一個(gè)加強(qiáng)版的交互式Shell,本文主要介紹了ubuntu安裝jupyter并設(shè)置遠(yuǎn)程訪問(wèn)的實(shí)現(xiàn),感興趣的可以了解一下2022-03-03
Python的Tornado框架的異步任務(wù)與AsyncHTTPClient
Tornado的奧義就在于異步處理來(lái)提高單線程的Python程序執(zhí)行性能,這里我們就來(lái)詳解Python的Tornado框架的異步任務(wù)與AsyncHTTPClient,需要的朋友可以參考下2016-06-06

