修改红外目标识别算法和烟幕弹作用算法

This commit is contained in:
Tian jianyong 2025-04-18 18:17:39 +08:00
parent 3d635f6180
commit 27060ee124
14 changed files with 605 additions and 194 deletions

View File

@ -0,0 +1,20 @@
{
"name": {
"zh": "周边烟幕弹",
"en": "Surround Smoke Grenade"
},
"type": "SmokeGrenade",
"SmokeGrenadeConfig": {
"smokeType": "Wall",
"IsObscuring": true,
"RadiationTemperature": 373.15,
"concentration": 0.2,
"duration": 60.0,
"wallWidth": 50.0,
"wallHeight": 10.0,
"cloudDiameter": 0.0,
"thickness": 5.0,
"formationDelay": 2.0,
"expansionRate": 5.0
}
}

View File

@ -6,13 +6,15 @@
"type": "SmokeGrenade",
"SmokeGrenadeConfig": {
"smokeType": "Wall",
"IsObscuring": false,
"RadiationTemperature": 293.15,
"concentration": 0.2,
"duration": 60.0,
"wallWidth": 50.0,
"wallHeight": 10.0,
"cloudDiameter": 0.0,
"thickness": 5.0,
"formationDelay": 1.5,
"formationDelay": 2.0,
"expansionRate": 5.0
}
}

View File

@ -6,6 +6,8 @@
"type": "SmokeGrenade",
"SmokeGrenadeConfig": {
"smokeType": "Cloud",
"IsObscuring": true,
"RadiationTemperature": 373.15,
"concentration": 0.2,
"duration": 60.0,
"wallWidth": 0.0,

View File

@ -24,11 +24,12 @@
"trackFieldOfView": 0.052,
"imageWidth": 640,
"imageHeight": 512,
"backgroundIntensity": 0.01,
"backgroundIntensity": 1e-4,
"searchRecognitionProbability": 0.6,
"trackRecognitionProbability": 0.8,
"targetLostTolerance": 0.2,
"lockConfirmationTime": 0.3,
"jammingResistanceThreshold": 1e-3
"jammingResistanceThreshold": 1e-3,
"waveLength": 3.0
}
}

View File

@ -40,6 +40,11 @@ namespace ThreatSource.Guidance
/// </summary>
public Vector3D LineOfSight { get; }
/// <summary>
/// 烟幕覆盖掩码 (true 表示被遮蔽烟幕覆盖)
/// </summary>
public bool[,] SmokeCoverageMask { get; }
/// <summary>
/// 初始化红外图像的新实例
/// </summary>
@ -47,13 +52,16 @@ namespace ThreatSource.Guidance
/// <param name="height">图像高度</param>
/// <param name="pixelSize">像素物理尺寸</param>
/// <param name="lineOfSight">视线方向</param>
public InfraredImage(int width, int height, double pixelSize, Vector3D lineOfSight)
/// <param name="smokeCoverageMask">烟幕覆盖掩码 (可选)</param>
public InfraredImage(int width, int height, double pixelSize, Vector3D lineOfSight, bool[,] smokeCoverageMask = null)
{
Width = width;
Height = height;
PixelSize = pixelSize;
LineOfSight = lineOfSight;
Intensity = new double[height, width];
SmokeCoverageMask = smokeCoverageMask ?? new bool[height, width];
}
/// <summary>

View File

