ThreatSourceLibaray/ThreatSource/src/Guidance/InfraredImageGenerator.cs

514 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Linq; // Add Linq for FirstOrDefault
using ThreatSource.Equipment;
using ThreatSource.Utils;
using AirTransmission;
using ThreatSource.Jammer; // 添加引用
using ThreatSource.Simulation; // Add Simulation namespace
namespace ThreatSource.Guidance
{
/// <summary>
/// 红外图像生成器,用于生成目标的红外图像
/// </summary>
/// <remarks>
/// 该类实现红外图像的生成过程:
/// - 计算目标在图像平面上的投影
/// - 根据目标特性生成红外强度
/// - 添加背景噪声
/// - 考虑大气透过率影响
/// </remarks>
public class InfraredImageGenerator
{
/// <summary>
/// 图像宽度,单位:像素
/// </summary>
private readonly int imageWidth;
/// <summary>
/// 图像高度,单位:像素
/// </summary>
private readonly int imageHeight;
/// <summary>
/// 视场角,单位:弧度
/// </summary>
private readonly double fieldOfView;
/// <summary>
/// 背景辐射强度单位W/sr
/// </summary>
private readonly double backgroundIntensity;
/// <summary>
/// 红外波长,单位:微米
/// </summary>
private readonly double wavelength;
/// <summary>
/// 随机数生成器
/// </summary>
private readonly Random random = new();
/// <summary>
/// 烟幕温度转换为强度的比例因子 (简化 T^4 关系)
/// </summary>
private const double SMOKE_TEMPERATURE_FACTOR = 1e-11;
/// <summary>
/// 目标温度(K)转换为强度(W/sr)的比例因子 (基于简化 T^4) - **需要调整**
/// </summary>
private const double TEMPERATURE_TO_INTENSITY_FACTOR = 1e-6; // Initial guess 1e-11, changed to 1e-7, now 1e-6
private const double KELVIN_OFFSET = 273.15;
// 视线坐标系
private Vector3D forward; // 视线方向
private Vector3D right; // 图像平面右方向
private Vector3D up; // 图像平面上方向
/// <summary>
/// 初始化红外图像生成器的新实例
/// </summary>
/// <param name="imageWidth">图像宽度</param>
/// <param name="imageHeight">图像高度</param>
/// <param name="fieldOfView">视场角</param>
/// <param name="backgroundIntensity">背景辐射强度单位W/sr典型地表背景约0.01-0.1 W/sr</param>
/// <param name="wavelength">红外波长单位微米默认为3.0(中波红外)</param>
public InfraredImageGenerator(
int imageWidth = 640,
int imageHeight = 512,
double fieldOfView = Math.PI / 18,
double backgroundIntensity = 0.01,
double wavelength = 3.0)
{
this.imageWidth = imageWidth;
this.imageHeight = imageHeight;
this.fieldOfView = fieldOfView;
this.backgroundIntensity = backgroundIntensity;
this.wavelength = wavelength;
// Initialize coordinate system with default values
forward = Vector3D.UnitZ;
right = Vector3D.UnitX;
up = Vector3D.UnitY;
}
/// <summary>
/// 更新视线坐标系
/// </summary>
private void UpdateLineOfSightFrame(Vector3D missilePosition, Vector3D targetPosition)
{
// 计算视线方向
forward = (targetPosition - missilePosition).Normalize();
Console.WriteLine($"视线方向: {forward}");
// 选择合适的上方向
Vector3D worldUp = Math.Abs(Vector3D.DotProduct(forward, Vector3D.UnitZ)) > 0.99
? Vector3D.UnitY
: Vector3D.UnitZ;
// 计算右方向和上方向
right = Vector3D.CrossProduct(forward, worldUp).Normalize();
up = Vector3D.CrossProduct(right, forward);
}
/// <summary>
/// 将世界坐标投影到图像平面
/// </summary>
private (double x, double y) ProjectToImagePlane(Vector3D worldPoint, Vector3D origin)
{
Vector3D relativePos = worldPoint - origin;
// 计算在图像平面上的投影
double x = Vector3D.DotProduct(relativePos, right);
double y = Vector3D.DotProduct(relativePos, up);
double z = Vector3D.DotProduct(relativePos, forward);
// 转换为角度
double angleX = Math.Atan2(x, z);
double angleY = Math.Atan2(y, z);
Console.WriteLine($"投影角度: X={angleX:F6} 弧度, Y={angleY:F6} 弧度");
return (angleX, angleY);
}
/// <summary>
/// 将角度转换为像素坐标
/// </summary>
private (int x, int y) AngleToPixel(double angleX, double angleY)
{
double pixelSize = fieldOfView / imageWidth;
int pixelX = imageWidth/2 + (int)(angleX / pixelSize);
int pixelY = imageHeight/2 + (int)(angleY / pixelSize);
return (pixelX, pixelY);
}
/// <summary>
/// 计算大气透过率
/// </summary>
/// <param name="distance">距离(米)</param>
/// <param name="weather">天气条件</param>
/// <param name="smokeAttenuation">烟幕衰减</param>
/// <returns>大气透过率0-1之间</returns>
private double CalculateAtmosphericTransmittance(double distance, Weather weather, double smokeAttenuation)
{
double transmittance;
// 使用AtmosphereDllWrapper计算红外波段的透过率
transmittance = AtmosphereDllWrapper.CalculateTransmittance(
distance,
RadiationType.Infrared,
wavelength,
weather);
// 考虑烟幕衰减
transmittance*= smokeAttenuation;
return transmittance;
}
/// <summary>
/// 应用遮蔽型烟幕效果 (根据类型区分形状)
/// </summary>
/// <param name="smokeIntensityLayer">存储烟幕强度的图层</param>
/// <param name="missilePosition">导弹当前位置</param>
/// <param name="simulationManager">仿真管理器实例</param>
private void ApplyObscuringSmokeOverlay(double[,] smokeIntensityLayer, Vector3D missilePosition, ISimulationManager simulationManager)
{
const int minPixelSize = 3; // 最小像素尺寸 (直径或边长)
var smokeGrenades = simulationManager.GetEntitiesByType<SmokeGrenade>();
foreach (var smoke in smokeGrenades)
{
// 检查是否是遮蔽型烟幕 - 通过 config 访问
// 确保 config 和 RadiationTemperature 存在
if (smoke.IsActive ==false || smoke.config == null || !smoke.config.IsObscuring || smoke.config.RadiationTemperature <= 0) continue;
Vector3D toSmoke = smoke.Position - missilePosition;
double distance = toSmoke.Magnitude();
// 粗略检查烟幕是否在前方视野内
if (Vector3D.DotProduct(toSmoke.Normalize(), forward) < Math.Cos(fieldOfView / 2.0)) continue; // Use half FOV
// 计算烟幕强度 (基于 T^4 简化模型) - 通过 config 访问
// 注意: 这里没有考虑从烟幕到导弹的大气衰减,可以根据需要添加
double smokeIntensity = SMOKE_TEMPERATURE_FACTOR * Math.Pow(smoke.config.RadiationTemperature, 4);
// 投影烟幕中心
var (angleX, angleY) = ProjectToImagePlane(smoke.Position, missilePosition);
var (smokeCenterX, smokeCenterY) = AngleToPixel(angleX, angleY);
// 检查投影中心是否大致在图像内 (允许部分超出)
// if (smokeCenterX < -imageWidth/2 || smokeCenterX >= imageWidth*1.5 || smokeCenterY < -imageHeight/2 || smokeCenterY >= imageHeight*1.5) continue;
// More robust check needed? For now, let's proceed if center is roughly projectable.
// 根据烟幕类型计算尺寸并填充 - 通过 config 访问
switch (smoke.config.SmokeType)
{
case SmokeScreenType.Wall:
// 墙状烟幕,近似为水平矩形
double wallWidth = smoke.config.WallWidth; // 通过 config 访问
double wallHeight = smoke.config.WallHeight; // 通过 config 访问
int pixelWidth = (int)Math.Max(minPixelSize, (wallWidth / distance) * imageWidth / fieldOfView);
int pixelHeight = (int)Math.Max(minPixelSize, (wallHeight / distance) * imageHeight / fieldOfView); // Use imageHeight for vertical FOV estimate
int halfPixelWidth = pixelWidth / 2;
int halfPixelHeight = pixelHeight / 2;
// 确定绘制边界,确保在图像范围内
int minX_rect = Math.Max(0, smokeCenterX - halfPixelWidth);
int maxX_rect = Math.Min(imageWidth - 1, smokeCenterX + halfPixelWidth);
int minY_rect = Math.Max(0, smokeCenterY - halfPixelHeight);
int maxY_rect = Math.Min(imageHeight - 1, smokeCenterY + halfPixelHeight);
for (int y = minY_rect; y <= maxY_rect; y++)
{
for (int x = minX_rect; x <= maxX_rect; x++)
{
// 存储烟幕强度,处理重叠(取最大值)
smokeIntensityLayer[y, x] = Math.Max(smokeIntensityLayer[y, x], smokeIntensity);
}
}
Console.WriteLine($"记录遮蔽烟幕(Wall): 中心({smokeCenterX},{smokeCenterY}), 尺寸{pixelWidth}x{pixelHeight}px, 强度{smokeIntensity:F6}");
break;
case SmokeScreenType.Cloud:
// 云状烟幕,近似为圆形
double cloudDiameter = smoke.config.CloudDiameter; // 通过 config 访问
int pixelDiameter = (int)Math.Max(minPixelSize, (cloudDiameter / distance) * imageWidth / fieldOfView);
int pixelRadius = pixelDiameter / 2;
int radiusSquared = pixelRadius * pixelRadius;
// 确定绘制边界,确保在图像范围内
int minX_circle = Math.Max(0, smokeCenterX - pixelRadius);
int maxX_circle = Math.Min(imageWidth - 1, smokeCenterX + pixelRadius);
int minY_circle = Math.Max(0, smokeCenterY - pixelRadius);
int maxY_circle = Math.Min(imageHeight - 1, smokeCenterY + pixelRadius);
for (int y = minY_circle; y <= maxY_circle; y++)
{
for (int x = minX_circle; x <= maxX_circle; x++)
{
int dx = x - smokeCenterX;
int dy = y - smokeCenterY;
if (dx * dx + dy * dy <= radiusSquared)
{
// 存储烟幕强度,处理重叠(取最大值)
smokeIntensityLayer[y, x] = Math.Max(smokeIntensityLayer[y, x], smokeIntensity);
}
}
}
Console.WriteLine($"记录遮蔽烟幕(Cloud): 中心({smokeCenterX},{smokeCenterY}), 半径{pixelRadius}px, 强度{smokeIntensity:F6}");
break;
}
}
}
/// <summary>
/// 生成目标的红外图像
/// </summary>
/// <param name="target">目标对象</param>
/// <param name="missilePosition">导弹位置</param>
/// <param name="smokeAttenuation">衰减型烟幕透过率</param>
/// <param name="simulationManager">仿真管理器实例</param>
/// <returns>红外图像</returns>
public InfraredImage GenerateImage(BaseEquipment target, Vector3D missilePosition, double smokeAttenuation, ISimulationManager simulationManager)
{
// 更新视线坐标系
UpdateLineOfSightFrame(missilePosition, target.Position);
// 创建并初始化烟幕强度图层 (-1表示无烟幕)
double[,] smokeIntensityLayer = new double[imageHeight, imageWidth];
for (int y = 0; y < imageHeight; y++)
{
for (int x = 0; x < imageWidth; x++)
{
smokeIntensityLayer[y, x] = -1.0;
}
}
// 应用(记录)遮蔽型烟幕效果到图层
ApplyObscuringSmokeOverlay(smokeIntensityLayer, missilePosition, simulationManager);
// --- 新增:根据烟幕强度图层创建烟幕覆盖掩码 ---
bool[,] smokeCoverageMask = new bool[imageHeight, imageWidth];
for (int y = 0; y < imageHeight; y++)
{
for (int x = 0; x < imageWidth; x++)
{
smokeCoverageMask[y, x] = (smokeIntensityLayer[y, x] >= 0); // >= 0 表示被覆盖
}
}
// --- 结束新增 ---
// --- 新增:在此处创建 image 对象并传入掩码 ---
var image = new InfraredImage(imageWidth, imageHeight, fieldOfView / imageWidth, forward, smokeCoverageMask);
// --- 结束新增 ---
// 计算目标中心投影
var (angleX, angleY) = ProjectToImagePlane(target.Position, missilePosition);
var (centerX, centerY) = AngleToPixel(angleX, angleY);
// 计算目标尺寸
double distance = (target.Position - missilePosition).Magnitude();
double targetLengthAngle = Math.Atan2(target.Properties.Length, distance);
double targetWidthAngle = Math.Atan2(target.Properties.Width, distance);
int pixelLength = (int)(targetLengthAngle / (fieldOfView / imageWidth));
int pixelWidth = (int)(targetWidthAngle / (fieldOfView / imageWidth));
// 确保最小尺寸
pixelLength = Math.Max(1, pixelLength);
pixelWidth = Math.Max(1, pixelWidth);
Console.WriteLine($"生成目标 {target.Id} 的图像,尺寸: {pixelLength}x{pixelWidth} 像素,目标中心: ({centerX}, {centerY})");
// 计算大气和衰减型烟幕的总透过率
double transmittance = CalculateAtmosphericTransmittance(distance, simulationManager.CurrentWeather, smokeAttenuation);
Console.WriteLine($"大气及衰减烟幕透过率: {transmittance}");
// 生成目标图像强度,考虑遮蔽烟幕覆盖
GenerateTargetIntensity(image, smokeIntensityLayer, centerX, centerY, pixelLength, pixelWidth, target, distance, transmittance);
// 添加背景和噪声(这会影响到烟幕和目标区域)
AddBackgroundAndNoise(image, simulationManager.CurrentWeather);
return image;
}
/// <summary>
/// 生成目标的红外强度分布,考虑遮蔽烟幕覆盖
/// </summary>
/// <param name="image">红外图像对象</param>
/// <param name="smokeIntensityLayer">遮蔽烟幕强度图层</param>
/// <param name="centerX">目标中心X像素坐标</param>
/// <param name="centerY">目标中心Y像素坐标</param>
/// <param name="pixelLength">目标像素长度</param>
/// <param name="pixelWidth">目标像素宽度</param>
/// <param name="target">目标设备对象</param>
/// <param name="distance">目标距离</param>
/// <param name="transmittance">大气和衰减烟幕透过率</param>
private static void GenerateTargetIntensity(
InfraredImage image,
double[,] smokeIntensityLayer, // 新增参数
int centerX,
int centerY,
int pixelLength,
int pixelWidth,
BaseEquipment target,
double distance,
double transmittance)
{
// --- 开始: 使用 ThermalPattern 重构 ---
// 获取目标当前的 3x3 热成像模式 (摄氏度)
double[,] thermalPattern = target.GetCurrentThermalPattern();
if (thermalPattern == null)
{
Console.WriteLine($"警告: 目标 {target.Id} 未提供 ThermalPattern无法生成基于模式的强度。");
return;
}
int halfLength = pixelLength / 2;
int halfWidth = pixelWidth / 2;
int pixelsSet = 0;
double maxSetIntensity = 0;
// 确保映射的分母不为零
double patternPixelWidth = Math.Max(1.0, (double)pixelWidth / 3.0);
double patternPixelHeight = Math.Max(1.0, (double)pixelLength / 3.0); // Assuming Length maps to Cols (Height of pattern grid)
// 遍历目标在图像中的包围盒
for (int dy = -halfWidth; dy <= halfWidth; dy++)
{
int y = centerY + dy;
if (y < 0 || y < 0 || y >= image.Height) continue; // Corrected y boundary check
for (int dx = -halfLength; dx <= halfLength; dx++)
{
int x = centerX + dx;
if (x < 0 || x >= image.Width) continue;
double finalIntensity;
double smokeIntensity = smokeIntensityLayer[y, x];
if (smokeIntensity >= 0) // 检查此像素是否被遮蔽烟幕覆盖
{
finalIntensity = smokeIntensity;
}
else // 未被烟幕覆盖,基于 ThermalPattern 计算强度
{
// 1. 将像素坐标映射到 3x3 热成像模式的索引
// Map offset relative to top-left of bbox to [0, width/height-1] then divide
// Map dy from [-halfWidth, +halfWidth] to [0, pixelWidth-1]
// Map dx from [-halfLength, +halfLength] to [0, pixelLength-1]
int pixelYInBox = dy + halfWidth;
int pixelXInBox = dx + halfLength;
// Map to pattern indices [0, 2]
int patternRow = (int)Math.Floor(pixelYInBox / patternPixelWidth);
int patternCol = (int)Math.Floor(pixelXInBox / patternPixelHeight); // X maps to Col
// Clamp indices to [0, 2]
patternRow = Math.Max(0, Math.Min(2, patternRow));
patternCol = Math.Max(0, Math.Min(2, patternCol));
// 2. 获取对应模式单元的温度 (摄氏度)
double tempCelsius = thermalPattern[patternRow, patternCol];
// 3. 转换为开尔文
double tempKelvin = tempCelsius + KELVIN_OFFSET;
// 4. 使用简化的 T^4 定律计算基础强度 (W/sr)
// Ensure tempKelvin is positive before power calculation
double pixelBaseIntensity = tempKelvin > 0 ? TEMPERATURE_TO_INTENSITY_FACTOR * Math.Pow(tempKelvin, 4) : 0;
// 5. 应用大气透过率和距离衰减 (使用 distance^2)
// Avoid division by zero distance
finalIntensity = (distance > 1e-6) ? (pixelBaseIntensity * transmittance / (distance * distance)) : pixelBaseIntensity * transmittance;
}
// 设置最终计算出的强度到图像
// 确保强度非负
finalIntensity = Math.Max(0, finalIntensity);
image.SetIntensity(y, x, finalIntensity);
if (smokeIntensity < 0) // 只统计非烟幕覆盖的像素
{
pixelsSet++;
maxSetIntensity = Math.Max(maxSetIntensity, finalIntensity);
}
}
}
Console.WriteLine($"目标基于ThermalPattern强度分布: 像素设置: {pixelsSet}, 最大强度: {maxSetIntensity:F6} W/sr");
}
/// <summary>
/// 添加背景辐射和噪声
/// </summary>
private void AddBackgroundAndNoise(InfraredImage image, Weather weather)
{
double maxIntensityBefore = double.MinValue;
double maxIntensityAfter = double.MinValue;
int pixelsModified = 0;
// 根据天气条件调整背景噪声水平
double noiseLevel = 0.2; // 默认噪声水平
double bgIntensity = backgroundIntensity;
// 雾、雨、雪等天气增加背景噪声
if (weather.Type == WeatherType.Fog)
{
noiseLevel = 0.5;
bgIntensity *= (1.0 + (1.0 - weather.Visibility / 10.0)); // 雾增加背景辐射
}
else if (weather.Type == WeatherType.Rain || weather.Type == WeatherType.Snow)
{
noiseLevel = 0.3;
// 降水增加噪声水平
if (weather.Precipitation > 0)
{
noiseLevel += 0.1 * Math.Min(1.0, weather.Precipitation / 10.0);
}
}
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
double currentIntensity = image.GetIntensity(y, x);
maxIntensityBefore = Math.Max(maxIntensityBefore, currentIntensity);
// 只在非目标区域添加背景
if (currentIntensity < bgIntensity)
{
currentIntensity = bgIntensity;
pixelsModified++;
}
// 添加高斯噪声 (降低噪声水平)
double noise = (random.NextDouble() - 0.5) * bgIntensity * noiseLevel;
currentIntensity += noise;
// 确保非负
currentIntensity = Math.Max(0, currentIntensity);
image.SetIntensity(y, x, currentIntensity);
maxIntensityAfter = Math.Max(maxIntensityAfter, currentIntensity);
}
}
}
}
}