def calc_hsv_metrics_by_ground_truth(img, ground_truth, gt_type="GT_ORANGE"): NDARRAY_ASSERT(img, ndim=3, dtype=np.uint8) SAME_SHAPE_ASSERT(img, ground_truth, ignore_ndim=True) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) ground_truth = gen_ground_truth_by_type(ground_truth, gt_type) h, s, v = [hsv[:, :, i][ground_truth == True] for i in range(3)] metrics = { k: { "min": ch.min(), "max": ch.max(), "mean": np.mean(ch), "median": np.median(ch), "stddev": np.std(ch), "var": np.var(ch) } for k, ch in { "H": h, "S": s, "V": v }.items() } return metrics
def gen_overlay_by_gt(img_mask, img_gt): """ 正解データと建物マスク画像のオーバーレイ画像の生成 Parameters ---------- img_mask : numpy.ndarray マスク画像 img_gt : numpy.ndarray 正解データ Returns ------- numpy.ndarray オーバーレイ画像 """ TYPE_ASSERT(img_mask, np.ndarray) TYPE_ASSERT(img_gt, np.ndarray) SAME_SHAPE_ASSERT(img_mask, img_gt) White = np.array([255, 255, 255], dtype=np.uint8) Red = np.array([0, 0, 255], dtype=np.uint8) Black = np.array([0, 0, 0], dtype=np.uint8) dst = np.zeros(img_gt.shape, dtype=np.uint8) dst[(img_mask == White).all(axis=2) & (img_gt == Red).all(axis=2)] = Red dst[(img_mask == White).all(axis=2) & (img_gt == Black).all(axis=2)] = White return dst
def match_to_template(self, img_roi, template): """ 切り出した領域がテンプレートにマッチするかどうか調べる Parameters ---------- img_roi : numpy.ndarray 切り出した領域 template : numpy.ndarray テンプレート Returns ------- bool 切り出した領域がテンプレートにマッチしたかどうか Notes ----- `img_roi` と `template` は同じ大きさである必要がある """ DONT_CARE = self.DONT_CARE SAME_SHAPE_ASSERT(img_roi, template) return np.array_equal(img_roi[template != DONT_CARE], template[template != DONT_CARE])
def hsv_blending(bg_img, fg_img, bg_v_scale=None, fg_v_scale=None): NDARRAY_ASSERT(fg_img, ndim=3) SAME_SHAPE_ASSERT(bg_img, fg_img) if bg_img.ndim == 3: bg_img = cv2.cvtColor( bg_img, cv2.COLOR_BGR2GRAY ) if bg_img.ndim == 2: bg_img = cv2.cvtColor( bg_img, cv2.COLOR_GRAY2BGR ) bg_hsv, fg_hsv = [ cv2.cvtColor(img, cv2.COLOR_BGR2HSV) for img in [bg_img, fg_img] ] b_h, b_s, b_v = [bg_hsv[:, :, i] for i in range(3)] f_h, f_s, f_v = [fg_hsv[:, :, i] for i in range(3)] if bg_v_scale is not None: fg_not_masked = np.all(fg_img == C_BLACK, axis=2) b_v = b_v.astype(np.float32) b_v[fg_not_masked == True] = (b_v[fg_not_masked == True] * bg_v_scale) # Clip value b_v[b_v > 255.0] = 255 # Cast Type b_v = b_v.astype(np.uint8) if fg_v_scale is not None: fg_mask = ~np.all(fg_img == C_BLACK, axis=2) b_v = b_v.astype(np.float32) b_v[fg_mask == True] = (b_v[fg_mask == True] * fg_v_scale) # Clip value b_v[b_v > 255.0] = 255 # Cast Type b_v = b_v.astype(np.uint8) dst = cv2.cvtColor( np.dstack( [f_h, f_s, b_v] ), cv2.COLOR_HSV2BGR ) return dst
def merge_arrays_by_mask(array_1, array_2, mask): NDARRAY_ASSERT(mask, ndim=2, dtype=np.bool) SAME_NDIM_ASSERT(array_1, array_2) SAME_SHAPE_ASSERT(array_1, array_2) SAME_SHAPE_ASSERT(array_1, mask, ignore_ndim=True) array_1 = array_1.copy() array_2 = array_2.copy() Z = np.zeros( (1 if array_1.ndim == 2 else array_1.shape[2],), dtype=array_1.dtype ) array_1[mask == True] = Z array_2[mask == False] = Z return array_1 + array_2
def mask_to_gray(img, mask): NDARRAY_ASSERT(img, ndim=3, dtype=np.uint8) NDARRAY_ASSERT(mask, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(img, mask, ignore_ndim=True) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hsv[mask == False, 1] = 0 dst = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) return dst
def detect_road_damage_1(result, road_mask, logger=None): """ 建物被害の結果から道路上の被害抽出を行う Parameters ---------- result : numpy.ndarray 建物被害の結果 黒を背景、白を被害領域として 2値化されている必要がある road_mask : numpy.ndarray 道路マスク画像 黒を背景、白を道路領域として 2値化されている必要がある logger : ImageLogger, default is None 処理途中の画像をロギングする ImageLogger Returns ------- numpy.ndarray 道路上の被害抽出結果 Notes ----- - `result` と `road_mask` は同じ大きさである必要がある """ NDARRAY_ASSERT(result, ndim=2, dtype=np.bool) NDARRAY_ASSERT(road_mask, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(result, road_mask) # 1. 建物被害結果に道路マスクを適用 result_extracted = result * road_mask result_extracted = (result_extracted * 255).astype(np.uint8) if logger: logger.logging_img(result_extracted, "result_extracted") # 2. 1. の画像から距離画像を作成する dist = cv2.distanceTransform(result_extracted, cv2.DIST_L2, maskSize=5) # FIXED: Normalize # dist = (dist / dist.max() * 255).astype(np.uint8) if logger: logger.logging_img(dist, "distance", cmap="gray") logger.logging_img(dist, "distance_visualized", cmap="jet") return dist
def evaluation_by_confusion_matrix(result, ground_truth): """ 混同行列 (Confusion-Matrix) を用いた 精度評価 Notes ----- - `result` と `ground_truth` は bool 型で 同じサイズの行列である必要がある Parameters ---------- result : numpy.ndarray 出力結果 ground_truth : numpy.ndarray 正解データ Returns ------- tuple 混同行列と各種スコアのタプル: (confusion_matrix, metrics) """ NDARRAY_ASSERT(result, dtype=bool) NDARRAY_ASSERT(ground_truth, dtype=bool) SAME_SHAPE_ASSERT(result, ground_truth, ignore_ndim=True) for data in [result, ground_truth]: if data.ndim == 3: data = np.any(data, axis=2) TP = np.count_nonzero(result & ground_truth) FP = np.count_nonzero(result & (ground_truth == False)) FN = np.count_nonzero((result == False) & ground_truth) TN = np.count_nonzero((result == False) & (ground_truth == False)) confusion_matrix = { "TP": TP, "FP": FP, "FN": FN, "TN": TN, } metrics = calculate_metrics(confusion_matrix) return confusion_matrix, metrics
def apply_road_mask(img, mask, bg_value=0): NDARRAY_ASSERT(img) NDARRAY_ASSERT(mask, ndim=2) SAME_SHAPE_ASSERT(img, mask, ignore_ndim=True) assert check_if_binary_image(mask), \ "'mask' must be binary image" masked = img.copy() if img.ndim == 2: masked[mask == mask.max()] = bg_value.astype(masked.dtype) else: masked[mask == mask.max(), :] = np.full( masked.shape[2], fill_value=bg_value, dtype=masked.dtype ) return masked
def compute_by_window(imgs, func, window_size=16, step=2, dst_dtype=np.float32, n_worker=12): """ 画像を一部を切り取り、func 関数で行った計算結果を 返却する Parameters ---------- imgs : numpy.ndarray or tuple of numpy.ndarray 入力画像 tuple で複数画像を与える場合、各画像に対して 同じ領域を切り取り、処理を行うため、各画像の 縦、横サイズは一致している必要がある func : callable object 切り取った画像の一部に対して何らかの計算を行う 関数。引数として画像の一部が渡される。 window_size : int or tuple of int 画像を切り取るサイズ。 int を指定した場合は、縦横同じサイズで切り取る。 tuple(int, int) を指定した場合は、縦横で異なったサイズ で切り取り、指定する順序は ndarray の次元に対応する step : int or tuple of int 切り取り間隔 int を指定した場合は、縦横同じ間隔を開けて処理をする tuple(int, int) を指定した場合は、縦横で異なった間隔 を開けて処理を行い、指定する順序は ndarray の次元に 対応する dst_dtype : type, default numpy.float32 返却値のデータ型 n_worker : int, default 4 並列するプロセス数 Returns ------- numpy.ndarray 各切り取り画像に対する処理結果の行列 """ # TYPE ASSERTION TYPE_ASSERT(imgs, [np.ndarray, tuple]) TYPE_ASSERT(window_size, [int, tuple]) TYPE_ASSERT(step, [int, tuple]) TYPE_ASSERT(dst_dtype, type) TYPE_ASSERT(n_worker, int) if isinstance(imgs, np.ndarray): imgs = tuple([imgs]) for img in imgs: TYPE_ASSERT(img, np.ndarray) for i in range(len(imgs) - 1): SAME_SHAPE_ASSERT(imgs[i], imgs[i + 1]) n_imgs = len(imgs) height, width = imgs[0].shape[:2] assert callable(func) and n_args(func) >= n_imgs, \ "argument 'func' must be callable object which has {0} argument at least. \n".format(n_imgs) + \ " ( num of argumets of 'func' depends on argument 'imgs')" if isinstance(step, int): step = tuple([step] * 2) if isinstance(window_size, int): window_size = tuple([window_size] * 2) s_i, s_j = step w_w, w_h = window_size results_shape = ceil(height / s_i), ceil(width / s_j) # Add padding to input images eprint("Add padding ... ") imgs = [ np.pad( img, pad_width=[ tuple([w_w // 2]), tuple([w_h // 2]), ] + [] if img.ndim == 2 else [tuple([0])], mode="constant", constant_values=0 ) for img in imgs ] if n_worker == 1: results = np.ndarray(results_shape, dtype=dst_dtype) for ii, i in tqdm(enumerate(range(w_h // 2, height + w_h // 2, s_i)), total=results_shape[0]): for jj, j in tqdm(enumerate(range(w_w // 2, width + w_w // 2, s_j)), total=results_shape[1], leave=False): rois = [ img[ get_window_rect( img.shape, center=(j, i), wnd_size=(w_w, w_h), ret_type="slice" ) ] for img in imgs ] results[ii][jj] = func(*rois) else: global _func global _callee _func = func def _callee(_imgs, _func, _width, _s_j, _w_w, _n_loop): _worker_id = current_process()._identity[0] _desc = f"Worker #{_worker_id:3d}" _results = list() for jj, j in tqdm(enumerate(range(_w_w // 2, _width + _w_w // 2, _s_j)), total=_n_loop, desc=_desc, position=_worker_id, leave=False): _rois = [ # _roi[:, j:j + _w_w] _roi[ get_window_rect( _roi.shape, center=(j, -1), wnd_size=(_w_w, -1), ret_type="slice" ) ] for _roi in _imgs ] _results.append(_func(*_rois)) return _results progress_bar = tqdm(total=results_shape[0], position=0) def _update_progressbar(arg): progress_bar.update() cp = CustomPool() pool = cp.Pool(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(),)) results = list() for ii, i in enumerate(range(w_h // 2, height + w_h // 2, s_i)): rois = [ img[ get_window_rect( img.shape, center=(-1, i), wnd_size=(-1, w_h), ret_type="slice" ) ] for img in imgs ] results.append( pool.apply_async( _callee, args=(rois, func, width, s_j, w_h, results_shape[1]), callback=_update_progressbar ) ) pool.close() pool.join() cp.update() results = np.array( [result.get() for result in results], dtype=dst_dtype ) return results
def find_color_threshold_in_hsv(self, img, ground_truth, precision=10): """ HSV 色空間における色閾値探索 - RGB → HSV 変換 を行う - HSV の各チャンネルで閾値処理を行い統合する - 正解データを用いて精度評価を行う Parameters ---------- img : numpy.ndarray 入力画像 (8-Bit RGB カラー) ground_truth : numpy.ndarray 正解データ (1-Bit) precision : int 閾値計算の精度 Returns ------- reasonable_params : dict F値が最も高くなったときの閾値 result : numpy.ndarray その際の閾値処理結果画像 (1-Bit 2値画像) Notes ----- `ground_truth`: - 1-Bit (bool 型) 2値画像 - 黒:背景、白:被害領域 `precision`: - precision=N のとき、H, S, V の各チャンネルに対し 2N ずつにパーセンタイル分割を行う """ global _worker_find_color_threshold_in_hsv # Worker methods executed parallel @worker_exception_raisable def _worker_find_color_threshold_in_hsv(_img, _masked, _q_h, _q_s): # Value used in tqdm _worker_id = current_process()._identity[0] _desc = f"Worker #{_worker_id:3d}" # Unpack arguments _q_h_low, _q_h_high = _q_h _q_s_low, _q_s_high = _q_s # Split image to each channne _img_h, _img_s, _img_v = [_img[:, :, i] for i in range(3)] _masked_h, _masked_s, _masked_v = [_masked[:, i] for i in range(3)] # Initialize variables reasonable_params = { "Score": { "F Score": -1, }, "Range": -1 } # Find thresholds for _q_v_low, _q_v_high in tqdm(list( product(np.linspace(50 / precision, 50, precision), repeat=2)), desc=_desc, position=_worker_id, leave=False): # Generate result _h_min, _h_max = self._in_range_percentile( _masked_h, (_q_h_low, _q_h_high)) _s_min, _s_max = self._in_range_percentile( _masked_s, (_q_s_low, _q_s_high)) _v_min, _v_max = self._in_range_percentile( _masked_v, (_q_v_low, _q_v_high)) _result = (((_h_min <= _img_h) & (_img_h <= _h_max)) & ((_s_min <= _img_s) & (_img_s <= _s_max)) & ((_v_min <= _img_v) & (_img_v <= _v_max))) # Calculate score _cm, _metrics = evaluation_by_confusion_matrix( _result, ground_truth) # Update reasonable_params if _metrics["F Score"] > reasonable_params["Score"]["F Score"]: reasonable_params = { "Score": _metrics, "Confusion Matrix": _cm, "Range": { "H": (_h_min, _h_max, _q_h_low, _q_h_high), "S": (_s_min, _s_max, _q_s_low, _q_s_high), "V": (_v_min, _v_max, _q_v_low, _q_v_high), } } return reasonable_params # Check arguments NDARRAY_ASSERT(img, ndim=3, dtype=np.uint8) NDARRAY_ASSERT(ground_truth, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(img, ground_truth, ignore_ndim=True) # Convert RGB -> HSV img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # `masked`: `img` masked by `ground_truth` masked = img[ground_truth] # Percentile Split Q = list(product(np.linspace(50 / precision, 50, precision), repeat=4)) # `progress_bar`: whole progress bar progress_bar = tqdm(total=len(Q), position=0) def _update_progressbar(arg): progress_bar.update() # Initialize process pool cp = CustomPool() pool = cp.Pool(initializer=tqdm.set_lock, initargs=(tqdm.get_lock(), )) results = list() # Multi-Processing ! for q_h_low, q_h_high, q_s_low, q_s_high in Q: results.append( pool.apply_async(_worker_find_color_threshold_in_hsv, args=(img, masked, (q_h_low, q_h_high), (q_s_low, q_s_high)), callback=_update_progressbar)) pool.close() pool.join() cp.update() # Resolve results try: results = [result.get() for result in results] except Exception as e: print(e) # Get result whose F-Score is max in results reasonable_params = max(results, key=lambda e: e["Score"]["F Score"]) img_h, img_s, img_v = [img[:, :, i] for i in range(3)] h_min, h_max, _, _ = reasonable_params["Range"]["H"] s_min, s_max, _, _ = reasonable_params["Range"]["S"] v_min, v_max, _, _ = reasonable_params["Range"]["V"] # Generate image using reasonable thresholds result = (((h_min <= img_h) & (img_h <= h_max)) & ((s_min <= img_s) & (img_s <= s_max)) & ((v_min <= img_v) & (img_v <= v_max))) # Logging if self.logger: self.logger.logging_dict(reasonable_params, "color_thresholds_in_hsv", sub_path=self.logger_sub_path) self.logger.logging_img(result, "meanshift_thresholded", sub_path=self.logger_sub_path) return reasonable_params, result
def find_reasonable_morphology(self, result_img, ground_truth): """ 最適なモルフォロジー処理を模索 - 正解データとの精度比較により、各種処理結果の補正として 最適なモルフォロジー処理を模索する Parameters ---------- result_img : numpy.ndarray 処理結果画像 ground_truth : numpy.ndarray 正解データ Returns ------- reasonable_params : dict 導き出されたパラメータ - モルフォロジー処理のパターン - 適用時のスコア result : numpy.ndarray - モルフォロジー処理結果画像 Notes ----- `result_img`: - 1-Bit (bool 型) 2値画像 - 黒:無被害、白:被害 `ground_truth`: - 1-Bit (bool 型) 2値画像 - 黒:背景、白:被害領域 """ # Check arguments NDARRAY_ASSERT(result_img, ndim=2, dtype=np.bool) NDARRAY_ASSERT(ground_truth, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(result_img, ground_truth, ignore_ndim=True) # 処理の組み合わせパターン FIND_RANGE = { # カーネルの大きさ: 3x3, 5x5 "Kernel Size": [3, 5], # 処理の対象: 4近傍, 8近傍 "# of Neighbors": [4, 8], # モルフォロジー処理 "Morphology Methods": ["ERODE", "DILATE", "OPEN", "CLOSE"], # 繰り返し回数 "# of Iterations": range(1, 6) } # Convert input image to uint8 (for `cv2.morphologyEx`) result_img = (result_img * 255).astype(np.uint8) # Initialize variables reasonable_params = { "Confusion Matrix": dict(), "Score": { "F Score": -1, }, "Params": { "Operation": "", "Kernel": { "Size": (-1, -1), "#_of_Neighbor": -1 }, "Iterations": -1 } } result = None # Finding reasonable process for kernel_size in FIND_RANGE["Kernel Size"]: for n_neighbor in FIND_RANGE["# of Neighbors"]: for operation in FIND_RANGE["Morphology Methods"]: for n_iterations in FIND_RANGE["# of Iterations"]: # Set parameters if n_neighbor == 4: kernel = np.zeros((kernel_size, kernel_size), dtype=np.uint8) kernel[kernel_size // 2, :] = 1 kernel[:, kernel_size // 2] = 1 else: kernel = np.ones((kernel_size, kernel_size), dtype=np.uint8) # Generate result _result = cv2.morphologyEx( src=result_img, op=cv2.__dict__[f"MORPH_{operation}"], kernel=kernel, iterations=n_iterations).astype(bool) # Calculate scores _cm, _metrics = evaluation_by_confusion_matrix( _result, ground_truth) # Update reasonable_params if _metrics["F Score"] > reasonable_params["Score"][ "F Score"]: reasonable_params = { "Confusion Matrix": _cm, "Score": _metrics, "Params": { "Operation": operation, "Kernel": { "Size": (kernel_size, kernel_size), "#_of_Neighbor": n_neighbor }, "Iterations": n_iterations } } result = _result.copy() # Logging if self.logger: self.logger.logging_img(result, "result_morphology", sub_path=self.logger_sub_path) self.logger.logging_dict(reasonable_params, "params_morphology", sub_path=self.logger_sub_path) return reasonable_params, result
def find_canny_thresholds(self, img, ground_truth): """ Canny のアルゴリズムの閾値探索を行う Parameters ---------- img : numpy.ndarray 入力画像 (8−Bit グレースケール画像) ground_truth 正解データ (1-Bit 画像) Returns ------- reasonable_params : dict F値が最も高くなったときの閾値 result : numpy.ndarray その際の閾値処理結果画像 Notes ----- `ground_truth`: - 1-Bit (bool 型) 2値画像 - 黒:背景、白:被害領域 """ from skimage.feature import canny NDARRAY_ASSERT(img, ndim=2, dtype=np.uint8) NDARRAY_ASSERT(ground_truth, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(img, ground_truth, ignore_ndim=False) # Initialize variables reasonable_params = { "Score": { "F Score": -1 }, "Confusion Matrix": None, "Thresholds": None, } result = None # Calculate thresholds for th_1, th_2 in tqdm(list(product(range(256), repeat=2))): # Generate result _result = canny(img, low_threshold=th_1, high_threshold=th_2) # Calculate scores _cm, _metrics = evaluation_by_confusion_matrix( _result, ground_truth) # Update reasonable_params if _metrics["F Score"] > reasonable_params["Score"]["F Score"]: reasonable_params = { "Score": _metrics, "Confusion Matrix": _cm, "Thresholds": list([th_1, th_2]) } result = _result.copy() if result.dtype != bool: result = (result * 255).astype(np.uint8) # Logging if self.logger: self.logger.logging_dict(reasonable_params, "canny_thresholds", sub_path=self.logger_sub_path) self.logger.logging_img(result, "canny", sub_path=self.logger_sub_path) return reasonable_params, result
def find_subtracted_thresholds(self, img_a, img_b, ground_truth, precision=10): """ 2画像間の差分結果の閾値計算を行う - 画像A, B それぞれで閾値処理 - 各画像の最小値、最大値の間をパーセンタイルで分割する - A & not(B) を計算し、正解データと比較 - F値が最も高くなるときの画像A, B の閾値を返却する Parameters ---------- img_a, img_b : numpy.ndarray 入力画像 (グレースケール画像) ground_truth : numpy.ndarray 正解データ (1-Bit) precision : int 閾値計算の精度 Returns ------- reasonable_params : dict F値が最も高くなったときの閾値 result : numpy.ndarray その際の閾値処理結果画像 (1-Bit 2値画像) Notes ----- `ground_truth`: - 1-Bit (bool 型) 2値画像 - 黒:背景、白:被害領域 `precision`: - precision=N のとき、2N ずつにパーセンタイル分割を行う """ NDARRAY_ASSERT(img_a, ndim=2) NDARRAY_ASSERT(img_b, ndim=2) NDARRAY_ASSERT(ground_truth, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(img_a, img_b, ignore_ndim=False) SAME_SHAPE_ASSERT(img_a, ground_truth, ignore_ndim=False) # Initialize variables reasonable_params = { "Score": { "F Score": -1 }, "Confusion Matrix": None, "Range": None, } result = None # Calculate thresholds for q_a_low, q_a_high, q_b_low, q_b_high in tqdm( list( product(np.linspace(50 / precision, 50, precision), repeat=4))): # Generate result a_min, a_max = self._in_range_percentile(img_a, (q_a_low, q_a_high)) b_min, b_max = self._in_range_percentile(img_b, (q_b_low, q_b_high)) _result_a = (a_min < img_a) & (img_a < a_max) _result_b = (b_min < img_b) & (img_b < b_max) _result = _result_a & np.bitwise_not(_result_b) # Calculate scores _cm, _metrics = evaluation_by_confusion_matrix( _result, ground_truth) # Update reasonable_params if _metrics["F Score"] > reasonable_params["Score"]["F Score"]: reasonable_params = { "Score": _metrics, "Confusion Matrix": _cm, "Range": { "img_a": [a_min, a_max], "img_b": [b_min, b_max], } } result = _result.copy() # Logging if self.logger: self.logger.logging_dict(reasonable_params, f"params_subtracted_thresholds", sub_path=self.logger_sub_path) self.logger.logging_img(result, f"subtract_thresholded", sub_path=self.logger_sub_path) return reasonable_params, result
def meanshift_and_color_thresholding( self, func_mean_shift=cv2.pyrMeanShiftFiltering, params_mean_shift={ "sp": 40, "sr": 50 }, retval_pos=None, skip_find_params=False): """ 建物被害検出: Mean-Shift による減色処理と色閾値処理 - 入力画像に Mean-Shift による減色処理を適用する - 正解データをもとに、色空間での閾値を探索し、適用する - 閾値処理結果に対し、モルフォロジー処理による補正処理を行う Parameters ---------- func_mean_shift : callable object Mean-Shift 処理関数 params_mean_shift : dict Mean-Shift 処理関数に渡されるパラメータ retval_pos : int, default None Mean-Shift 処理関数の返却値が複数の場合、 領域分割後の画像が格納されている位置を指定する Returns ------- building_damage : numpy.ndarray 被害抽出結果 Notes ----- `func_mean_shift` - 第1引数に画像を取れるようになっている必要がある `building_damage` - 1-Bit (bool 型) 2値画像 - 黒:背景、白:被害抽出結果 """ img = self.img ground_truth = self.ground_truth logger = self.logger # Check arguments NDARRAY_ASSERT(img, ndim=3, dtype=np.uint8) NDARRAY_ASSERT(ground_truth, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(img, ground_truth, ignore_ndim=True) TYPE_ASSERT(params_mean_shift, dict) if isinstance(func_mean_shift, str): func_mean_shift = eval(func_mean_shift) assert callable(func_mean_shift ), "argument 'func_mean_shift' must be callable object" # Set parameters params = { "Mean-Shift": { "func": get_qualified_class_name(func_mean_shift, wrap_with_quotes=False), "params": params_mean_shift } } eprint("Pre-processing ({func_name}, {params}) ... ".format( func_name=params["Mean-Shift"]["func"], params=", ".join([ f"{k}={v}" for k, v in params["Mean-Shift"]["params"].items() ])), end="") # Mean-Shift if retval_pos is None: smoothed = func_mean_shift(img, **params_mean_shift) else: smoothed = func_mean_shift(img, **params_mean_shift)[retval_pos] eprint("done !") # Logging if logger: logger.logging_img(smoothed, "filtered") logger.logging_dict(params, "detail_mean_shift") if not skip_find_params: params_finder = ParamsFinder(logger=logger) # Find: Color thresholds in HSV _, result = params_finder.find_color_threshold_in_hsv( img=smoothed, ground_truth=ground_truth, ) # Find: Morphology processing _, building_damage = params_finder.find_reasonable_morphology( result_img=result, ground_truth=ground_truth, ) # Logging if logger: logger.logging_img(building_damage, "building_damage") return building_damage return smoothed
def detect_road_damage_2(result, road_mask, vegetation_mask=None, logger=None): """ 建物被害の結果から道路上の被害抽出を行う Parameters ---------- result : numpy.ndarray 建物被害の結果 黒を背景、白を被害領域として 2値化されている必要がある road_mask : numpy.ndarray 道路マスク画像 黒を背景、白を道路領域として 2値化されている必要がある logger : ImageLogger, default is None 処理途中の画像をロギングする ImageLogger Returns ------- numpy.ndarray 道路上の被害抽出結果 Notes ----- - `result` と `road_mask` は同じ大きさである必要がある """ NDARRAY_ASSERT(result, ndim=2, dtype=np.bool) NDARRAY_ASSERT(road_mask, ndim=2, dtype=np.bool) SAME_SHAPE_ASSERT(result, road_mask) TYPE_ASSERT(vegetation_mask, [None, np.ndarray]) if vegetation_mask is not None: NDARRAY_ASSERT(vegetation_mask, ndim=2, dtype=np.bool) result = result & ~vegetation_mask result = (result * 255).astype(np.uint8) dist = cv2.distanceTransform(result, cv2.DIST_L2, maskSize=5) # FIXED: Normalize # dist = (dist / dist.max() * 255).astype(np.uint8) logger_sub_path = "" if vegetation_mask is None else "removed_vegetation" if logger: logger.logging_img(dist, "distance", cmap="gray", sub_path=logger_sub_path) logger.logging_img(dist, "distance_visualized", cmap="jet", sub_path=logger_sub_path) result_extracted = dist * road_mask if logger: logger.logging_img(result_extracted, "result_extracted", sub_path=logger_sub_path) return result_extracted
def draw_edge_angle_line(self, line_color=(255, 255, 255), line_length=10, draw_on_angle_img=True, mask_img=None): """ エッジ角度に対応する線分を描画する Parameters ---------- src_img : numpy.ndarray 角度線が描画されるベース画像 edge_angle : numpy.ndarray エッジ角度 line_color : tuple 線の色(R, G, B の順) line_length : int 線の長さ mask_img : [None, numpy.ndarray] マスク画像(2値化済み) mask_img が与えられた場合、白色(非ゼロ値)の 箇所のみ角度線が描画される Returns ------- angle_line_img : numpy.ndarray 角度線が描画された画像(BGR) 線描画の都合上、画像の大きさが縦、横 それぞれ3倍されて返却される """ TYPE_ASSERT(mask_img, [None, np.ndarray]) NDIM_ASSERT(mask_img, 2) if draw_on_angle_img: base_img = self.get_angle_colorized_img(max_intensity=True, mask_img=mask_img) else: base_img = self.src_img SAME_SHAPE_ASSERT(mask_img, base_img, ignore_ndim=True) angles = self._calc_angle() angle_line_img = cv2.resize(base_img, dsize=None, fx=3.0, fy=3.0, interpolation=cv2.INTER_NEAREST) # Vectorization Process if mask_img is not None: draw_points = (np.argwhere(mask_img != 0) + 1) * 3 - 2 angles = angles[mask_img != 0].flatten() else: draw_points = np.stack( np.meshgrid(*[np.arange(i) for i in mask_img.shape[:2]]), axis=2) angles = angles.flatten() pts_1, pts_2 = self.calc_end_points(draw_points, angles, line_length) # Line Drawing for (i, (pt_1, pt_2)) in enumerate(zip(pts_1, pts_2)): pt_1, pt_2 = tuple(pt_1[::-1]), tuple(pt_2[::-1]) print(f"\rComputing ... [ {i} / {pts_1.shape[0]} ]", end="", flush=True) cv2.line(angle_line_img, pt_1, pt_2, line_color, thickness=1) return angle_line_img
def _check_arrays(I, M): NDARRAY_ASSERT(I, dtype=np.uint8) NDARRAY_ASSERT(M, dtype=np.uint8) SAME_SHAPE_ASSERT(I, M, ignore_ndim=True)