@ -1,7 +1,10 @@
using System;
using System.Linq; // Add Linq for FirstOrDefault
using ThreatSource.Equipment;
using ThreatSource.Utils;
using AirTransmission; // 添加引用
using AirTransmission;
using ThreatSource.Jammer; // 添加引用
using ThreatSource.Simulation; // Add Simulation namespace
namespace ThreatSource.Guidance
{
@ -47,6 +50,11 @@ namespace ThreatSource.Guidance
/// </summary>
private readonly Random random = new();
/// <summary>
/// 烟幕温度转换为强度的比例因子 (简化 T^4 关系)
/// </summary>
private const double SMOKE_TEMPERATURE_FACTOR = 1e-11;
// 视线坐标系
private Vector3D forward; // 视线方向
private Vector3D right; // 图像平面右方向
@ -138,44 +146,165 @@ namespace ThreatSource.Guidance
/// </summary>
/// <param name="distance">距离(米)</param>
/// <param name="weather">天气条件</param>
/// <param name="smokeAttenuation">烟幕衰减</param>
/// <returns>大气透过率0-1之间</returns>
private double CalculateAtmosphericTransmittance(double distance, Weather? weather)
private double CalculateAtmosphericTransmittance(double distance, Weather weather, double smokeAttenuation)
{
double transmittance;
if (weather == null)
{
// 如果没有天气信息,使用基于距离的简化模型
transmittance = Math.Exp(-0.2 * distance / 1000.0); // 0.2/km的衰减率
return 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="missileVelocity">导弹速度</param>
/// <param name="weather">天气条件</param>
/// <param name="smokeAttenuation">衰减型烟幕透过率</param>
/// <param name="simulationManager">仿真管理器实例</param>
/// <returns>红外图像</returns>
public InfraredImage GenerateImage(BaseEquipment target, Vector3D missilePosition, Vector3D missileVelocity, Weather? weather)
public InfraredImage GenerateImage(BaseEquipment target, Vector3D missilePosition, double smokeAttenuation, ISimulationManager simulationManager)
{
// 更新视线坐标系
UpdateLineOfSightFrame(missilePosition, target.Position);
// 创建图像
var image = new InfraredImage(imageWidth, imageHeight, fieldOfView / imageWidth, forward);
// 创建并初始化烟幕强度图层 (-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);
@ -193,77 +322,106 @@ namespace ThreatSource.Guidance
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);
// 生成目标图像,传递距离参数
GenerateTargetIntensity(image, centerX, centerY, pixelLength, pixelWidth, target, distance, weather);
// 添加背景和噪声
AddBackgroundAndNoise(image, weather);
// 添加背景和噪声(这会影响到烟幕和目标区域)
AddBackgroundAndNoise(image, simulationManager.CurrentWeather);
return image;
}
/// <summary>
/// 生成目标的红外强度分布
/// 生成目标的红外强度分布,考虑遮蔽烟幕覆盖
/// </summary>
private void GenerateTargetIntensity(
/// <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,
Weather? weather)
double transmittance)
{
// 计算大气透过率
double transmittance = CalculateAtmosphericTransmittance(distance, weather);
// 计算目标辐射强度,考虑距离和大气透过率的影响
double targetIntensity = target.Properties.InfraredRadiationIntensity * transmittance / Math.Pow(distance, 1.8);
// 计算目标自身应有的辐射强度(未被遮挡时)
double targetBaseIntensity = target.Properties.InfraredRadiationIntensity * transmittance / Math.Pow(distance, 1.8);
// 计算分布参数
double sigmaX = pixelLength / 6.0;
// 计算高斯分布参数
double sigmaX = pixelLength / 6.0; // 标准差设为长度/宽度的1/6覆盖约99.7%范围
double sigmaY = pixelWidth / 6.0;
// 避免除以零
sigmaX = Math.Max(sigmaX, 1e-6);
sigmaY = Math.Max(sigmaY, 1e-6);
int halfLength = pixelLength / 2;
int halfWidth = pixelWidth / 2;
int pixelsSet = 0;
double maxSetIntensity = 0;
// 设置强度分布
for (int dy = -halfWidth; dy <= halfWidth; dy++)
{
int y = centerY + dy;
if (y < 0 || y >= image.Height) continue;
for (int dx = -halfLength; dx <= halfLength; dx++)
{
int x = centerX + dx;
if (x < 0 || x >= image.Width) continue;
// 计算归一化距离
double normalizedX = dx / sigmaX;
double normalizedY = dy / sigmaY;
double r2 = normalizedX * normalizedX + normalizedY * normalizedY;
// 高斯分布
double intensity = targetIntensity * Math.Exp(-r2 / 2.0);
image.SetIntensity(y, x, intensity);
double finalIntensity;
double smokeIntensity = smokeIntensityLayer[y, x];
if (smokeIntensity >= 0) // 检查此像素是否被遮蔽烟幕覆盖
{
// 如果被覆盖,使用烟幕的强度
finalIntensity = smokeIntensity;
}
else
{
// 如果未被覆盖,计算目标的强度
// 计算归一化距离 (相对于高斯分布中心)
double normalizedX = dx / sigmaX;
double normalizedY = dy / sigmaY;
double r2 = normalizedX * normalizedX + normalizedY * normalizedY;
// 计算高斯分布下的目标强度
finalIntensity = targetBaseIntensity * Math.Exp(-r2 / 2.0);
// Console.WriteLine($"像素 ({x},{y}) 未被烟幕覆盖,使用目标强度: {finalIntensity:F6}");
}
// 设置最终计算出的强度到图像
image.SetIntensity(y, x, finalIntensity);
pixelsSet++;
maxSetIntensity = Math.Max(maxSetIntensity, intensity);
maxSetIntensity = Math.Max(maxSetIntensity, finalIntensity);
}
}
Console.WriteLine($"目标强度分布: 像素设置: {pixelsSet}, 最大强度: {maxSetIntensity:F6} W/sr");
Console.WriteLine($"目标/烟幕强度分布: 像素设置: {pixelsSet}, 最大强度: {maxSetIntensity:F6} W/sr");
}
/// <summary>
/// 添加背景辐射和噪声
/// </summary>
private void AddBackgroundAndNoise(InfraredImage image, Weather? weather)
private void AddBackgroundAndNoise(InfraredImage image, Weather weather)
{
double maxIntensityBefore = double.MinValue;
double maxIntensityAfter = double.MinValue;
@ -273,25 +431,24 @@ namespace ThreatSource.Guidance
double noiseLevel = 0.2; // 默认噪声水平
double bgIntensity = backgroundIntensity;
if (weather != null)
// 雾、雨、雪等天气增加背景噪声
if (weather.Type == WeatherType.Fog)
{
// 雾、雨、雪等天气增加背景噪声
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.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);
}
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++)
@ -300,14 +457,16 @@ namespace ThreatSource.Guidance
maxIntensityBefore = Math.Max(maxIntensityBefore, currentIntensity);
// 只在非目标区域添加背景
if (currentIntensity < bgIntensity * 0.1)
if (currentIntensity < bgIntensity)
{
currentIntensity = bgIntensity;
pixelsModified++;
}
// 添加高斯噪声
double noise = (random.NextDouble() - 0.5) * bgIntensity * noiseLevel;
// 添加高斯噪声 (降低噪声水平)
double noiseReductionFactor = 0.0001; // 从 0.001 改为 0.0001
double reducedNoiseLevel = noiseLevel * noiseReductionFactor;
double noise = (random.NextDouble() - 0.5) * bgIntensity * reducedNoiseLevel;
currentIntensity += noise;
// 确保非负

