近景摄影测量-像点坐标手动量测程序(圆拟合、圆心点提取)

        近景摄影测量中,像点坐标量测是获取观测值的过程,是大部分任务中必要的数据基础,像点获取之后可以完成基于共线条件方程的误差方程解算、直接线性变换等解析计算。        

        下面编写一个程序用来量测拍摄得到的相片上的控制点、量测点点位的像点坐标,要求能够打开影像,在影像上框选点位,实现计算机自动找到标志点的中心像点坐标,并且保证一定的精确度,要求达到亚像素级提取,同时将标志点中心像点坐标输出。

        主要提取对象是以下两种标志点:

        一、影像获取

        获取两张影像构成立体像对,要求相片中的控制点分布均匀、量测容易、尽量减少遮挡、物方坐标可得。要求定焦拍摄(MF模式,光圈优先模式),影像清晰、明亮。

        我使用的相机:富士-XE4定焦镜头

        拍摄地点:武汉大学遥感信息工程学院室内控制场

        拍摄过程中需要保证相机不调焦,并且能够拍摄到大范围的清晰影像,而物距又无法调整的很大,所以只能通过增大光圈号数(缩小孔径大小)来增大景深(清晰范围)。而如此一来进光量将减少,想要保证影像的亮度,就要提高ISO值或者延长快门速度,提高ISO会降低影像质量,增加噪点;延长快门速度防抖能力将会降低;最终我使用大光圈号数(F11左右)、适当的ISO值(1000左右)、较长的快门速度(1/25左右)借助三脚架稳定相机进行拍摄。

        二、像点坐标量测程序

        使用的方法是圆拟合找出最小包含标志点的圆形,然后使用灰度重心化的方法找出圆心位置,达到亚像素级提取。

        该步骤存在用户与程序的交互操作,故可以考虑使用MFC应用程序或python等。

        我使用的语言与环境:python+opencv

        2.1打开图片与范围框选

        1)使用opencv打开图像并可视化(由于图片过大,所以使用namewindow与、cv2.WINDOW_NORMAL来将图像完整显示到窗口大小),然后进行范围框选,由于需要多次框选,所以编写了循环读取打开图像的程序。

        2)标志点范围的框选使用鼠标事件实现。

        select_box是一个回调函数,它会在鼠标事件(如左键按下、移动、释放)时被触发。这个函数用于在图像上绘制一个矩形框。

  • 当鼠标左键按下时,它记录起始点坐标(ix, iy)。
  • 当鼠标移动时,如果正在绘制(drawing为True),则在图像副本上绘制一个矩形框,并实时更新显示。
  • 当鼠标左键释放时,它计算矩形的坐标,并在原图上绘制一个最终的矩形框。同时,它裁剪出这个矩形区域内的图像,并存储在crop_img变量中。然后,它设置done为True,表示框选已完成。
import cv2
import numpy as np
import math
#(0,0)-------------x-> /pixel
#|
#|
#|
#y
ctr_point={}#控制点使用字典保存 点号:(x,y)
id_point = 0#点号
for i in range(100):
        # 读取图像
        image = cv2.imread('2.jpg')
        # 创建一个窗口显示图像
        cv2.namedWindow('Image',cv2.WINDOW_NORMAL)
        cv2.imshow('Image', image)
        # 标志变量,表示是否完成框选
        done = False
        if id_point=='0':
            break
        def select_box(event, x, y, flags, param):
            global ix, iy, drawing, image_copy,crop_img,x1,y1
            # 当按下鼠标左键时
            if event == cv2.EVENT_LBUTTONDOWN:
                drawing = True
                ix, iy = x, y
            # 当鼠标移动时
            elif event == cv2.EVENT_MOUSEMOVE:
                if drawing:
                    image_copy = image.copy()
                    cv2.rectangle(image_copy, (ix, iy), (x, y), (0, 255, 0), 4)
                    cv2.imshow("Image", image_copy)
            # 当释放鼠标左键时
            elif event == cv2.EVENT_LBUTTONUP:
                drawing = False
                if ix != -1 and iy != -1:
                    # 计算矩形的坐标
                    x1 = min(x, ix)
                    y1 = min(y, iy)
                    rect = (x1, y1, x - x1, y - y1)
                    cv2.rectangle(image, (x1, y1), (x, y), (0, 255, 0), 2)
                    cv2.imshow('Image', image)
                    # 裁剪图像
                    crop_img = image[y1:y1 + rect[3], x1:x1 + rect[2]]
                    #cv2.imshow('Cropped Image', crop_img)
                # 设置标志变量,表示已完成框选
                global done
                done = True
        # 初始化变量
        global ix, iy, drawing, image_copy,crop_img,x1,y1
        drawing = False  # 标志变量,表示是否正在绘制矩形框
        ix, iy = -1, -1  # 矩形框起始点的坐标
        image_copy = None  # 图像的副本,用于绘制矩形框
        # 设置鼠标回调函数
        cv2.setMouseCallback("Image", select_box)
        # 等待按键,直到完成框选
        while not done:
            cv2.waitKey(0)

        2.2标志点提取与中心坐标计算

        最小外接圆&灰度重心化方法

        无论是圆形标志点,还是十字形标志点,似乎都可以使用最小外接圆来找到包括住它们的最小的圆形,于是我准备尝试使用最小外接圆的方法来完成这一任务。

  1. 二值化,将图像变为黑白分明的图像
  2. 在图像中查找轮廓,使用cv2.findContours函数,可能会找出很多轮廓,所以框选尽可能只选中圆形部分图像,减少干扰。
  3. 使用cv2.minEnclosingCircle()函数查找轮廓contours中所有轮廓的最小外接圆。
  4. 上一步会获取很多圆,半径大小不一,通过尝试,找到最合适的阈值来筛选正确的最小外接圆。
  5. 为了提高精度,不直接使用该最小外接圆的圆心作为结果,而是继续利用该圆将框住的范围保留,圆形外的部分全部留白,去除目标圆形以外的干扰。
  6. 将二值化图像黑白取反,将像素坐标进行重心化,即为最终结果,该结果可以达到亚像素级别。

