You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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)

二、霍夫变换改进版(获取规整屋顶轮廓)

原霍夫变换未做线条筛选和轮廓拟合,导致无关线条干扰,改进步骤如下:

  1. 用概率霍夫变换直接获取线段,方便后续筛选
  2. 按角度聚类,只保留屋顶常见的角度区间(比如双坡屋顶的两组对边角度)
  3. 过滤短线条,排除噪声干扰
  4. 用多边形拟合线段生成规整轮廓
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)

三、分水岭算法改进版(精准分割屋顶区域)

原分水岭的前景/背景标记不准确,导致过度分割,调整方向如下:

  1. 用自适应阈值替代OTSU阈值,适配光照不均
  2. 缩小形态学操作的核与迭代次数,减少过度腐蚀
  3. 优化距离变换阈值,确保前景标记精准
  4. 筛选最大连通分量作为目标屋顶
# 自适应阈值分割,适配局部光照差异
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)

四、备选方案:基于轮廓的直接检测

卫星屋顶多为规则多边形,可直接提取轮廓后筛选特征:

  1. 预处理后提取所有轮廓
  2. 按面积、边数、角度特征筛选符合屋顶的轮廓
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 20:43:15