View File

@ -91,6 +91,11 @@ namespace ThreatSource.Guidance
/// </summary>
private double lockConfirmationTimer = 0;
/// <summary>
/// 烟幕衰减
/// </summary>
private double SmokeAttenuation { get; set; } = 1.0;
/// <summary>
/// 初始化红外成像制导系统的新实例
@ -128,9 +133,10 @@ namespace ThreatSource.Guidance
imageWidth: guidanceConfig.ImageWidth,
imageHeight: guidanceConfig.ImageHeight,
fieldOfView: guidanceConfig.SearchFieldOfView,
backgroundIntensity: guidanceConfig.BackgroundIntensity
backgroundIntensity: guidanceConfig.BackgroundIntensity,
wavelength: guidanceConfig.WaveLength
);
InitializeJamming(guidanceConfig.JammingResistanceThreshold, [JammingType.Infrared]);
InitializeJamming(guidanceConfig.JammingResistanceThreshold, [JammingType.Infrared, JammingType.SmokeScreen]);
SwitchToSearchMode(); // 初始化为搜索模式
}
@ -170,11 +176,11 @@ namespace ThreatSource.Guidance
/// <param name="parameters">干扰参数</param>
protected override void HandleJammingApplied(JammingParameters parameters)
{
base.HandleJammingApplied(parameters);
if (parameters.Type == JammingType.Infrared)
{
Debug.WriteLine($"红外引导系统受到红外干扰,功率:{parameters.Power}瓦特");
// 确保基类的处理逻辑被调用设置HasGuidance = false
base.HandleJammingApplied(parameters);
// 在强干扰下切换到搜索模式
if (currentMode != WorkMode.Search)
@ -182,6 +188,14 @@ namespace ThreatSource.Guidance
SwitchToSearchMode();
}
}
else if (parameters.Type == JammingType.SmokeScreen)
{
if (SimulationManager.GetEntityById(parameters.JammerId) is SmokeGrenade smokeGrenade)
{
// 计算烟幕衰减
SmokeAttenuation = smokeGrenade.GetSmokeTransmittanceOnLine(Position, lastTargetPosition, config.WaveLength);
}
}
}
/// <summary>
@ -204,13 +218,12 @@ namespace ThreatSource.Guidance
/// </summary>
public override void Activate()
{
if (!IsActive)
{
IsActive = true;
// 在这里订阅事件,确保只订阅一次
SimulationManager.SubscribeToEvent<InfraredJammingEvent>(OnInfraredJamming);
}
base.Activate();
// 在这里订阅事件,确保只订阅一次
SimulationManager.SubscribeToEvent<InfraredJammingEvent>(OnInfraredJamming);
// 订阅烟幕事件
SimulationManager.SubscribeToEvent<SmokeScreenEvent>(OnSmokeScreen);
}
/// <summary>
@ -218,14 +231,46 @@ namespace ThreatSource.Guidance
/// </summary>
public override void Deactivate()
{
if (IsActive)
{
IsActive = false;
SimulationManager.UnsubscribeFromEvent<InfraredJammingEvent>(OnInfraredJamming);
}
base.Deactivate();
SimulationManager.UnsubscribeFromEvent<InfraredJammingEvent>(OnInfraredJamming);
SimulationManager.UnsubscribeFromEvent<SmokeScreenEvent>(OnSmokeScreen);
}
/// <summary>
/// 处理烟幕事件
/// </summary>
/// <param name="evt">烟幕事件</param>
private void OnSmokeScreen(SmokeScreenEvent evt)
{
if (evt != null && evt.SmokeGrenadeId != null)
{
// 获取烟幕弹的配置
if (SimulationManager.GetEntityById(evt.SmokeGrenadeId) is SmokeGrenade smokeGrenade)
{
var config = smokeGrenade.config;
// 创建干扰参数
var parameters = new JammingParameters
{
Type = JammingType.SmokeScreen,
JammerId = smokeGrenade.Id,
SourcePosition = smokeGrenade.Position,
Direction = smokeGrenade.Orientation.ToVector(),
AngleRange = config.SmokeType == SmokeScreenType.Wall ? Math.PI : Math.PI * 2, // 墙状烟幕为半球形覆盖,云状为全方位
SmokeConcentration = config.Concentration,
SmokeType = config.SmokeType,
SmokeThickness = config.Thickness,
Duration = config.Duration,
Mode = JammingMode.Obscuration
};
// 使用JammableComponent进行干扰判断
ApplyJamming(parameters);
}
}
}
/// <summary>
/// 更新引导系统的状态和计算结果
/// </summary>
@ -242,7 +287,7 @@ namespace ThreatSource.Guidance
public override void Update(double deltaTime, Vector3D missilePosition, Vector3D missileVelocity)
{
base.Update(deltaTime, missilePosition, missileVelocity);
if (!IsJammed)
if (!IsHardJammed)
{
if (TryDetectAndIdentifyTarget(missilePosition, missileVelocity, deltaTime, out Vector3D tankPosition))
{
@ -284,7 +329,6 @@ namespace ThreatSource.Guidance
}
else
{
HasGuidance = false;
GuidanceAcceleration = Vector3D.Zero;
}
}
@ -309,7 +353,8 @@ namespace ThreatSource.Guidance
imageWidth: config.ImageWidth,
imageHeight: config.ImageHeight,
fieldOfView: config.SearchFieldOfView,
backgroundIntensity: config.BackgroundIntensity
backgroundIntensity: config.BackgroundIntensity,
wavelength: config.WaveLength
);
Console.WriteLine($"切换到搜索模式, 视场角: {config.SearchFieldOfView * 180 / Math.PI} 度");
}
@ -333,7 +378,8 @@ namespace ThreatSource.Guidance
imageWidth: config.ImageWidth,
imageHeight: config.ImageHeight,
fieldOfView: config.TrackFieldOfView,
backgroundIntensity: config.BackgroundIntensity
backgroundIntensity: config.BackgroundIntensity,
wavelength: config.WaveLength
);
Console.WriteLine($"切换到跟踪模式, 视场角: {config.TrackFieldOfView * 180 / Math.PI} 度");
}
@ -393,7 +439,7 @@ namespace ThreatSource.Guidance
if (angle <= currentFov / 2)
{
// 生成红外图像
var image = imageGenerator.GenerateImage(target, missilePosition, missileVelocity, SimulationManager.CurrentWeather );
var image = imageGenerator.GenerateImage(target, missilePosition, SmokeAttenuation, SimulationManager);
switch (currentMode)
{

View File

@ -1,5 +1,7 @@
using ThreatSource.Equipment;
using ThreatSource.Simulation;
using System.Collections.Generic; // For Queue and List
using System.Linq; // For OrderByDescending
namespace ThreatSource.Guidance
{
@ -74,7 +76,7 @@ namespace ThreatSource.Guidance
/// <summary>
/// 背景辐射强度,用于阈值计算
/// </summary>
private const double BACKGROUND_INTENSITY = 0.01;
private const double BACKGROUND_INTENSITY = 0.0001;
/// <summary>
/// 初始化红外图像目标识别器
@ -149,51 +151,139 @@ namespace ThreatSource.Guidance
}
/// <summary>
/// 图像分割,提取目标区域
/// 图像分割,提取目标区域 - **重写:使用连通区域分析**
/// </summary>
private ImageSegment SegmentTarget(InfraredImage image)
{
// 使用简单的阈值分割方法
double threshold = CalculateThreshold(image);
int minX = image.Width, minY = image.Height, maxX = 0, maxY = 0;
bool found = false;
int pixelsAboveThreshold = 0;
int width = image.Width;
int height = image.Height;
bool[,] visited = new bool[height, width];
List<Blob> blobs = new List<Blob>();
Queue<(int x, int y)> queue = new Queue<(int x, int y)>();
// 寻找目标边界
for (int y = 0; y < image.Height; y++)
Console.WriteLine($"开始连通区域分析,阈值: {threshold:F6}");
// 1. 查找所有连通区域 (Blobs)
for (int y = 0; y < height; y++)
{
for (int x = 0; x < image.Width; x++)
for (int x = 0; x < width; x++)
{
double intensity = image.GetIntensity(y, x);
if (intensity > threshold)
if (!visited[y, x] && image.GetIntensity(y, x) > threshold)
{
minX = Math.Min(minX, x);
minY = Math.Min(minY, y);
maxX = Math.Max(maxX, x);
maxY = Math.Max(maxY, y);
found = true;
pixelsAboveThreshold++;
// 发现新 Blob 的起点
var currentBlob = new Blob
{
Label = blobs.Count + 1,
Pixels = new List<(int x, int y)>(),
MinX = x, MinY = y, MaxX = x, MaxY = y
};
queue.Enqueue((x, y));
visited[y, x] = true;
while (queue.Count > 0)
{
var (qx, qy) = queue.Dequeue();
currentBlob.Pixels.Add((qx, qy));
// 更新边界框
currentBlob.MinX = Math.Min(currentBlob.MinX, qx);
currentBlob.MinY = Math.Min(currentBlob.MinY, qy);
currentBlob.MaxX = Math.Max(currentBlob.MaxX, qx);
currentBlob.MaxY = Math.Max(currentBlob.MaxY, qy);
// 检查 4-邻域
int[] dx = { 0, 0, 1, -1 };
int[] dy = { 1, -1, 0, 0 };
for (int i = 0; i < 4; i++)
{
int nx = qx + dx[i];
int ny = qy + dy[i];
if (nx >= 0 && nx < width && ny >= 0 && ny < height &&
!visited[ny, nx] && image.GetIntensity(ny, nx) > threshold)
{
visited[ny, nx] = true;
queue.Enqueue((nx, ny));
}
}
}
blobs.Add(currentBlob);
Console.WriteLine($" 发现 Blob {currentBlob.Label}: 面积={currentBlob.Area}, 边界=({currentBlob.MinX},{currentBlob.MinY})-({currentBlob.MaxX},{currentBlob.MaxY})");
}
}
}
Console.WriteLine($"分割结果:");
Console.WriteLine($" 像素超过阈值: {pixelsAboveThreshold}");
if (found)
Console.WriteLine($"发现 {blobs.Count} 个 Blobs");
// 2. 过滤 Blobs
List<Blob> filteredBlobs = new List<Blob>();
int minArea = 10; // 最小面积阈值 (过滤噪声)
int maxArea = (int)(width * height * 0.25); // 最大面积阈值 (过滤背景或巨大干扰, 25%)
const double SMOKE_COVERAGE_THRESHOLD = 0.75; // 烟幕覆盖率阈值
foreach (var blob in blobs)
{
Console.WriteLine($" 目标边界: ({minX},{minY}) 到 ({maxX},{maxY})");
Console.WriteLine($" 目标尺寸: {maxX - minX + 1}x{maxY - minY + 1} 像素");
// 检查面积
if (blob.Area < minArea || blob.Area > maxArea)
{
Console.WriteLine($" Blob {blob.Label} 因面积 ({blob.Area}) 不符被过滤 (允许范围: {minArea}-{maxArea})");
continue;
}
// 检查烟幕覆盖率 (使用 Blob 边界框)
int smokeCoveredPixelsInBox = 0;
int totalPixelsInBox = 0; // 只统计边界框内高于阈值的像素
bool[,] smokeMask = image.SmokeCoverageMask;
for (int by = blob.MinY; by <= blob.MaxY; by++)
{
for (int bx = blob.MinX; bx <= blob.MaxX; bx++)
{
// 检查像素是否属于该 Blob (近似检查:在边界框内且高于阈值)
if (image.GetIntensity(by, bx) > threshold)
{
totalPixelsInBox++;
if (smokeMask[by, bx])
{
smokeCoveredPixelsInBox++;
}
}
}
}
if (totalPixelsInBox > 0)
{
double smokeCoverageRatio = (double)smokeCoveredPixelsInBox / totalPixelsInBox;
Console.WriteLine($" Blob {blob.Label} 烟幕覆盖率: {smokeCoverageRatio:P2} ({smokeCoveredPixelsInBox}/{totalPixelsInBox})");
if (smokeCoverageRatio >= SMOKE_COVERAGE_THRESHOLD)
{
Console.WriteLine($" Blob {blob.Label} 因烟幕覆盖率过高被过滤");
continue;
}
}
// 通过所有检查,添加到过滤后的列表
filteredBlobs.Add(blob);
Console.WriteLine($" Blob {blob.Label} 通过过滤");
}
if (!found)
// 3. 选择最佳 Blob
if (filteredBlobs.Count == 0)
{
return new ImageSegment();
Console.WriteLine("没有找到有效的 Blob");
return new ImageSegment(); // 返回无效分割
}
// 选择面积最大的 Blob 作为目标
Blob targetBlob = filteredBlobs.OrderByDescending(b => b.Area).First();
Console.WriteLine($"选择 Blob {targetBlob.Label} 作为目标 (面积: {targetBlob.Area})");
// 4. 返回基于选定 Blob 的 ImageSegment
return new ImageSegment(
isValid: true,
center: ((minX + maxX) / 2, (minY + maxY) / 2),
size: (maxX - minX + 1, maxY - minY + 1)
center: targetBlob.Center,
size: (targetBlob.Width, targetBlob.Height)
);
}
@ -212,31 +302,44 @@ namespace ThreatSource.Guidance
for (int x = 0; x < image.Width; x++)
{
double value = image.GetIntensity(y, x);
maxIntensity = Math.Max(maxIntensity, value);
minIntensity = Math.Min(minIntensity, value);
sum += value;
count++;
// Skip completely black pixels if necessary, or handle potential division by zero later
if (value > 0) // Avoid issues if minIntensity remains double.MaxValue
{
maxIntensity = Math.Max(maxIntensity, value);
minIntensity = Math.Min(minIntensity, value);
sum += value;
count++;
}
}
}
// Handle case where image is entirely black or has no valid pixels
if (count == 0 || maxIntensity <= minIntensity)
{
Console.WriteLine("图像全黑或无有效强度信息无法计算阈值返回0");
return 0; // Or handle appropriately
}
double mean = sum / count;
// 计算背景基准阈值
double backgroundThreshold = BACKGROUND_INTENSITY * 1.2;
// 计算目标基准阈值
double targetThreshold = maxIntensity * 0.2;
// 取两个基准中的较大值作为最终阈值
double threshold = Math.Max(backgroundThreshold, targetThreshold);
// --- 修改为基于动态范围的阈值计算 ---
double thresholdFactor = 0.001; // 基于动态范围的因子 (初始值设为0.15, 从 0.05 改为 0.01)
double dynamicRange = maxIntensity - minIntensity;
double threshold = minIntensity + dynamicRange * thresholdFactor;
// 添加一个小的偏移量,以确保阈值严格大于最小强度,避免将所有背景像素包含进来
// double epsilon = 1e-9;
// threshold = Math.Max(threshold, minIntensity + epsilon); // 确保阈值至少比最小值大一点点
Console.WriteLine($"图像统计:");
Console.WriteLine($" 最大强度: {maxIntensity:F6}");
Console.WriteLine($" 最小强度: {minIntensity:F6}");
Console.WriteLine($" 平均强度: {mean:F6}");
Console.WriteLine($" 背景阈值: {backgroundThreshold:F6}");
Console.WriteLine($" 目标阈值: {targetThreshold:F6}");
Console.WriteLine($" 动态范围: {dynamicRange:F6}");
Console.WriteLine($" 动态阈值因子: {thresholdFactor:P1}");
Console.WriteLine($" 最终阈值: {threshold:F6}");
// --- 结束修改 ---
return threshold;
}
@ -309,28 +412,32 @@ namespace ThreatSource.Guidance
}
/// <summary>
/// 计算温度梯度特征
/// 计算温度梯度特征 - **修正:强制基于图像计算**
/// </summary>
private double CalculateTemperatureGradient(InfraredImage image, ImageSegment segment, IEquipment target)
{
// 直接从目标获取温度分布数据
double[,] thermalPattern = target.GetCurrentThermalPattern();
if (thermalPattern == null)
{
Console.WriteLine("没有温度分布数据, 使用图像计算梯度");
return CalculateImageBasedGradient(image, segment);
}
// -- 移除直接从目标获取理想温度分布数据的逻辑 --
// double[,] thermalPattern = target.GetCurrentThermalPattern(); // 错误:这会绕过烟幕遮蔽效果
// if (thermalPattern == null)
// {
// Console.WriteLine("没有温度分布数据, 使用图像计算梯度");
// return CalculateImageBasedGradient(image, segment);
// }
//
// var pattern = new ThermalPattern(thermalPattern, thermalPattern);
//
// // 判断目标是否在运动 (这个判断可能仍然有用,但基于图像的梯度计算目前不使用它)
// bool isMoving = IsTargetMoving(image, segment);
// Console.WriteLine($"目标运动状态: {(isMoving ? "移动" : "静止")}");
//
// // 使用理想温度分布模式计算梯度特征 (错误)
// double gradientFeature = pattern.CalculateGradientFeature(isMoving);
//
// return gradientFeature;
var pattern = new ThermalPattern(thermalPattern, thermalPattern);
// 判断目标是否在运动
bool isMoving = IsTargetMoving(image, segment);
Console.WriteLine($"目标运动状态: {(isMoving ? "" : "")}");
// 使用温度分布模式计算梯度特征
double gradientFeature = pattern.CalculateGradientFeature(isMoving);
return gradientFeature;
// **修正:始终基于传入的红外图像计算梯度特征**
Console.WriteLine("计算基于图像的温度梯度特征");
return CalculateImageBasedGradient(image, segment);
}
/// <summary>
@ -428,14 +535,14 @@ namespace ThreatSource.Guidance
// 计算中部区域的梯度特征
double middleAreaGradient = avgGridGradients[1, 1];
// 计算梯度分布特征
double gradientDistribution = engineAreaGradient / (frontAreaGradient + 0.1);
double gradientContrast = engineAreaGradient / (middleAreaGradient + 0.1);
// 计算梯度分布特征 (调整除零保护)
double gradientDistribution = engineAreaGradient / (frontAreaGradient + 1e-6);
double gradientContrast = engineAreaGradient / (middleAreaGradient + 1e-6);
// 最终得分:后部梯度强度(0.3) + 梯度分布特征(0.4) + 梯度对比度(0.3)
return engineAreaGradient * 0.3 +
Math.Min(gradientDistribution, 3.0) / 3.0 * 0.4 +
Math.Min(gradientContrast, 3.0) / 3.0 * 0.3;
// 最终得分:调整权重 (engine*0.5 + dist*0.3 + contrast*0.2)
return engineAreaGradient * 0.5 +
Math.Min(gradientDistribution, 3.0) / 3.0 * 0.3 +
Math.Min(gradientContrast, 3.0) / 3.0 * 0.2;
}
/// <summary>
@ -525,20 +632,21 @@ namespace ThreatSource.Guidance
histogram[i] /= totalPixels;
}
// 计算集中度得分:高强度bin的占比越高得分越高
// 计算集中度得分:改为计算最高 30% bin 的占比
double concentrationScore = 0;
for (int i = 0; i < BINS; i++)
int topBinsStart = (int)(BINS * 0.7); // Start from the 70th percentile bin (e.g., index 7 for BINS=10)
for (int i = topBinsStart; i < BINS; i++)
{
// 使用指数权重使高强度bin的影响更大
double weight = Math.Pow(2, i) / Math.Pow(2, BINS-1); // 指数增长的权重
concentrationScore += histogram[i] * weight;
concentrationScore += histogram[i];
}
// 计算中心区域相对强度比
double centerIntensityRatio = avgCenterIntensity / avgTotalIntensity;
double centerIntensityRatio = avgTotalIntensity > 0 ? avgCenterIntensity / avgTotalIntensity : 0; // Avoid division by zero
// 最终得分:集中度得分(0.6)和中心强度比(0.4)的加权和
return concentrationScore * 0.6 + centerIntensityRatio * 0.4;
// 同时确保最终结果在 [0, 1] 范围内
double finalScore = concentrationScore * 0.6 + centerIntensityRatio * 0.4;
return Math.Max(0, Math.Min(1, finalScore)); // Clamp result to [0, 1]
}
/// <summary>
@ -578,13 +686,15 @@ namespace ThreatSource.Guidance
private double[] CalculateFeatureWeights(TargetFeature features)
{
// 使用固定权重,基于特征的重要性
return new[]
{
0.3, // 长宽比权重
0.2, // 相对尺寸权重
0.25, // 强度模式权重
0.25 // 温度梯度权重
};
// **重新平衡权重:降低梯度权重,提升形状和尺寸权重**
return
[
0.30, // 长宽比权重 (原 0.15, 最初 0.3)
0.25, // 相对尺寸权重 (原 0.15, 最初 0.2)
0.15, // 强度模式权重 (原 0.20, 最初 0.25)
0.30 // 温度梯度权重 (原 0.50, 最初 0.25)
// 总和为 1.00
];
}
/// <summary>
@ -673,5 +783,21 @@ namespace ThreatSource.Guidance
Size = size;
}
}
// --- 新增Blob 结构体,用于存储连通区域信息 ---
private struct Blob
{
public int Label { get; set; }
public List<(int x, int y)> Pixels { get; set; }
public int MinX { get; set; }
public int MinY { get; set; }
public int MaxX { get; set; }
public int MaxY { get; set; }
public int Area => Pixels?.Count ?? 0;
public int Width => MaxX - MinX + 1;
public int Height => MaxY - MinY + 1;
public (int X, int Y) Center => ((MinX + MaxX) / 2, (MinY + MaxY) / 2);
}
// --- 结束新增 ---
}
}

