形态学操作【未完结】

  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
      
posted @ 2022-06-18 11:36  BNTU  阅读(44)  评论(0编辑  收藏  举报