363 lines
13 KiB
Plaintext
Raw Normal View History

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)