基于OpenCV从卫星图像提取屋顶面的技术求助
卫星图像屋顶检测优化方案
一、预处理优化(解决两种方法的共性问题)
卫星图像普遍存在光照不均、噪声干扰的问题,先做针对性预处理增强边缘、抑制噪声:
import cv2 import numpy as np img = cv2.imread('2 faced roof.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 用CLAHE增强对比度,避免单纯模糊丢失边缘细节 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) enhanced_gray = clahe.apply(gray) # 小核高斯模糊去噪 blur = cv2.GaussianBlur(enhanced_gray, (5,5), 0)
二、霍夫变换改进版(获取规整屋顶轮廓)
原霍夫变换未做线条筛选和轮廓拟合,导致无关线条干扰,改进步骤如下:
- 用概率霍夫变换直接获取线段,方便后续筛选
- 按角度聚类,只保留屋顶常见的角度区间(比如双坡屋顶的两组对边角度)
- 过滤短线条,排除噪声干扰
- 用多边形拟合线段生成规整轮廓
canny = cv2.Canny(blur, 30, 150) # 概率霍夫变换:调整阈值和线段长度/间隙参数 lines = cv2.HoughLinesP(canny, 1, np.pi/180, threshold=50, minLineLength=100, maxLineGap=20) # 筛选符合屋顶角度的线条(根据实际图像调整角度区间) filtered_lines = [] for line in lines: x1,y1,x2,y2 = line[0] angle = abs(np.arctan2(y2-y1, x2-x1) * 180 / np.pi) if (30 <= angle <= 60) or (120 <= angle <= 150): filtered_lines.append(line) # 绘制筛选后的线条并提取轮廓 line_img = np.zeros_like(img) for line in filtered_lines: x1,y1,x2,y2 = line[0] cv2.line(line_img, (x1,y1), (x2,y2), (0,255,0), 2) gray_line = cv2.cvtColor(line_img, cv2.COLOR_BGR2GRAY) contours, _ = cv2.findContours(gray_line, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 取面积最大的轮廓(默认目标屋顶是画面中最大的规则区域) contours = sorted(contours, key=cv2.contourArea, reverse=True)[:1] for cnt in contours: epsilon = 0.02 * cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, epsilon, True) cv2.drawContours(img, [approx], -1, (0,0,255), 3) cv2.imshow('Roof Contour', img) cv2.waitKey(0)
三、分水岭算法改进版(精准分割屋顶区域)
原分水岭的前景/背景标记不准确,导致过度分割,调整方向如下:
- 用自适应阈值替代OTSU阈值,适配光照不均
- 缩小形态学操作的核与迭代次数,减少过度腐蚀
- 优化距离变换阈值,确保前景标记精准
- 筛选最大连通分量作为目标屋顶
# 自适应阈值分割,适配局部光照差异 thresh = cv2.adaptiveThreshold(enhanced_gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) # 小核开运算去除噪声点 kernel = np.ones((2,2), np.uint8) opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1) # 确定背景:减少膨胀次数,避免背景过度扩张 sure_bg = cv2.dilate(opening, kernel, iterations=2) # 距离变换:提高阈值,只保留屋顶中心区域为前景 dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 3) ret, sure_fg = cv2.threshold(dist_transform, 0.3*dist_transform.max(), 255, 0) sure_fg = np.uint8(sure_fg) # 计算未知区域并标记 unknown = cv2.subtract(sure_bg, sure_fg) ret, markers = cv2.connectedComponents(sure_fg) markers = markers + 1 markers[unknown == 255] = 0 # 应用分水岭并提取屋顶区域 markers = cv2.watershed(img, markers) img[markers == -1] = [255,0,0] # 筛选最大连通分量作为目标屋顶 mask = np.zeros_like(gray) mask[markers == 2] = 255 # 根据实际结果调整标记值 contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cv2.drawContours(img, contours, -1, (0,255,0), 3) cv2.imshow('Watershed Roof', img) cv2.waitKey(0)
四、备选方案:基于轮廓的直接检测
卫星屋顶多为规则多边形,可直接提取轮廓后筛选特征:
- 预处理后提取所有轮廓
- 按面积、边数、角度特征筛选符合屋顶的轮廓
canny = cv2.Canny(blur, 30, 150) contours, _ = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 筛选符合屋顶特征的轮廓 for cnt in contours: area = cv2.contourArea(cnt) # 按图像尺寸调整面积范围 if 5000 < area < 50000: perimeter = cv2.arcLength(cnt, True) approx = cv2.approxPolyDP(cnt, 0.02*perimeter, True) # 双坡屋顶通常是4或5个顶点 if len(approx) in [4,5]: # 验证角度特征:存在钝角和锐角组合 angles = [] for i in range(len(approx)): p1 = approx[i][0] p2 = approx[(i+1)%len(approx)][0] p3 = approx[(i+2)%len(approx)][0] v1 = p2 - p1 v2 = p3 - p2 angle = np.arccos(np.dot(v1, v2)/(np.linalg.norm(v1)*np.linalg.norm(v2))) * 180/np.pi angles.append(angle) if any(120 <= a <= 150 for a in angles) and any(30 <= a <= 60 for a in angles): cv2.drawContours(img, [approx], -1, (0,255,0), 3) cv2.imshow('Contour Roof', img) cv2.waitKey(0)
内容的提问来源于stack exchange,提问作者m landry
相关产品推荐
相关产品推荐