过程中的分步处理结果图:

二值化

最小外接圆外取白

黑白取反

图.最小外接圆处理过程截图

        经过多次检测实验,该方法效果非常好,无论是圆形还是十字形标志点都能够正确的检测,且代码简洁运行速度快。

        将计算得到的圆心点绘制到图像上再次展示,可以观测到圆心点提取是否精确,有无偏离。

        注意,计算得到的坐标是框选范围内像素坐标,还需要加上框选范围左上角在整个原图像中的坐标,乘以像素大小才可以得到标志点中心在原图像中的像平面坐标。

        

#完成框选,开始进行标志点检测 crop_img是框选得到的图像
        img = crop_img     # 0或者cv2.IMREAD_GRAYSCALE  读取为灰度图像
        #要确保传入的图片类型正确,彩色or灰度,单通or多通
        img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#灰度化
        height, width = img_gray.shape
        ret , binary = cv2.threshold(img_gray , 100 , 255 ,cv2.THRESH_BINARY |cv2.THRESH_OTSU )#灰度二值化
        cv2.waitKey(0)
        contours , hirrarchy = cv2.findContours( binary , cv2.RETR_TREE ,
                cv2.CHAIN_APPROX_NONE)#寻找轮廓
        #cv2.imshow('二值化',binary)
        for contour in contours:
            (x,y) , radius = cv2.minEnclosingCircle(contour)#对每个轮廓找最小包含圆
            if radius>height/3 and radius < height/2:#自定义阈值
                center = (int(x) , int(y))#获取中心坐标
                radius = int(radius)#获取半径
                cv2.circle(img_gray , center , 2 , (0,0,255) , 2)#绘制提取到的圆形范围
                #下面的二层循环是为了将圆形范围外的像素变为白色,消除灰度重心化时的外界误差
                for x0 in range(width):  
                    for y0 in range(height):  
                        dis = math.sqrt((x0-x)*(x0-x)+(y0-y)*(y0-y))
                        if dis < radius and binary[y0,x0] == 0:
                            continue
                        else:
                            binary[y0,x0] = 255
                #cv2.imshow('框选',binary)
                #灰度重心化计算亚像素级圆心坐标
                cv2.bitwise_not(binary,binary)
                #cv2.imshow('取反',binary)
                cv2.waitKey(0)
                M=cv2.moments(binary)
                cx=(M["m10"]/M["m00"])
                cy=(M["m01"]/M["m00"])
                center2 = (int(cx) , int(cy))
                cv2.circle(img_gray , center2 , 2 , (255,255,255) , 2)
                cv2.namedWindow('img',cv2.WINDOW_NORMAL) 
                cv2.imshow("img",img_gray)
                cv2.waitKey(0)
                #输入点号,保存为字典
                id_point=input("请输入点号:")
                print("圆心坐标为:",cx+x1,cy+y1)
                #如果输入的点号不是0或1则保存
                if id_point != '0' and id_point != '1':#if 0 
                    ctr_point[id_point]=[cx+x1,cy+y1]
        #如果是0则结束程序,写入文件
        if id_point=='0':
            print(ctr_point)
            with open('pointallRR.txt', 'w') as file:  
            # 遍历字典的项  
                for key, value in ctr_point.items():  
                    file.write(f"{key}  {value[0]} {value[1]}\n"  )
        

        

        2.3像点坐标保存与输出

        完成了以上像点坐标提取后,只需要最后一步,将自动提取到的像点坐标标记上序号保存输出即可。

  1. 输入标志点点号:

图.键入序号程序界面

     2.将标志点中心坐标和序号组成键值对,保存在字典中(这里的像点坐标是上一步手动框选图像中的像素坐标,加上手动框选的图像在原图像中的坐标后计算得到的坐标,是像点在原始图像中的坐标):

      3.保存为文件,以空格间隔:

表: 像点量测结果文件(单位/mm)

像点号

量测x坐标

量测y坐标

340

3488.7729852440407

3463.123344684071

341

3480.232709519935

2915.161106590724

344

3445.9122557726464

1202.829840142096

345

3434.537372147915

737.9976396538159

346

3424.1253462603877

259.07790858725764

141

3313.3726260641783

4034.6950447500544

143

3283.066207184628

2554.620509607352

144

3261.5910143584993

1745.5479388605836

145

3250.8248536695182

1119.5056280954525

146

3234.8441532258066

470.79637096774195

223

2873.4306748466256

1599.1601226993864

        三、使用方法:

     

        框选待提取标志点范围,计算中心坐标,可视化提取点位

  1.         若目测点位精确,则键入点号保存该点

        若提取错误,想重新框选,则键入1,跳过该次提取

        若结束提取,再任意框选一个标志点,键入0,会自动保存为文件

        四、总结

        该代码程序可以实现像点坐标的手动框选与提取中心点位,且具有较高的精度,并且可以输出可用的像点坐标文件。可以完成后续后方交会和直接线性变换解析。

        不足:

        在原理与数据精度方面没有太大问题,可以优化的是程序交互操作。

        循环框选的程序可以更加便捷便于操作,添加交互程序窗口与界面等,程序结束与错误点跳过也可以设计更加人性化的操作方法加以优化,可以加入鼠标滚动功能,放大缩小图像便于框选操作等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值