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
Exemple #2
0
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
Exemple #3
0
    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
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
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
Exemple #11
0
    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
Exemple #12
0
    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
Exemple #13
0
    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
Exemple #14
0
    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
Exemple #15
0
    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
Exemple #17
0
    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
Exemple #18
0
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)