-
形态学重建
-
测地膨胀
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)
-
形态学重建开操作
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)
-
测地腐蚀
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)
-
形态学重建闭操作
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)
-
形态学重建操作
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)
-
-
形态学梯度获取
-
单尺度形态学梯度获取
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)
-
多尺度形态学梯度获取
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)
-
-
形态学分割
-
去掉小于面积阈值的斑块
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
-
获取内部元素标记
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)
-
获取外部元素标记
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
-