一、運行demo.py
按照readme裏頭的創建一個新環境,按照要求安裝即可,中間也遇到了不少的問題,比如說一開始裝上了torch0.4.1,之後不能安裝torchvision,所以又升到了torch1.0,安裝完torchvison之後又回退到了torch0.4.1版本。中間也遇到過DCNv2編譯失敗的情況,把失敗的DCNv2刪掉後再重新編譯,安裝了一些必要組件,好像是那個ffi的模塊,然後莫名其妙就編譯成功了,之後的external也順利安裝成功了。
除此之外,由於demo最後要顯示圖片,總是顯示"X Server沒有連接",還費勁安裝了X manager。
安裝完之後在服務器shell上輸入: export DISPLAY= <本地IP>:0.0應該就可以在Xmanager上顯示了。X manager上也運行了XStart,填入服務器地址、用戶名和密碼就可以了,Xstart應該也是要啓用才能顯示的。前一步可能不是必須的,但是一定要關閉防火牆。
在Pycharm上也能夠運行遠程的shell,Tools——>Start SSH session就可以了。
以上這些都是準備工作,下面先一步一步的解析代碼,讓他能夠在VOC數據集上運行。
運行demo的命令是:python demo.py ctdet --demo ../images --load_model ../models/ctdet_coco_dla_2x.pth
注:
如果遇到ffi的問題: torch.utils.ffi is deprecated.,按照以下方法來做
https://github.com/xingyizhou/CenterNet/issues/7
如果遇到cv2的版本太高,無法顯示的話:
cv2.error: OpenCV(4.1.1) /io/opencv/modules/highgui/src/window.cpp:627: error: (-2:Unspecified error) The function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Cocoa support. If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config, then re-run cmake or configure script in function 'cvShowImage'
可以將src/lib/utils/debugger.py中的self.ipynb(215行show_all_imgs)直接設置成False,這樣就不會調用imshow,但是可以使用裏plt來顯示。
如果遇到顯示圖片卡住:
可以嘗試修改src/lib/utils/debugger.py中的show_all_imgs的waitKey。或許會管用
二、解析準備工作
嘗試debug,在Pycharm上出現warning: Debugger speedups using cython not found.以及Connected to pydev debugger 錯誤。
第一步:將Pycharm上面的解釋器替換爲CenterNet環境中的解釋器,位置在anaconda3/envs/CenterNet/bin
第二步:在pycharm_helpers/pydev/中運行python setup_cython.py build_ext --inplace,建立debugger,之後就能夠debug了。
之後還出現了Python Console的錯誤,按照下圖修改即可:
,,將Python interpreter修改爲CenterNet中的解釋器。
三、流程
1. _init_paths:添加.../CenterNet/src/lib(絕對路徑)到sys.path中,並優先啓用。之後import的根目錄就是lib目錄了。
2. 引入opts類,opts類就是一個parser,存在self.parser中,方法parse用來預處理參數,方法init用來初始化,接收輸入參數。
if __name__ == '__main__':
opt = opts().init()
demo(opt)
接收到的參數放到opt中,傳入demo函數中。
3. 兩句代碼建立Detector
from detectors.detector_factory import detector_factory
Detector = detector_factory[opt.task]
detector = Detector(opt)
detector_factory是如下樣子的一個字典,Key是名字,Value是各個Detector類。
detector_factory = {
'exdet': ExdetDetector,
'ddd': DddDetector,
'ctdet': CtdetDetector,
'multi_pose': MultiPoseDetector,
}
detector使用opt初始化了一個檢測器。由於不是使用的攝像頭,接下來執行如下代碼:
4.
else:
if os.path.isdir(opt.demo):
image_names = []
ls = os.listdir(opt.demo)
for file_name in sorted(ls):
ext = file_name[file_name.rfind('.') + 1:].lower()
if ext in image_ext:
image_names.append(os.path.join(opt.demo, file_name))
else:
image_names = [opt.demo]
for (image_name) in image_names:
ret = detector.run(image_name)
time_str = ''
for stat in time_stats:
time_str = time_str + '{} {:.3f}s |'.format(stat, ret[stat])
print(time_str)
第一部分獲得image_names,就是images文件夾中的圖片。
第二部分就是挨個處理圖片,detector.run(image_name)
第三部分就是輸出時間,time_str
輸出結果如下:
重點考察detector.run(image_name)。
5. detector.run,在detectors/ctdet.py中。run的部分繼承自base_detector。
三、解析
首先要學習parser的知識,這個博文寫的很好:https://www.cnblogs.com/lovemyspring/p/3214598.html
修改爲以下代碼:
args = ['ctdet',
'--demo', '../images',
'--load_model', '../models/ctdet_coco_dla_2x.pth']
opt = opts().init(args=args)
將demo中的第19行修改後可以不顯示圖像:
opt.debug = 0# max(opt.debug, 1)
接下來解析detector.run。
第一部分是建立debugger:
def run(self, image_or_path_or_tensor, meta=None):
load_time, pre_time, net_time, dec_time, post_time = 0, 0, 0, 0, 0
merge_time, tot_time = 0, 0
debugger = Debugger(dataset=self.opt.dataset, ipynb=(self.opt.debug==3),
theme=self.opt.debugger_theme)
start_time = time.time()
pre_processed = False
debugger的代碼在utils/debugger中。傳入的三個參數爲:dataset='coco',ipynb=False, theme='white'。
之後debugger初始化,有這些參數。
第二部分是讀取圖片,存放在image中:
if isinstance(image_or_path_or_tensor, np.ndarray):
image = image_or_path_or_tensor
elif type(image_or_path_or_tensor) == type (''):
image = cv2.imread(image_or_path_or_tensor)
else:
image = image_or_path_or_tensor['image'][0].numpy()
pre_processed_images = image_or_path_or_tensor
pre_processed = True
之後進行預處理,self.scales=[1.0],pre_processed=False,進行self.pre_process,產生images和meta:
第三部分是preprocess,在pre_process方法中:
def pre_process(self, image, scale, meta=None):
height, width = image.shape[0:2]
new_height = int(height * scale)
new_width = int(width * scale)
if self.opt.fix_res:
inp_height, inp_width = self.opt.input_h, self.opt.input_w
c = np.array([new_width / 2., new_height / 2.], dtype=np.float32)
s = max(height, width) * 1.0
else:
inp_height = (new_height | self.opt.pad) + 1
inp_width = (new_width | self.opt.pad) + 1
c = np.array([new_width // 2, new_height // 2], dtype=np.float32)
s = np.array([inp_width, inp_height], dtype=np.float32)
trans_input = get_affine_transform(c, s, 0, [inp_width, inp_height])
resized_image = cv2.resize(image, (new_width, new_height))
inp_image = cv2.warpAffine(
resized_image, trans_input, (inp_width, inp_height),
flags=cv2.INTER_LINEAR)
inp_image = ((inp_image / 255. - self.mean) / self.std).astype(np.float32)
images = inp_image.transpose(2, 0, 1).reshape(1, 3, inp_height, inp_width)
if self.opt.flip_test:
images = np.concatenate((images, images[:, :, :, ::-1]), axis=0)
images = torch.from_numpy(images)
meta = {'c': c, 's': s,
'out_height': inp_height // self.opt.down_ratio,
'out_width': inp_width // self.opt.down_ratio}
return images, meta
首先進行尺度變換,由於尺度只有1.0,不變,產生resized_image,之後self.opt.fix_res = True,將圖像尺寸歸一化爲512*512,產生inp_image,最後還要進行歸一化,並且reshape,得到images(Tensor),以及meta(dict),裏頭有
{c:resized_image的中心位置,
s:最長寬度,
'out_height': inp_height // 下采樣率=輸出heatmap高度,
'out_width': inp_width // 下采樣率}
第四部分進一步處理數據,放入GPU中:
images = images.to(self.opt.device)
torch.cuda.synchronize()
pre_process_time = time.time()
pre_time += pre_process_time - scale_start_time
其中torch.cuda.synchronize()是爲了讓所有核同步,測得真實時間。
第五部分放入網絡中測試,產生輸出:
output, dets, forward_time = self.process(images, return_time=True)
process部分在ctdet.py中:
def process(self, images, return_time=False):
with torch.no_grad():
output = self.model(images)[-1]
hm = output['hm'].sigmoid_()
wh = output['wh']
reg = output['reg'] if self.opt.reg_offset else None
if self.opt.flip_test:
hm = (hm[0:1] + flip_tensor(hm[1:2])) / 2
wh = (wh[0:1] + flip_tensor(wh[1:2])) / 2
reg = reg[0:1] if reg is not None else None
torch.cuda.synchronize()
forward_time = time.time()
dets = ctdet_decode(hm, wh, reg=reg, K=self.opt.K)
if return_time:
return output, dets, forward_time
else:
return output, dets
首先將images放入model中,就得到output了。output具有三個部分
{'hm': 1*80*128*128,
'reg': 1*2*128*128,
'wh': 1*2*128*128},可以看出來,只有hm(heatmap)是與類別相關的,reg(offset)和wh(width & height)是與類別無關的。
之後使用ctdet_decode進行解碼,得到dets,dets是1*100*6的張量。
最終,返回outputs,dets,forward_time。
第六部分對得到得dets進行後處理:
dets = self.post_process(dets, meta, scale)
torch.cuda.synchronize()
post_process_time = time.time()
post_time += post_process_time - decode_time
detections.append(dets)
post_process在ctdet.py中出現:
def post_process(self, dets, meta, scale=1):
dets = dets.detach().cpu().numpy()
dets = dets.reshape(1, -1, dets.shape[2])
dets = ctdet_post_process(
dets.copy(), [meta['c']], [meta['s']],
meta['out_height'], meta['out_width'], self.opt.num_classes)
for j in range(1, self.num_classes + 1):
dets[0][j] = np.array(dets[0][j], dtype=np.float32).reshape(-1, 5)
dets[0][j][:, :4] /= scale
return dets[0]
使用ctdet_post_process進行後處理,
最後得到得dets是一個len=80的張量(列表?)
其中每個元素是一個N * 5的ndarray。猜測是每個類別中的dets。detections是一個包含dets的列表。
第七部分進行最後的後處理:
results = self.merge_outputs(detections)
torch.cuda.synchronize()
end_time = time.time()
merge_time += end_time - post_process_time
tot_time += end_time - start_time
merge_outputs在ctdet.py中:
def merge_outputs(self, detections):
results = {}
for j in range(1, self.num_classes + 1):
results[j] = np.concatenate(
[detection[j] for detection in detections], axis=0).astype(np.float32)
if len(self.scales) > 1 or self.opt.nms:
soft_nms(results[j], Nt=0.5, method=2)
scores = np.hstack(
[results[j][:, 4] for j in range(1, self.num_classes + 1)])
if len(scores) > self.max_per_image:
kth = len(scores) - self.max_per_image
thresh = np.partition(scores, kth)[kth]
for j in range(1, self.num_classes + 1):
keep_inds = (results[j][:, 4] >= thresh)
results[j] = results[j][keep_inds]
return results