• 形态学操作【未完结】


    1. 形态学重建

      1. 测地膨胀

        def dilate_geo(marker: np.ndarray, ksize: int, mask: np.ndarray) -> np.ndarray:
            """
            灰度级测地膨胀(膨胀形态学重建):$(f\oplus{B})\wedge{g}$
        
            :param marker:  标记图像
            :param ksize:   结构元素大小
            :param mask:    掩模图像
            :return:        $D_{g}^{(1)}(f)$
            """
            kernel = cv.getStructuringElement(cv.MORPH_RECT, [ksize] * 2)
            dilation = cv.dilate(marker, kernel)
            return np.min((dilation, mask), axis=0).astype(dtype=np.uint8)
        
      2. 形态学重建开操作

        def morph_reopen(gray: np.ndarray, ksize: int = 3, erode_its: int = 2, max_its: int = 10000) -> np.ndarray:
            """
            重建开操作:$O_{R}^{(n)}(f)=R_{g}^{D}[(f\ominus^{(n)}{B})]$
        
            :param gray:        输入的灰度图像
            :param ksize:       结构元素的大小
            :param erode_its:   腐蚀时的迭代次数
            :param max_its:     灰度级测地膨胀最大迭代次数
            :return:            重建开操作之后的图像
            """
            # 先进行n次(its,默认为2次)腐蚀,将腐蚀过的图像作为标记图像,原始图像作为掩模图像
            kernel = cv.getStructuringElement(cv.MORPH_RECT, [ksize] * 2)
            eroded_gray: np.ndarray = cv.erode(gray, kernel, iterations=erode_its)
            # 迭代进行测地膨胀(终止条件为:膨胀失效或者迭代超时)
            marker_pre, mask = eroded_gray, gray
            marker_cur = dilate_geo(marker_pre, ksize, mask)
            # 设置计数器,之前已经迭代过一次了,所以赋初值为1
            counter = 1
            # 有一个不同并且在允许时间内,则继续迭代测地膨胀
            while np.any(marker_pre != marker_cur) and counter < max_its:
                # cv.imshow("marker_pre", marker_pre)
                # cv.waitKey(0)
                marker_pre, marker_cur = marker_cur, dilate_geo(marker_cur, ksize, mask)
                counter += 1
            return marker_cur.astype(dtype=np.uint8)
        
      3. 测地腐蚀

        def erode_geo(marker: np.ndarray, ksize: int, mask: np.ndarray) -> np.ndarray:
            """
            灰度级测地腐蚀(腐蚀形态学重建):$(f\ominus{B})\vee{g}$
        
            :param marker:  标记图像
            :param ksize:   结构元素大小
            :param mask:    掩模图像
            :return:        $E_{g}^{(1)}(f)$
            """
            kernel = cv.getStructuringElement(cv.MORPH_RECT, [ksize] * 2)
            erosion = cv.erode(marker, kernel)
            return np.max((erosion, mask), axis=0).astype(dtype=np.uint8)
        
      4. 形态学重建闭操作

        def morph_reclose(gray: np.ndarray, ksize: int = 3, dilate_its: int = 2, max_its: int = 10000) -> np.ndarray:
            """
            重建闭操作: $C_{R}^{(n)}(f)=R_{g}^{E}[(f\oplus^{(n)}{B})]$
        
            :param gray:        输入的灰度图像
            :param ksize:       结构元素的大小
            :param dilate_its:  膨胀时的迭代次数
            :param max_its:     灰度级测地腐蚀最大迭代次数
            :return:            重建闭操作之后的图像
            """
            # 先进行n次(its,默认为2次)膨胀,将腐蚀过的图像作为标记图像,原始图像作为掩模图像
            kernel = cv.getStructuringElement(cv.MORPH_RECT, [ksize] * 2)
            dilated_gray: np.ndarray = cv.dilate(gray, kernel, iterations=dilate_its)
            # 迭代进行测地腐蚀(终止条件为:腐蚀失效或者迭代超时)
            marker_pre, mask = dilated_gray, gray
            marker_cur = erode_geo(marker_pre, ksize, mask)
            # 设置计数器,之前已经迭代过一次了,所以赋初值为1
            counter = 1
            # 有一个不同并且在允许时间内,则继续迭代测地腐蚀
            while np.any(marker_pre != marker_cur) and counter < max_its:
                marker_pre, marker_cur = marker_cur, erode_geo(marker_cur, ksize, mask)
                counter += 1
            return marker_cur.astype(dtype=np.uint8)
        
      5. 形态学重建操作

        def morph_restructure(gray: np.ndarray, ksize: int = 3, its: int = 2, max_its: int = 10000) -> np.ndarray:
            """
            对灰度图进行形态学重建(重建开闭操作 + 测地膨胀),在保留原始图像边缘结果的基础上,消除掉噪声和细密纹理、填充孔洞缺口,产生区域性的极大和极小值。
        
            :param gray:    输入的灰度图像
            :param ksize:   结构元素大小
            :return:        重建后的平滑图像
            :param its:     灰度图像初始时腐蚀或者膨胀的次数
            :param max_its: 灰度级测地操作最大迭代次数
            :return:        形态学重建后的灰度图像
            """
            # 首先进行形态学重建开操作,在保留原始图像边缘结果的基础上,平滑(削去)“山峰”(白噪音和细密纹理)
            gray_reopened = morph_reopen(gray, ksize, its, max_its)
            # 然后使用形态学闭重建操作,平滑(填充)“山谷”(黑噪音和ROI内孔洞)
            return morph_reclose(gray_reopened, ksize, its, max_its)
        
    2. 形态学梯度获取

      1. 单尺度形态学梯度获取

        def single_scale_morph_grad(gray: np.ndarray, ksize: int, esize: int = 1) -> np.ndarray:
            """
            单尺度形态学梯度 $\grad_{I}=(I\oplus{K}-I\ominus{K})\ominus{E}$
        
            :param gray:    输入的灰度图像
            :param ksize:   梯度算子的大小
            :param esize:   腐蚀的结构元素大小
            :return:        单尺度形态学梯度图像
            """
            grad_kernel = cv.getStructuringElement(cv.MORPH_RECT, [ksize] * 2)
            erode_kernel = cv.getStructuringElement(cv.MORPH_RECT, [esize] * 2)
            dilated_gray = cv.dilate(gray, grad_kernel)
            eroded_gray = cv.erode(gray, grad_kernel)
            single_grad = cv.erode((dilated_gray - eroded_gray), erode_kernel)
            single_grad_smoothed = cv.morphologyEx(single_grad, cv.MORPH_OPEN, grad_kernel)
            return single_grad_smoothed.astype(dtype=np.uint8)
        
      2. 多尺度形态学梯度获取

        def multi_scale_morph_grad(gray: np.ndarray, ksize_list=None) -> np.ndarray:
            """
            多尺度形态学梯度算法
        
            :param gray:        输入的灰度图像
            :param ksize_list:  一组各种尺度的结构元素大小
            :return:            多尺度形态学梯度图像
            """
            if ksize_list is None:
                ksize_list = [1, 3, 5, 7, 9]
            # cv.imshow("gray", gray)
            # cv.waitKey(0)
            gray = morph_reopen(gray)
            # cv.imshow("mo", gray)
            # cv.waitKey(0)
            grad_list = []
            for i in range(1, len(ksize_list)):
                ksize, esize = ksize_list[i], ksize_list[i - 1]
                single_grad = single_scale_morph_grad(gray, ksize, esize)
                grad_list.append(single_grad)
            multi_grad: np.ndarray = np.array(grad_list).mean(axis=0)
            return multi_grad.astype(dtype=np.uint8)
        
    3. 形态学分割

      1. 去掉小于面积阈值的斑块

        def bw_area_open(binary: np.ndarray, area_threshold: int) -> np.ndarray:
            """
            去除掉小于面积阈值的斑块
        
            :param binary:          输入的二值化图像
            :param area_threshold:  设定的面积阈值
            :return:                处理后的二值化图像
            """
            # 找轮廓
            contours, _ = cv.findContours(binary, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
            # 选择的轮廓(-1表示全部选择),轮廓线条的粗细(-1表示内部全部填充)
            # cv.drawContours(binary, contours, -1, 255, -1)
        
            for idx, contour in enumerate(contours):
                # 将第i号轮廓块用黑色(0,0,0)填充(-1表示线条轮廓内部全部填充)
                if cv.contourArea(contour) < area_threshold:
                    cv.drawContours(binary, contours, idx, 0, -1)
        
            return binary
        
      2. 获取内部元素标记

        def get_foreground_marker(gray: np.ndarray, mf_size=20, ksize=3, area_threshold: int = 10) -> np.ndarray:
            """
            获取前景区域的标记(内部元素标记)
        
            :param gray:            输入的灰度图像
            :param mf_size:         极大值变换时所需的结构元素大小
            :param ksize:           形态学操作时所需的结构元素大小
            :param area_threshold:  设置的面积阈值
            :return:                前景区域标记(内部元素标记)
            """
            # 初步获取标记矩阵()
            marker = cv.erode(gray, cv.getStructuringElement(cv.MORPH_RECT, [ksize] * 2), iterations=2)
            # 获取局部([ksize, ksize])极大值区域
            max_marker = ndimage.maximum_filter(marker, size=mf_size)
            # 形态学重建操作,保留原始图像的边缘信息,去除周围的毛刺和内部空洞
            marker_restructure = morph_restructure(max_marker)
            # 极大值结果修正:去除掉小面积的极大值区域
            return bw_area_open(marker_restructure, area_threshold)
        
      3. 获取外部元素标记

        def get_background_marker(gray: np.ndarray, grad: np.ndarray,
                                  mask_size: int = 5, dist_alpha: float = 0.7, ksize: int = 3):
            """
            获取背景区域标记(外部元素标记)
        
            :param gray:        输入的灰度图像
            :param grad:        输入的梯度图像
            :param mask_size:   获取距离转换图像时的掩膜大小
            :param dist_alpha:  距离转换图像二值化时的最大阈值倍率
            :param ksize:       膨胀时(获取已确定背景区域时)的结构元素大小
            :return:            背景区域标记(外部元素标记)
            """
            # 使用阈值分割(OTSU)将灰度图像二值化
            _, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU)
            # 根据二值化图像获取距离转换图像,并对其归一化
            dist = cv.distanceTransform(binary, cv.DIST_L2, mask_size)
            cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
            # 根据距离转换图形获取确定的前景区域
            _, sure_fg = cv.threshold(dist, dist_alpha * dist.max(), 255, cv.THRESH_BINARY)
            # 根据原始图像获取确定的背景区域(这里是背景区域的取反图像)
            kernel = cv.getStructuringElement(cv.MORPH_RECT, [ksize] * 2)
            sure_bg_inv = cv.dilate(binary, kernel)
            # 根据已确定的背景区域和已确定的前景区域获取不确定区域
            sure_fg = np.array(sure_fg, dtype=np.uint8)
            sure_bg_inv = np.array(sure_bg_inv, dtype=np.uint8)
            unknown = cv.subtract(sure_bg_inv, sure_fg)
        
            # 将已确定的前景区域“先”标记为range(1, _),背景区域和unknown区域标记为0
            _, markers = cv.connectedComponents(sure_fg)
            # 此时已确定的前景区域标记为range(2, _+1),其他区域(背景区域和unknown)均为1
            markers = markers + 1
            # 将unknown区域标记为0,而此时已确定背景区域标记为1,已确定前景区域标记为range(2, _+1)
            markers[unknown == 255] = 0
        
            # 使用分水岭算法执行基于标记的图像分割,将图像中的对象与背景分离
            markers = np.array(markers)  # 因为分水岭算法需要负数表示VISITED(-1)和W_SHED(-2),所以这里不用给出dtype
            # 大于1的正数表示前景,1表示背景,0表示未确定,-1表示已访问,-2表示分水岭
            markers = Watershed(grad, markers).watershed()
        
            # 获取背景区域
            markers_bg = np.zeros_like(markers).astype(dtype=np.uint8)
            markers_bg[markers == 1] = 255
            return markers_bg
        
  • 相关阅读:
    算法初探
    OIer数学相关
    算法初探
    MySQL事务
    MySQL多表查询
    数据库的设计
    winform选择文件夹
    获取上次打开目录
    C#拆分中文和数字字符串
    uCharts如何设置双Y轴,左侧一个右侧一个,数据源与对应的Y轴绑定
  • 原文地址:https://www.cnblogs.com/SimbaWang/p/16387986.html
Copyright © 2020-2023  润新知