363 lines
13 KiB
Plaintext
363 lines
13 KiB
Plaintext
import numpy as np
|
||
import cv2
|
||
import math
|
||
import os
|
||
import pyproj
|
||
# import gdal
|
||
from osgeo import gdal, osr
|
||
gdal.UseExceptions() # 显式启用 GDAL 异常
|
||
from touying.ImageReproject_python.img_types import Point, POS, ImgEO, CamParas, ImgExif
|
||
from touying.ImageReproject_python.dpal import DPAL
|
||
|
||
class ImageReproject:
|
||
"""图像重投影类,实现目标定位、红线重投影和图像重投影功能"""
|
||
|
||
def __init__(self):
|
||
"""初始化"""
|
||
pass
|
||
|
||
def target_positioning(self, u, v, interval, gimbal_pitch, gimbal_yaw, gimbal_roll,
|
||
h, l, b, cam_paras, img_width, img_height, dem_file,
|
||
x=None, y=None, z=None):
|
||
"""目标定位
|
||
|
||
Args:
|
||
u: 图像x坐标
|
||
v: 图像y坐标
|
||
interval: 间隔
|
||
gimbal_pitch: 云台俯仰角(弧度)
|
||
gimbal_yaw: 云台偏航角(弧度)
|
||
gimbal_roll: 云台横滚角(弧度)
|
||
h: 高度
|
||
l: 经度(弧度)
|
||
b: 纬度(弧度)
|
||
cam_paras: 相机参数
|
||
img_width: 图像宽度
|
||
img_height: 图像高度
|
||
dem_file: DEM文件路径
|
||
x: 输出经度
|
||
y: 输出纬度
|
||
z: 输出高度
|
||
|
||
Returns:
|
||
(x, y, z): 目标点的经度、纬度和高度
|
||
"""
|
||
# 创建ImgExif对象并设置参数
|
||
img_exif = ImgExif()
|
||
img_exif.width = img_width
|
||
img_exif.height = img_height
|
||
img_exif.B = b
|
||
img_exif.L = l
|
||
img_exif.H = h
|
||
img_exif.GimbalYaw = gimbal_yaw
|
||
img_exif.GimbalPitch = gimbal_pitch
|
||
img_exif.GimbalRoll = gimbal_roll
|
||
img_exif.camPara = cam_paras
|
||
|
||
# 计算投影矩阵
|
||
img_exif.com_zone()
|
||
img_exif.computing_p_mat()
|
||
|
||
# 去畸变处理
|
||
u_undistort, v_undistort = img_exif.undistortion_point(u, v)
|
||
|
||
# 创建UTM投影
|
||
wgs84 = pyproj.CRS('EPSG:4326')
|
||
utm_proj = pyproj.CRS(f'EPSG:{32600+img_exif.UTMzone}')
|
||
transformer = pyproj.Transformer.from_crs(utm_proj, wgs84, always_xy=True)
|
||
# 获取DEM的最小和最大高程
|
||
minZ, maxZ = self.get_dem_min_max(dem_file)
|
||
|
||
planes = []
|
||
h = minZ
|
||
while h <= maxZ:
|
||
planes.append([0.0, 0.0, 1.0, -h])
|
||
h += interval
|
||
|
||
points = []
|
||
for plane in planes:
|
||
new_row = np.array([plane], dtype=np.float64)
|
||
P_ = np.vstack([img_exif.P, new_row])
|
||
mat_inv = np.linalg.inv(P_)
|
||
img_pt = np.array([[u_undistort], [v_undistort], [1], [0]], dtype=np.float64)
|
||
target_ptr = np.dot(mat_inv, img_pt)
|
||
point = self.normalized(target_ptr)
|
||
# utm转经纬度
|
||
point = transformer.transform(point[0], point[1], point[2])
|
||
points.append(point)
|
||
|
||
# 过滤不符合高程的点
|
||
filtered_points = []
|
||
for pt in points:
|
||
projectZ = pt[2]
|
||
demZ = self.get_dem_z(dem_file, pt[0], pt[1])
|
||
diff = projectZ - demZ
|
||
if abs(diff) <= interval:
|
||
filtered_points.append(pt)
|
||
|
||
if len(filtered_points) == 0:
|
||
print("未得到采样点!")
|
||
return False, None
|
||
if len(filtered_points) == 1:
|
||
return filtered_points[0][0], filtered_points[0][1], filtered_points[0][2]
|
||
else:
|
||
cam_center = np.array([img_exif.EO.Xs, img_exif.EO.Ys, img_exif.EO.Zs])
|
||
dists = [np.linalg.norm(np.array(pt) - cam_center) for pt in filtered_points]
|
||
min_idx = int(np.argmin(dists))
|
||
return filtered_points[min_idx][0], filtered_points[min_idx][1], filtered_points[min_idx][2]
|
||
|
||
def red_line_reproject(self, gimbal_pitch, gimbal_yaw, gimbal_roll, h, l, b,
|
||
cam_paras, img_width, img_height, points, img_file, img_save_file):
|
||
"""红线重投影
|
||
|
||
Args:
|
||
gimbal_pitch: 云台俯仰角(弧度)
|
||
gimbal_yaw: 云台偏航角(弧度)
|
||
gimbal_roll: 云台横滚角(弧度)
|
||
h: 高度
|
||
l: 经度(弧度)
|
||
b: 纬度(弧度)
|
||
cam_paras: 相机参数
|
||
img_width: 图像宽度
|
||
img_height: 图像高度
|
||
points: 点坐标列表
|
||
img_file: 输入图像文件路径
|
||
img_save_file: 输出图像文件路径
|
||
|
||
Returns:
|
||
None
|
||
"""
|
||
# 创建ImgExif对象并设置参数
|
||
img_exif = ImgExif()
|
||
img_exif.width = img_width
|
||
img_exif.height = img_height
|
||
img_exif.B = b
|
||
img_exif.L = l
|
||
img_exif.H = h
|
||
img_exif.GimbalYaw = gimbal_yaw
|
||
img_exif.GimbalPitch = gimbal_pitch
|
||
img_exif.GimbalRoll = gimbal_roll
|
||
img_exif.camPara = cam_paras
|
||
|
||
# 计算投影矩阵
|
||
img_exif.com_zone()
|
||
img_exif.computing_p_mat()
|
||
|
||
# 创建UTM投影
|
||
wgs84 = pyproj.CRS('EPSG:4326')
|
||
utm_proj = pyproj.CRS(f'EPSG:{32600+img_exif.UTMzone}')
|
||
transformer = pyproj.Transformer.from_crs(wgs84, utm_proj, always_xy=True)
|
||
# 将点坐标转换为UTM坐标
|
||
utm_points = []
|
||
for point in points:
|
||
# 将经纬度转换为UTM坐标
|
||
x, y = transformer.transform(point.Y , point.X )
|
||
utm_point = Point(x, y, point.Z)
|
||
utm_points.append(utm_point)
|
||
|
||
# 读取图像
|
||
img = cv2.imread(img_file)
|
||
if img is None:
|
||
print(f"无法读取图像文件: {img_file}")
|
||
return
|
||
pixel_lists=[]
|
||
# 投影点到图像上
|
||
for point in utm_points:
|
||
# 构建三维点的齐次坐标
|
||
point_3d = np.array([point.X, point.Y, point.Z, 1.0])
|
||
|
||
# 投影到图像平面
|
||
point_2d = np.matmul(img_exif.P, point_3d)
|
||
|
||
# 归一化
|
||
point_2d = point_2d / point_2d[2]
|
||
|
||
# 应用畸变
|
||
u, v = img_exif.distortion_point(point_2d[0], point_2d[1])
|
||
|
||
#
|
||
# # 检查点是否在图像范围内
|
||
if 0 <= u < img_width and 0 <= v < img_height:
|
||
pixel_lists.append({
|
||
"u": int(u),
|
||
"v": int(v)
|
||
})
|
||
# # 在图像上绘制点
|
||
# cv2.circle(img, (int(u), int(v)), 5, (0, 0, 255), -1)
|
||
|
||
# 保存图像
|
||
# cv2.imwrite(img_save_file, img)
|
||
return pixel_lists
|
||
def img_reproject(self, gimbal_pitch, gimbal_yaw, gimbal_roll, h, l, b,
|
||
cam_paras, img_width, img_height, obj_longitude, obj_latitude, obj_height,
|
||
pixel_x=None, pixel_y=None):
|
||
"""图像重投影
|
||
|
||
Args:
|
||
gimbal_pitch: 云台俯仰角(弧度)
|
||
gimbal_yaw: 云台偏航角(弧度)
|
||
gimbal_roll: 云台横滚角(弧度)
|
||
h: 高度
|
||
l: 经度(弧度)
|
||
b: 纬度(弧度)
|
||
cam_paras: 相机参数
|
||
img_width: 图像宽度
|
||
img_height: 图像高度
|
||
obj_longitude: 目标点经度(度)
|
||
obj_latitude: 目标点纬度(度)
|
||
obj_height: 目标点高度
|
||
pixel_x: 输出像素x坐标
|
||
pixel_y: 输出像素y坐标
|
||
|
||
Returns:
|
||
(pixel_x, pixel_y): 目标点在图像上的像素坐标
|
||
"""
|
||
# 创建ImgExif对象并设置参数
|
||
img_exif = ImgExif()
|
||
img_exif.width = img_width
|
||
img_exif.height = img_height
|
||
img_exif.B = b
|
||
img_exif.L = l
|
||
img_exif.H = h
|
||
img_exif.GimbalYaw = gimbal_yaw
|
||
img_exif.GimbalPitch = gimbal_pitch
|
||
img_exif.GimbalRoll = gimbal_roll
|
||
img_exif.camPara = cam_paras
|
||
|
||
# 计算投影矩阵
|
||
img_exif.com_zone()
|
||
img_exif.computing_p_mat()
|
||
|
||
# 创建UTM投影
|
||
wgs84 = pyproj.CRS('EPSG:4326')
|
||
utm_proj = pyproj.CRS(f'EPSG:{32600+img_exif.UTMzone}')
|
||
transformer = pyproj.Transformer.from_crs(wgs84, utm_proj, always_xy=True)
|
||
# 将目标点经纬度转换为UTM坐标
|
||
obj_x, obj_y = transformer.transform(obj_longitude, obj_latitude)
|
||
|
||
# 构建三维点的齐次坐标
|
||
point_3d = np.array([obj_x, obj_y, obj_height, 1.0])
|
||
|
||
# 投影到图像平面
|
||
point_2d = np.matmul(img_exif.P, point_3d)
|
||
|
||
# 归一化
|
||
point_2d = point_2d / point_2d[2]
|
||
|
||
# 应用畸变
|
||
u, v = img_exif.distortion_point(point_2d[0], point_2d[1])
|
||
|
||
# 设置输出结果
|
||
if pixel_x is not None:
|
||
pixel_x = u
|
||
if pixel_y is not None:
|
||
pixel_y = v
|
||
|
||
return u, v
|
||
|
||
def get_dem_min_max(self, dem_file):
|
||
"""获取DEM文件的最小和最大高程
|
||
|
||
Args:
|
||
dem_file: DEM文件路径
|
||
|
||
Returns:
|
||
(min_z, max_z): 最小和最大高程
|
||
"""
|
||
# 打开DEM文件
|
||
dataset = gdal.Open(dem_file, gdal.GA_ReadOnly)
|
||
if dataset is None:
|
||
print(f"无法打开DEM文件: {dem_file}")
|
||
return 0, 1000 # 返回默认值
|
||
|
||
# 获取波段
|
||
band = dataset.GetRasterBand(1)
|
||
|
||
# 获取统计信息
|
||
min_z, max_z, mean, std_dev = band.GetStatistics(True, True)
|
||
|
||
# 关闭数据集
|
||
dataset = None
|
||
|
||
return min_z, max_z
|
||
|
||
def get_dem_z(self, dem_file, x, y):
|
||
"""从DEM文件中获取指定点的高程
|
||
|
||
Args:
|
||
dem_file: DEM文件路径
|
||
x: UTM X坐标
|
||
y: UTM Y坐标
|
||
|
||
Returns:
|
||
z: 高程值,如果无法获取则返回None
|
||
"""
|
||
# 打开DEM文件
|
||
dataset = gdal.Open(dem_file, gdal.GA_ReadOnly)
|
||
if dataset is None:
|
||
print(f"无法打开DEM文件: {dem_file}")
|
||
return None
|
||
|
||
# 获取地理变换参数
|
||
geotransform = dataset.GetGeoTransform()
|
||
|
||
# 获取波段
|
||
band = dataset.GetRasterBand(1)
|
||
|
||
# 计算像素坐标
|
||
px = int((x - geotransform[0]) / geotransform[1])
|
||
py = int((y - geotransform[3]) / geotransform[5])
|
||
|
||
# 检查像素坐标是否在范围内
|
||
if px < 0 or px >= dataset.RasterXSize or py < 0 or py >= dataset.RasterYSize:
|
||
dataset = None
|
||
return None
|
||
|
||
# 读取高程值
|
||
data = band.ReadAsArray(px, py, 1, 1)
|
||
if data is None:
|
||
dataset = None
|
||
return None
|
||
|
||
# 获取高程值
|
||
z = data[0, 0]
|
||
|
||
# 检查是否为NoData值
|
||
no_data_value = band.GetNoDataValue()
|
||
if no_data_value is not None and z == no_data_value:
|
||
dataset = None
|
||
return None
|
||
|
||
# 关闭数据集
|
||
dataset = None
|
||
|
||
return z
|
||
def normalized(self, mat): # 归一化
|
||
return [mat[0,0]/mat[3,0], mat[1,0]/mat[3,0], mat[2,0]/mat[3,0]]
|
||
|
||
# C风格接口
|
||
def ir_target_positioning(u, v, interval, gimbal_pitch, gimbal_yaw, gimbal_roll,
|
||
h, l, b, cam_paras, img_width, img_height, dem_file,
|
||
x=None, y=None, z=None):
|
||
"""目标定位的C风格接口"""
|
||
reproject = ImageReproject()
|
||
return reproject.target_positioning(u, v, interval, gimbal_pitch, gimbal_yaw, gimbal_roll,
|
||
h, l, b, cam_paras, img_width, img_height, dem_file,
|
||
x, y, z)
|
||
|
||
def ir_red_line_reproject(gimbal_pitch, gimbal_yaw, gimbal_roll, h, l, b,
|
||
cam_paras, img_width, img_height, points, img_file, img_save_file):
|
||
"""红线重投影的C风格接口"""
|
||
reproject = ImageReproject()
|
||
pixel_list=reproject.red_line_reproject(gimbal_pitch, gimbal_yaw, gimbal_roll, h, l, b,
|
||
cam_paras, img_width, img_height, points, img_file, img_save_file)
|
||
return pixel_list
|
||
|
||
def ir_img_reproject(gimbal_pitch, gimbal_yaw, gimbal_roll, h, l, b,
|
||
cam_paras, img_width, img_height, obj_longitude, obj_latitude, obj_height,
|
||
pixel_x=None, pixel_y=None):
|
||
"""图像重投影的C风格接口"""
|
||
reproject = ImageReproject()
|
||
return reproject.img_reproject(gimbal_pitch, gimbal_yaw, gimbal_roll, h, l, b,
|
||
cam_paras, img_width, img_height, obj_longitude, obj_latitude, obj_height,
|
||
pixel_x, pixel_y) |