Files
JoyD/Windows/CS/Framework4.0/Camera/Camera/Camera.cs
2026-03-27 15:51:17 +08:00

901 lines
31 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.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Timers;
using System.Net;
using System.IO;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading;
using Emgu.CV;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
namespace Camera
{
public class Camera
{
// 摄像头配置信息
private const string IP = "192.168.100.60";
private const string SNAPSHOT_URI = "/snapshot.jpg";
private const string USER = "admin";
private const string PASSWD = "Yexian.net.168";
// 调试模式
private const Boolean IsDebug = false;
// 配置文件目录
private string _configPath;
// 配置数据
private Rectangle _detectionZone;
private ConcurrentDictionary<int, Rectangle> _ledZones = new ConcurrentDictionary<int, Rectangle>();
private ConcurrentDictionary<int, Color> _ledZoneColors = new ConcurrentDictionary<int, Color>();
private ConcurrentDictionary<int, string> _ledZoneDetectionResults = new ConcurrentDictionary<int, string>();
private ConcurrentDictionary<int, Tuple<double, double, double>> _ledZoneHsvValues = new ConcurrentDictionary<int, Tuple<double, double, double>>();
private ConcurrentDictionary<int, bool> _ledZoneVisibility = new ConcurrentDictionary<int, bool>();
private LedDetector _ledDetector = new LedDetector();
public void SetLedDetectorParams(int brightLimit, int satLimit)
{
_ledDetector.BrightLimit = brightLimit;
_ledDetector.SatLimit = satLimit;
}
public int GetBrightLimit()
{
return _ledDetector.BrightLimit;
}
public int GetSatLimit()
{
return _ledDetector.SatLimit;
}
public void SetColorThresholds(int redMin, int redMax, int greenMin, int greenMax, int blueMin, int blueMax)
{
_ledDetector.RedMin = redMin;
_ledDetector.RedMax = redMax;
_ledDetector.GreenMin = greenMin;
_ledDetector.GreenMax = greenMax;
_ledDetector.BlueMin = blueMin;
_ledDetector.BlueMax = blueMax;
}
public int GetRedMin() { return _ledDetector.RedMin; }
public int GetRedMax() { return _ledDetector.RedMax; }
public int GetGreenMin() { return _ledDetector.GreenMin; }
public int GetGreenMax() { return _ledDetector.GreenMax; }
public int GetBlueMin() { return _ledDetector.BlueMin; }
public int GetBlueMax() { return _ledDetector.BlueMax; }
private Color _detectionZoneColor = Color.Black;
// 检测状态
private bool _isDetecting = false;
private readonly object _detectionLock = new object();
// 定时器
private System.Timers.Timer _timer;
// 当前图像
private Image _currentImage;
private readonly object _imageLock = new object();
private string _authorization;
// 事件定义
public event EventHandler<ImageEventArgs> ImageCaptured;
/// <summary>
/// 根据区域编号获取检测结果
/// </summary>
/// <param name="AreaNum">区域编号</param>
/// <returns>检测结果字符串</returns>
public string GetColor(int AreaNum)
{
return GetLedZoneDetectionResult(AreaNum);
}
/// <summary>
/// 获取当前图像(返回副本,避免线程冲突)
/// </summary>
public Image GetCurrentImage()
{
lock (_imageLock)
{
if (_currentImage != null)
return _currentImage.Clone() as Image;
return null;
}
}
private static bool _isEmguCvAvailable = false;
private static string _emguCvError = "";
private static bool _isDllExtracted = false;
public static bool CheckEmguCv()
{
try
{
if (!_isDllExtracted)
{
ExtractDlls();
_isDllExtracted = true;
}
using (var testImage = new Image<Bgr, byte>(1, 1))
{
testImage.Dispose();
}
_isEmguCvAvailable = true;
return true;
}
catch (Exception ex)
{
_isEmguCvAvailable = false;
_emguCvError = ex.Message;
return false;
}
}
private static void ExtractDlls()
{
string appDir = AppDomain.CurrentDomain.BaseDirectory;
bool is64Bit = Environment.Is64BitProcess;
string[] dllFiles = is64Bit
? new string[] { "x64.cvextern.dll", "x64.opencv_ffmpeg310_64.dll" }
: new string[] { "x86.cvextern.dll", "x86.opencv_ffmpeg310.dll" };
string[] destFiles = is64Bit
? new string[] { "cvextern.dll", "opencv_ffmpeg310_64.dll" }
: new string[] { "cvextern.dll", "opencv_ffmpeg310.dll" };
for (int i = 0; i < dllFiles.Length; i++)
{
string destPath = Path.Combine(appDir, destFiles[i]);
if (!File.Exists(destPath))
{
try
{
using (var stream = typeof(Camera).Assembly.GetManifestResourceStream("Camera." + dllFiles[i]))
{
if (stream != null)
{
using (var fs = new FileStream(destPath, FileMode.Create))
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
fs.Write(buffer, 0, bytesRead);
}
}
}
}
}
catch { }
}
}
}
public static bool IsEmguCvAvailable()
{
return _isEmguCvAvailable;
}
public static string GetEmguCvError()
{
return _emguCvError;
}
/// <summary>
/// 开始获取图像
/// </summary>
public void Start()
{
if (!CheckEmguCv())
{
throw new InvalidOperationException("Emgu CV 初始化失败:" + _emguCvError);
}
Stop();
_timer = new System.Timers.Timer(200);
_timer.AutoReset = false;
_timer.Elapsed += Timer_Elapsed;
_timer.Start();
}
/// <summary>
/// 停止获取图像
/// </summary>
public void Stop()
{
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
_timer = null;
}
_authorization = null;
}
/// <summary>
/// 打开设置窗口
/// </summary>
public void SetArea()
{
Setting settingForm = new Setting();
settingForm.SetCamera(this);
if (_currentImage != null)
{
settingForm.SetImage(_currentImage);
}
if (!string.IsNullOrEmpty(_configPath))
{
settingForm.SetConfigPath(_configPath);
}
settingForm.ShowDialog();
}
/// <summary>
/// 设置配置文件目录
/// </summary>
public void SetConfigPath(string path)
{
_configPath = path;
LoadConfig();
}
/// <summary>
/// 获取检测区域
/// </summary>
public Rectangle GetDetectionZone()
{
return _detectionZone;
}
/// <summary>
/// 获取Led区域列表
/// </summary>
public ConcurrentDictionary<int, Rectangle> GetLedZones()
{
return _ledZones;
}
/// <summary>
/// 获取指定索引的Led区域
/// </summary>
public Rectangle GetLedZone(int index)
{
if (_ledZones.ContainsKey(index))
return _ledZones[index];
return new Rectangle(0, 0, 0, 0);
}
/// <summary>
/// 获取默认Led区域索引为1
/// </summary>
public Rectangle GetLedZone()
{
if (_ledZones.ContainsKey(1))
return _ledZones[1];
return new Rectangle(0, 0, 0, 0);
}
/// <summary>
/// 设置默认Led区域索引为1
/// </summary>
public void SetLedZone(Rectangle zone)
{
_ledZones[1] = zone;
if (!_ledZoneColors.ContainsKey(1))
{
_ledZoneColors[1] = Color.Lime;
}
}
/// <summary>
/// 获取默认Led区域颜色索引为1
/// </summary>
public Color GetLedZoneColor()
{
if (_ledZoneColors.ContainsKey(1))
return _ledZoneColors[1];
return Color.Lime;
}
/// <summary>
/// 设置默认Led区域颜色索引为1
/// </summary>
public void SetLedZoneColor(Color color)
{
_ledZoneColors[1] = color;
}
/// <summary>
/// 设置检测区域
/// </summary>
public void SetDetectionZone(Rectangle zone)
{
_detectionZone = zone;
}
/// <summary>
/// 添加Led区域
/// </summary>
public void AddLedZone(int index, Rectangle zone, Color color)
{
// 裁截LED区域确保在图像范围内
zone = ClipZone(zone);
_ledZones[index] = zone;
_ledZoneColors[index] = color;
}
/// <summary>
/// 设置指定索引的Led区域
/// </summary>
public void SetLedZone(int index, Rectangle zone)
{
if (_ledZones.ContainsKey(index))
{
// 裁截LED区域确保在图像范围内
zone = ClipZone(zone);
_ledZones[index] = zone;
}
}
/// <summary>
/// 裁截区域,确保在图像范围内
/// </summary>
private Rectangle ClipZone(Rectangle zone)
{
if (_currentImage == null) return zone;
int imageWidth = _currentImage.Width;
int imageHeight = _currentImage.Height;
// 确保坐标和尺寸在图像范围内
int x = Math.Max(0, zone.X);
int y = Math.Max(0, zone.Y);
int width = Math.Min(zone.Width, imageWidth - x);
int height = Math.Min(zone.Height, imageHeight - y);
// 确保宽度和高度为正数
width = Math.Max(1, width);
height = Math.Max(1, height);
return new Rectangle(x, y, width, height);
}
/// <summary>
/// 移除Led区域
/// </summary>
public void RemoveLedZone(int index)
{
Rectangle r;
Color c;
string s;
_ledZones.TryRemove(index, out r);
_ledZoneColors.TryRemove(index, out c);
_ledZoneDetectionResults.TryRemove(index, out s);
}
/// <summary>
/// 设置指定索引的Led区域颜色
/// </summary>
public void SetLedZoneColor(int index, Color color)
{
if (_ledZoneColors.ContainsKey(index))
{
_ledZoneColors[index] = color;
}
}
/// <summary>
/// 获取Led区域数量
/// </summary>
public int GetLedZoneCount()
{
return _ledZones.Count;
}
public Color GetDetectionZoneColor()
{
return _detectionZoneColor;
}
public ConcurrentDictionary<int, Color> GetLedZoneColors()
{
return _ledZoneColors;
}
public void SetDetectionZoneColor(Color color)
{
_detectionZoneColor = color;
}
/// <summary>
/// 获取指定索引的Led区域颜色
/// </summary>
public Color GetLedZoneColor(int index)
{
if (_ledZoneColors.ContainsKey(index))
return _ledZoneColors[index];
return Color.Lime;
}
public bool GetLedZoneVisibility(int index)
{
if (_ledZoneVisibility.ContainsKey(index))
return _ledZoneVisibility[index];
return true;
}
public void SetLedZoneVisibility(int index, bool visible)
{
_ledZoneVisibility[index] = visible;
}
/// <summary>
/// 获取LED区域检测结果
/// </summary>
public ConcurrentDictionary<int, string> GetLedZoneDetectionResults()
{
return _ledZoneDetectionResults;
}
/// <summary>
/// 获取指定索引的LED区域检测结果
/// </summary>
public string GetLedZoneDetectionResult(int index)
{
if (_ledZoneDetectionResults.ContainsKey(index))
return _ledZoneDetectionResults[index];
return "";
}
/// <summary>
/// 设置LED区域检测结果
/// </summary>
public void SetLedZoneDetectionResult(int index, string result)
{
_ledZoneDetectionResults[index] = result;
}
/// <summary>
/// 获取所有LED区域的HSV值
/// </summary>
public ConcurrentDictionary<int, Tuple<double, double, double>> GetLedZoneHsvValues()
{
return _ledZoneHsvValues;
}
/// <summary>
/// 获取指定索引的LED区域的HSV值
/// </summary>
public Tuple<double, double, double> GetLedZoneHsvValue(int index)
{
if (_ledZoneHsvValues.ContainsKey(index))
return _ledZoneHsvValues[index];
return new Tuple<double, double, double>(0, 0, 0);
}
/// <summary>
/// 保存配置
/// </summary>
public void SaveConfig()
{
if (string.IsNullOrEmpty(_configPath)) return;
string configFile = Path.Combine(_configPath, "Camera.csv");
try
{
using (StreamWriter writer = new StreamWriter(configFile, false, System.Text.Encoding.UTF8))
{
writer.WriteLine("X坐标,Y坐标,宽度,高度,颜色");
if (_detectionZone.Width > 0 && _detectionZone.Height > 0)
{
string colorStr = ColorTranslator.ToHtml(_detectionZoneColor);
writer.WriteLine(string.Format("{0},{1},{2},{3},{4}", _detectionZone.X, _detectionZone.Y, _detectionZone.Width, _detectionZone.Height, colorStr));
}
writer.WriteLine();
writer.WriteLine("索引,X坐标,Y坐标,宽度,高度,颜色");
foreach (var kvp in _ledZones)
{
int index = kvp.Key;
Rectangle ledZone = kvp.Value;
Color ledColor = _ledZoneColors[index];
string colorStr = ColorTranslator.ToHtml(ledColor);
writer.WriteLine(string.Format("{0},{1},{2},{3},{4},{5}", index, ledZone.X, ledZone.Y, ledZone.Width, ledZone.Height, colorStr));
}
writer.WriteLine();
writer.WriteLine(string.Format("亮灭阈值,{0},{1}", _ledDetector.BrightLimit, _ledDetector.SatLimit));
writer.WriteLine(string.Format("红阈值,{0},{1}", _ledDetector.RedMin, _ledDetector.RedMax));
writer.WriteLine(string.Format("绿阈值,{0},{1}", _ledDetector.GreenMin, _ledDetector.GreenMax));
writer.WriteLine(string.Format("蓝阈值,{0},{1}", _ledDetector.BlueMin, _ledDetector.BlueMax));
}
}
catch { }
}
/// <summary>
/// 加载配置
/// </summary>
private void LoadConfig()
{
if (string.IsNullOrEmpty(_configPath)) return;
_detectionZone = new Rectangle(0, 0, 2880, 1620);
_ledZones.Clear();
_ledZoneColors.Clear();
_detectionZoneColor = Color.Black;
string configFile = Path.Combine(_configPath, "Camera.csv");
try
{
if (File.Exists(configFile))
{
using (StreamReader reader = new StreamReader(configFile, System.Text.Encoding.UTF8))
{
string line;
bool isDetectionZone = true;
while ((line = reader.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
{
isDetectionZone = false;
continue;
}
string[] parts = line.Split(',');
if (parts.Length >= 2 && parts[0] == "亮灭阈值")
{
if (int.TryParse(parts[1], out int brightLimit))
_ledDetector.BrightLimit = brightLimit;
if (parts.Length >= 3 && int.TryParse(parts[2], out int satLimit))
_ledDetector.SatLimit = satLimit;
continue;
}
if (parts.Length >= 3 && parts[0] == "红阈值")
{
if (int.TryParse(parts[1], out int redMin))
_ledDetector.RedMin = redMin;
if (int.TryParse(parts[2], out int redMax))
_ledDetector.RedMax = redMax;
continue;
}
if (parts.Length >= 3 && parts[0] == "绿阈值")
{
if (int.TryParse(parts[1], out int greenMin))
_ledDetector.GreenMin = greenMin;
if (int.TryParse(parts[2], out int greenMax))
_ledDetector.GreenMax = greenMax;
continue;
}
if (parts.Length >= 3 && parts[0] == "蓝阈值")
{
if (int.TryParse(parts[1], out int blueMin))
_ledDetector.BlueMin = blueMin;
if (int.TryParse(parts[2], out int blueMax))
_ledDetector.BlueMax = blueMax;
continue;
}
if (parts.Length >= 4)
{
if (isDetectionZone)
{
if (!int.TryParse(parts[0], out int x)) continue;
if (!int.TryParse(parts[1], out int y)) continue;
if (!int.TryParse(parts[2], out int width)) continue;
if (!int.TryParse(parts[3], out int height)) continue;
_detectionZone = new Rectangle(x, y, width, height);
if (parts.Length >= 5 && !string.IsNullOrEmpty(parts[4]))
{
try { _detectionZoneColor = ColorTranslator.FromHtml(parts[4]); } catch { }
}
}
else if (parts[0] == "索引")
{
continue;
}
else
{
if (!int.TryParse(parts[0], out int index)) continue;
if (parts.Length >= 6)
{
if (!int.TryParse(parts[1], out int x)) continue;
if (!int.TryParse(parts[2], out int y)) continue;
if (!int.TryParse(parts[3], out int width)) continue;
if (!int.TryParse(parts[4], out int height)) continue;
Color zoneColor = Color.Lime;
if (parts.Length >= 6 && !string.IsNullOrEmpty(parts[5]))
{
try { zoneColor = ColorTranslator.FromHtml(parts[5]); } catch { }
}
_ledZones[index] = new Rectangle(x, y, width, height);
_ledZoneColors[index] = zoneColor;
}
}
}
}
}
}
else
{
SaveConfig();
}
}
catch { }
}
/// <summary>
/// 定时器触发事件
/// </summary>
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " Timer_Elapsed 开始");
try
{
lock (_detectionLock)
{
if (_isDetecting)
{
System.Diagnostics.Debug.WriteLine("Camera: 正在检测,跳过本次");
if (_timer != null)
{
_timer.Stop();
_timer.Start();
}
return;
}
_isDetecting = true;
}
Image image = GetCameraImage();
if (image != null)
{
System.Diagnostics.Debug.WriteLine("Camera: 捕获图像 Size=" + image.Width + "x" + image.Height);
DetectLedZones(image);
Image oldImage = null;
lock (_imageLock)
{
oldImage = _currentImage;
_currentImage = image;
}
if (oldImage != null)
{
oldImage.Dispose();
}
OnImageCaptured(new ImageEventArgs { Image = image });
System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " 事件已触发");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " 获取图像失败: " + ex.Message);
}
finally
{
lock (_detectionLock)
{
_isDetecting = false;
}
if (_timer != null)
{
System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff") + " 重新启动定时器");
_timer.Stop();
_timer.Start();
}
}
}
/// <summary>
/// 检测LED区域颜色
/// </summary>
private void DetectLedZones(Image image)
{
if (image == null || _ledZones.Count == 0) return;
if (_detectionZone.Width <= 0 || _detectionZone.Height <= 0) return;
try
{
using (Bitmap bmp = new Bitmap(image))
{
if (_detectionZone.X < 0 || _detectionZone.Y < 0 ||
_detectionZone.X + _detectionZone.Width > bmp.Width ||
_detectionZone.Y + _detectionZone.Height > bmp.Height)
return;
using (var imageBgr = new Image<Bgr, byte>(bmp))
{
foreach (var kvp in _ledZones)
{
int index = kvp.Key;
Rectangle zone = kvp.Value;
if (zone.Width <= 0 || zone.Height <= 0) continue;
Rectangle ledRect = new Rectangle(
_detectionZone.X + zone.X,
_detectionZone.Y + zone.Y,
zone.Width,
zone.Height);
if (ledRect.X < 0 || ledRect.Y < 0 ||
ledRect.X + ledRect.Width > bmp.Width ||
ledRect.Y + ledRect.Height > bmp.Height)
continue;
var detectResult = _ledDetector.Detect(imageBgr, ledRect);
string result = "";
if (detectResult.Item1 == LedState.On)
{
if (detectResult.Item2 == LedColor.Red)
result = "Red";
else if (detectResult.Item2 == LedColor.Green)
result = "Green";
else if (detectResult.Item2 == LedColor.Blue)
result = "Blue";
}
else
{
result = "Off";
}
_ledZoneDetectionResults[index] = result;
_ledZoneHsvValues[index] = new Tuple<double, double, double>(detectResult.Item3, detectResult.Item4, detectResult.Item5);
}
}
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Camera: 检测LED区域失败: " + ex.Message);
}
}
/// <summary>
/// 获取摄像头图像
/// </summary>
private Image GetCameraImage()
{
#pragma warning disable 0162
if (IsDebug)
{
if (File.Exists("test.jpg"))
return new Bitmap("test.jpg");
}
#pragma warning restore 0162
string url = "http://" + IP + SNAPSHOT_URI;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.Headers.Add("Cache-Control", "no-cache");
request.Headers.Add("Pragma", "no-cache");
if (!string.IsNullOrEmpty(_authorization))
{
request.Headers.Add("Authorization", _authorization);
}
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.GetResponse();
}
catch (System.Net.WebException ex)
{
if (ex.Response != null && ((HttpWebResponse)ex.Response).StatusCode == HttpStatusCode.Unauthorized)
{
response = (HttpWebResponse)ex.Response;
string authHeader = response.Headers["WWW-Authenticate"];
response.Close();
string realm = ExtractAuthParam(authHeader, "realm");
string nonce = ExtractAuthParam(authHeader, "nonce");
_authorization = GenerateDigestAuthorization(USER, PASSWD, realm, nonce, "GET", SNAPSHOT_URI);
request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.Headers.Add("Authorization", _authorization);
request.Headers.Add("Cache-Control", "no-cache");
request.Headers.Add("Pragma", "no-cache");
response = (HttpWebResponse)request.GetResponse();
}
else
{
throw;
}
}
byte[] imageData;
using (Stream stream = response.GetResponseStream())
using (MemoryStream memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
imageData = memoryStream.ToArray();
}
return new Bitmap(new MemoryStream(imageData));
}
/// <summary>
/// 从WWW-Authenticate头中提取参数
/// </summary>
private string ExtractAuthParam(string authHeader, string paramName)
{
Regex regex = new Regex(paramName + "=\"([^\"]+)\"");
Match match = regex.Match(authHeader);
if (match.Success)
{
return match.Groups[1].Value;
}
return string.Empty;
}
/// <summary>
/// 生成Digest认证的Authorization头
/// </summary>
private string GenerateDigestAuthorization(string username, string password, string realm, string nonce, string method, string uri)
{
string ha1 = CalculateMD5(username + ":" + realm + ":" + password);
string ha2 = CalculateMD5(method + ":" + uri);
string response = CalculateMD5(ha1 + ":" + nonce + ":" + ha2);
return string.Format("Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", response=\"{4}\"",
username, realm, nonce, uri, response);
}
/// <summary>
/// 计算MD5哈希值
/// </summary>
private string CalculateMD5(string input)
{
using (MD5 md5 = MD5.Create())
{
byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("x2"));
}
return sb.ToString();
}
}
/// <summary>
/// 触发图像捕获事件
/// </summary>
protected virtual void OnImageCaptured(ImageEventArgs e)
{
if (ImageCaptured != null)
{
ImageCaptured(this, e);
}
}
}
/// <summary>
/// 图像事件参数
/// </summary>
public class ImageEventArgs : EventArgs
{
public Image Image { get; set; }
}
}