View File

@ -123,7 +123,7 @@ namespace ThreatSource.Simulation
/// <summary>
/// 获取当前天气系统
/// </summary>
Weather? CurrentWeather { get; }
Weather CurrentWeather { get; }
/// <summary>
/// 设置当前天气

View File

@ -592,6 +592,11 @@ namespace ThreatSource.Simulation
/// </remarks>
public double JammingResistanceThreshold { get; set; } = 1e-3;
/// <summary>
/// 波长,单位:微米
/// </summary>
public double WaveLength { get; set; } = 8.0;
/// <summary>
/// 初始化红外成像导引配置的新实例
/// </summary>
@ -1621,6 +1626,14 @@ namespace ThreatSource.Simulation
/// </remarks>
public SmokeScreenType SmokeType { get; set; } = SmokeScreenType.Cloud;
/// <summary>
/// 是否是遮蔽型烟幕
/// </summary>
/// <remarks>
/// 遮蔽型烟幕会使用固定高强度值 (W/sr)
/// </remarks>
public bool IsObscuring { get; set; } = false;
/// <summary>
/// 烟幕浓度单位g/m³
/// </summary>
@ -1630,6 +1643,15 @@ namespace ThreatSource.Simulation
/// </remarks>
public double Concentration { get; set; } = 0.2;
/// <summary>
/// 辐射温度单位K
/// </summary>
/// <remarks>
/// 烟幕的辐射温度,影响其热辐射特性
/// 标准辐射温度范围200-300 K
/// </remarks>
public double RadiationTemperature { get; set; } = 273.15;
/// <summary>
/// 烟幕持续时间,单位:秒
/// </summary>

