【代碼】CenterNet使用(Detection)(demo.py)

一、運行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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章