View File

@ -125,7 +125,10 @@ namespace ThreatSource.Simulation
/// </remarks>
public virtual void Activate()
{
IsActive = true;
if (!IsActive)
{
IsActive = true;
}
}
/// <summary>
@ -139,7 +142,10 @@ namespace ThreatSource.Simulation
/// </remarks>
public virtual void Deactivate()
{
IsActive = false;
if (IsActive)
{
IsActive = false;
}
}
}
}

View File

@ -72,13 +72,14 @@ namespace ThreatSource.Simulation
/// <summary>
/// 当前天气系统
/// 默认为无风晴朗天气
/// </summary>
private Weather? _currentWeather;
private Weather _currentWeather = new(WeatherType.Clear, 25, 50, 23, 0, 415, 1013, 0, 0);
/// <summary>
/// 获取当前天气系统
/// </summary>
public Weather? CurrentWeather => _currentWeather;
public Weather CurrentWeather => _currentWeather;
/// <summary>
/// 启动仿真系统

View File

@ -136,7 +136,7 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(100, 5, 0),
Position = new Vector3D(50, 5, 0),
Orientation = Orientation.FromVector(Vector3D.UnitX),
InitialSpeed = 0.0
};
@ -148,6 +148,24 @@ namespace ThreatSource.Tools.MissileSimulation
jammers[smokeGrenadeId] = smokeGrenade as BaseJammer;
Console.WriteLine($"注册烟幕弹 {smokeGrenadeId}");
}
smokeGrenadeId = "SG_2";
smokeGrenade = _threatSourceFactory.CreateJammer(smokeGrenadeId, "infrared", motionParameters, "Tank_1");
if (smokeGrenade != null)
{
simulationManager.RegisterEntity(smokeGrenadeId, smokeGrenade);
jammers[smokeGrenadeId] = smokeGrenade as BaseJammer;
Console.WriteLine($"注册烟幕弹 {smokeGrenadeId}");
}
smokeGrenadeId = "SG_3";
smokeGrenade = _threatSourceFactory.CreateJammer(smokeGrenadeId, "top", motionParameters, "Tank_1");
if (smokeGrenade != null)
{
simulationManager.RegisterEntity(smokeGrenadeId, smokeGrenade);
jammers[smokeGrenadeId] = smokeGrenade as BaseJammer;
Console.WriteLine($"注册烟幕弹 {smokeGrenadeId}");
}
}
/// <summary>
@ -157,7 +175,7 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 1, 0),
Position = new Vector3D(2000, 1, 20),
Orientation = new Orientation(Math.PI, 0.01, 0),
InitialSpeed = 700
};
@ -175,8 +193,8 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 10, 100),
Orientation = new Orientation(Math.PI, 0.0, 0.0),
Position = new Vector3D(2000, 1, 20),
Orientation = new Orientation(Math.PI, 0.01, 0.0),
InitialSpeed = 300
};
string missileId = "LBRM_1";
@ -193,7 +211,7 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(3000, 0, 100),
Position = new Vector3D(3000, 0, 20),
Orientation = new Orientation(Math.PI, 0, 0),
InitialSpeed = 800
};
@ -213,8 +231,8 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 10, 100),
Orientation = new Orientation(Math.PI, 0.0, 0),
Position = new Vector3D(2000, 1, 20),
Orientation = new Orientation(Math.PI, 0.01, 0),
InitialSpeed = 300
};
@ -232,8 +250,8 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 20, 50),
Orientation = new Orientation(Math.PI, 0.0, 0),
Position = new Vector3D(2000, 1, 0),
Orientation = new Orientation(Math.PI, 0.02, 0),
InitialSpeed = 300
};
string missileId = "ITGM_1";
@ -250,8 +268,8 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(3000, 20, 100),
Orientation = new Orientation(Math.PI, 0.0, 0),
Position = new Vector3D(3000, 1, 20),
Orientation = new Orientation(Math.PI, 0.01, 0),
InitialSpeed = 300
};
string missileId = "MMWG_1";
@ -270,8 +288,8 @@ namespace ThreatSource.Tools.MissileSimulation
string laserDesignatorId = "LD_1";
var laserDesignatorLaunchParams = new MotionParameters
{
Position = new Vector3D(2100, 10, 100),
Orientation = new Orientation(Math.PI, 0, 0),
Position = new Vector3D(2100, 1, 100),
Orientation = new Orientation(Math.PI, 0.01, 0),
InitialSpeed = 0
};
var laserDesignator = _threatSourceFactory.CreateIndicator(laserDesignatorId, "ld_001", "Tank_1", "LSGM_1", laserDesignatorLaunchParams);
@ -283,7 +301,7 @@ namespace ThreatSource.Tools.MissileSimulation
string laserBeamRiderId = "LBR_1";
var laserBeamRiderLaunchParams = new MotionParameters
{
Position = new Vector3D(2100, 10, 100),
Position = new Vector3D(2100, 1, 20),
Orientation = new Orientation(Math.PI, 0, 0),
InitialSpeed = 0
};
@ -296,7 +314,7 @@ namespace ThreatSource.Tools.MissileSimulation
string infraredTrackerId = "IT_1";
var infraredTrackerLaunchParams = new MotionParameters
{
Position = new Vector3D(2100, 10, 100),
Position = new Vector3D(2100, 1, 100),
Orientation = new Orientation(Math.PI, 0, 0),
InitialSpeed = 0
};
@ -372,7 +390,7 @@ namespace ThreatSource.Tools.MissileSimulation
// }
if (jammers.ContainsKey("SG_1"))
{
jammers["SG_1"].Activate();
//jammers["SG_1"].Activate();
Console.WriteLine($"激活烟幕弹 {jammers["SG_1"].Id}");
}
break;
@ -413,8 +431,8 @@ namespace ThreatSource.Tools.MissileSimulation
switch (type)
{
case JammingType.SmokeScreen:
jammingParams.JammerId = "SG_1";
jammers["SG_1"].Activate();
jammingParams.JammerId = "SG_2";
jammers["SG_2"].Activate();
break;
case JammingType.Decoy:
jammingParams.JammerId = "LDY_1";

View File

@ -174,7 +174,7 @@ namespace ThreatSource.Tools.MissileSimulation
(JammingType.Infrared, "红外干扰"),
(JammingType.MillimeterWave, "毫米波干扰"),
(JammingType.Laser, "激光干扰"),
(JammingType.SmokeScreen, "烟幕干扰"),
(JammingType.SmokeScreen, "烟幕"),
(JammingType.Decoy, "假目标干扰")
};