Files
Z714System/外科辅料和患者防护罩激光抗性测试仪/BasicDemo/AutoCameraForm.cs

1178 lines
46 KiB
C#
Raw Normal View History

2026-01-05 19:21:33 +08:00
using MvCamCtrl.NET;
using System;
2026-01-06 21:13:53 +08:00
using System.Collections.Concurrent;
using System.Collections.Generic;
2026-01-05 19:21:33 +08:00
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
2026-01-06 21:13:53 +08:00
using System.Threading.Tasks;
2026-01-05 19:21:33 +08:00
using System.Windows.Forms;
namespace BasicDemo
{
public partial class AutoCameraForm : Form
{
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
// 支持最多3个相机
private const int MAX_CAMERAS = 3;
// 相机相关对象数组
private MyCamera[] cameras = new MyCamera[MAX_CAMERAS];
private bool[] grabbingFlags = new bool[MAX_CAMERAS];
private Thread[] grabThreads = new Thread[MAX_CAMERAS];
private MyCamera.MV_FRAME_OUT_INFO_EX[] frameInfos = new MyCamera.MV_FRAME_OUT_INFO_EX[MAX_CAMERAS];
// 图像显示相关
private Bitmap[] displayBitmaps = new Bitmap[MAX_CAMERAS];
private IntPtr[] displayBufs = new IntPtr[MAX_CAMERAS];
private UInt32[] displayBufSizes = new UInt32[MAX_CAMERAS];
// 设备列表
private MyCamera.MV_CC_DEVICE_INFO_LIST deviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
// UI控件
private PictureBox[] pictureBoxes = new PictureBox[MAX_CAMERAS];
private Label[] statusLabels = new Label[MAX_CAMERAS];
// 锁对象,用于线程同步
private static readonly object[] locks = new object[MAX_CAMERAS] { new object(), new object(), new object() };
// 状态回调
public delegate bool GetRunStatusDelegate();
private GetRunStatusDelegate _getRunStatusCallback;
private Thread _statusCheckThread;
// 添加:定义返回事件委托
public delegate void ReturnToPreviousFormDelegate();
public event ReturnToPreviousFormDelegate OnReturnRequested;
// 控制变量
private bool _shouldCapture = false;
private bool _camerasInitialized = false;
2026-01-06 21:13:53 +08:00
// 视频录制相关变量(修复后的版本)
private bool[] recordingFlags = new bool[MAX_CAMERAS];
private string[] recordingPaths = new string[MAX_CAMERAS];
private int _frameRate = 30;
private string _videoSavePath = @"D:\CameraRecordings\";
private List<string>[] imagePaths = new List<string>[MAX_CAMERAS]; // 临时保存图片路径
private int[] frameCounters = new int[MAX_CAMERAS]; // 帧计数器
// 添加:帧队列用于异步保存(可选)
private ConcurrentQueue<Bitmap>[] frameQueues = new ConcurrentQueue<Bitmap>[MAX_CAMERAS];
private Thread[] saveThreads = new Thread[MAX_CAMERAS];
private bool[] saveThreadRunning = new bool[MAX_CAMERAS];
// 添加视频录制状态标签
private Label[] recordingStatusLabels = new Label[MAX_CAMERAS];
public AutoCameraForm(GetRunStatusDelegate getRunStatusCallback, ReturnToPreviousFormDelegate returnCallback = null)
2026-01-05 19:21:33 +08:00
{
_getRunStatusCallback = getRunStatusCallback;
if (returnCallback != null)
{
OnReturnRequested += returnCallback;
}
2026-01-06 10:34:48 +08:00
2026-01-05 19:21:33 +08:00
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
// 设置窗体大小
this.Size = new Size(1900, 1000);
this.Text = "三相机自动监控系统";
this.StartPosition = FormStartPosition.CenterScreen;
this.FormClosing += AutoCameraForm_FormClosing;
// 初始化UI
InitializeUI();
2026-01-06 21:13:53 +08:00
// 初始化队列
for (int i = 0; i < MAX_CAMERAS; i++)
{
imagePaths[i] = new List<string>();
frameQueues[i] = new ConcurrentQueue<Bitmap>();
}
// 异步初始化相机,不阻塞窗体显示
this.Shown += (s, e) =>
{
Task.Run(() => InitializeCameras());
};
2026-01-05 19:21:33 +08:00
}
/// <summary>
/// 初始化UI界面
/// </summary>
private void InitializeUI()
{
this.BackColor = Color.FromArgb(240, 240, 240);
2026-01-06 21:13:53 +08:00
2026-01-05 19:21:33 +08:00
// 创建标题
Label titleLabel = new Label
{
Text = "三相机自动监控系统",
Font = new Font("Microsoft YaHei", 20, FontStyle.Bold),
ForeColor = Color.FromArgb(0, 112, 192),
Size = new Size(500, 40),
Location = new Point(700, 20),
TextAlign = ContentAlignment.MiddleCenter
};
this.Controls.Add(titleLabel);
2026-01-06 21:13:53 +08:00
// 创建三个相机显示区域 - 减小高度,为状态栏留出空间
2026-01-05 19:21:33 +08:00
int pictureWidth = 620;
2026-01-06 21:13:53 +08:00
int pictureHeight = 780; // 从850减小到780
2026-01-05 19:21:33 +08:00
int margin = 20;
for (int i = 0; i < MAX_CAMERAS; i++)
{
2026-01-06 21:13:53 +08:00
// 创建容器面板 - 减小高度
2026-01-05 19:21:33 +08:00
Panel cameraPanel = new Panel
{
BackColor = Color.White,
BorderStyle = BorderStyle.FixedSingle,
2026-01-06 21:13:53 +08:00
Size = new Size(pictureWidth, 830), // 从900减小到830
2026-01-05 19:21:33 +08:00
Location = new Point(margin + i * (pictureWidth + margin), 80)
};
this.Controls.Add(cameraPanel);
// 相机标题
Label cameraTitle = new Label
{
Text = $"相机 {i + 1}",
Font = new Font("Microsoft YaHei", 14, FontStyle.Bold),
ForeColor = Color.FromArgb(0, 112, 192),
Size = new Size(200, 30),
Location = new Point(10, 10),
TextAlign = ContentAlignment.MiddleLeft
};
cameraPanel.Controls.Add(cameraTitle);
// 状态标签
statusLabels[i] = new Label
{
Text = "状态:未连接",
Font = new Font("Microsoft YaHei", 10),
ForeColor = Color.Red,
Size = new Size(200, 25),
2026-01-06 21:13:53 +08:00
Location = new Point(10, 45), // Y坐标45
2026-01-05 19:21:33 +08:00
TextAlign = ContentAlignment.MiddleLeft
};
cameraPanel.Controls.Add(statusLabels[i]);
2026-01-06 21:13:53 +08:00
// 录制状态标签 - 调整位置
recordingStatusLabels[i] = new Label
{
Text = "录制状态:等待开始",
Font = new Font("Microsoft YaHei", 9),
ForeColor = Color.Gray,
Size = new Size(580, 20), // 加宽以适应长文本
Location = new Point(10, 70), // Y坐标70在状态标签下方
TextAlign = ContentAlignment.MiddleLeft
};
cameraPanel.Controls.Add(recordingStatusLabels[i]);
// 图像显示区域 - 调整位置
2026-01-05 19:21:33 +08:00
pictureBoxes[i] = new PictureBox
{
Size = new Size(pictureWidth - 40, pictureHeight),
2026-01-06 21:13:53 +08:00
Location = new Point(20, 95), // Y坐标95在录制状态标签下方
2026-01-05 19:21:33 +08:00
BackColor = Color.Black,
SizeMode = PictureBoxSizeMode.StretchImage,
BorderStyle = BorderStyle.FixedSingle
};
cameraPanel.Controls.Add(pictureBoxes[i]);
}
2026-01-06 21:13:53 +08:00
// 创建状态栏 - 保持在底部
Panel statusBar = new Panel
{
BackColor = Color.FromArgb(0, 112, 192),
Size = new Size(1900, 30),
Location = new Point(0, 970)
};
this.Controls.Add(statusBar);
2026-01-05 19:21:33 +08:00
2026-01-06 21:13:53 +08:00
Label statusText = new Label
{
Text = "系统状态:正在初始化...",
Font = new Font("Microsoft YaHei", 10),
ForeColor = Color.White,
Size = new Size(300, 25),
Location = new Point(10, 2),
TextAlign = ContentAlignment.MiddleLeft
};
statusBar.Controls.Add(statusText);
2026-01-05 19:21:33 +08:00
2026-01-06 21:13:53 +08:00
// 添加返回按钮
AddBackButton();
}
2026-01-05 19:21:33 +08:00
private void AddBackButton()
{
Button btnBack = new Button
{
Text = "返回",
Size = new Size(100, 40),
Location = new Point(1750, 30),
Font = new Font("Microsoft YaHei", 12),
BackColor = Color.FromArgb(0, 112, 192),
ForeColor = Color.White,
FlatStyle = FlatStyle.Flat
};
btnBack.Click += (s, e) =>
{
// 触发返回事件
OnReturnRequested?.Invoke();
// 关闭当前窗体
this.Close();
};
this.Controls.Add(btnBack);
}
private void InitializeCameras()
{
try
{
2026-01-06 21:13:53 +08:00
// 在UI线程更新状态
this.Invoke(new Action(() => UpdateSystemStatus("正在初始化SDK...")));
2026-01-05 19:21:33 +08:00
// 1. 初始化SDK
int ret = MyCamera.MV_CC_Initialize_NET();
if (ret != MyCamera.MV_OK)
{
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
MessageBox.Show($"SDK初始化失败错误代码: {ret}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
2026-01-05 19:21:33 +08:00
return;
}
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() => UpdateSystemStatus("正在枚举设备...")));
2026-01-05 19:21:33 +08:00
// 2. 枚举设备
ret = MyCamera.MV_CC_EnumDevices_NET(
MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE,
ref deviceList);
if (ret != MyCamera.MV_OK)
{
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
MessageBox.Show($"设备枚举失败,错误代码: {ret}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
2026-01-05 19:21:33 +08:00
return;
}
// 3. 检查设备数量
if (deviceList.nDeviceNum < MAX_CAMERAS)
{
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
MessageBox.Show($"检测到 {deviceList.nDeviceNum} 个设备,需要至少 {MAX_CAMERAS} 个相机",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
2026-01-05 19:21:33 +08:00
return;
}
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() => UpdateSystemStatus($"检测到 {deviceList.nDeviceNum} 个设备,准备初始化相机...")));
// 4. 并行初始化相机(加快速度)
List<Task> initTasks = new List<Task>();
2026-01-05 19:21:33 +08:00
for (int i = 0; i < MAX_CAMERAS; i++)
{
2026-01-06 21:13:53 +08:00
int index = i; // 创建本地变量
initTasks.Add(Task.Run(() =>
2026-01-05 19:21:33 +08:00
{
2026-01-06 21:13:53 +08:00
if (InitializeSingleCamera(index))
{
this.Invoke(new Action(() =>
UpdateCameraStatus(index, "设备已就绪", Color.Blue)));
}
}));
2026-01-05 19:21:33 +08:00
}
2026-01-06 21:13:53 +08:00
// 等待所有相机初始化完成
Task.WaitAll(initTasks.ToArray());
2026-01-05 19:21:33 +08:00
_camerasInitialized = true;
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
UpdateSystemStatus("所有相机已初始化就绪,等待测试开始...")));
// 5. 启动状态检查线程
2026-01-05 19:21:33 +08:00
StartStatusCheckThread();
}
catch (Exception ex)
{
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
MessageBox.Show($"初始化系统时发生异常: {ex.Message}", "异常", MessageBoxButtons.OK, MessageBoxIcon.Error)));
2026-01-05 19:21:33 +08:00
}
}
/// <summary>
/// 启动状态检查线程
/// </summary>
private void StartStatusCheckThread()
{
_statusCheckThread = new Thread(CheckRunStatusLoop)
{
IsBackground = true
};
_statusCheckThread.Start();
}
/// <summary>
/// 状态检查循环 - 添加异常处理
/// </summary>
private void CheckRunStatusLoop()
{
bool lastStatus = false;
while (!this.IsDisposed && _camerasInitialized)
{
try
{
bool currentStatus = _getRunStatusCallback?.Invoke() ?? false;
if (currentStatus != lastStatus)
{
lastStatus = currentStatus;
// 使用TryInvoke避免窗体已关闭时的异常
if (!this.IsDisposed && this.IsHandleCreated)
{
this.BeginInvoke(new Action(() =>
{
try
{
if (currentStatus)
{
StartAllCamerasCapture();
}
else
{
StopAllCamerasCapture();
}
}
catch (Exception ex)
{
Console.WriteLine($"状态切换异常: {ex.Message}");
}
}));
}
}
Thread.Sleep(1000); // 延长检查间隔减少CPU使用
}
catch
{
// 忽略异常
}
}
}
/// <summary>
/// 开始所有相机采集
/// </summary>
private void StartAllCamerasCapture()
{
if (!_camerasInitialized) return;
UpdateSystemStatus("测试开始,正在启动相机采集...");
for (int i = 0; i < MAX_CAMERAS; i++)
{
if (cameras[i] != null && !grabbingFlags[i])
{
StartSingleCameraCapture(i);
}
}
UpdateSystemStatus("所有相机已开始采集");
}
/// <summary>
/// 停止所有相机采集
/// </summary>
private void StopAllCamerasCapture()
{
if (!_camerasInitialized) return;
UpdateSystemStatus("测试结束,正在停止相机采集...");
for (int i = 0; i < MAX_CAMERAS; i++)
{
if (cameras[i] != null && grabbingFlags[i])
{
StopSingleCameraCapture(i);
}
}
UpdateSystemStatus("所有相机已停止采集");
}
private bool InitializeSingleCamera(int cameraIndex)
{
try
{
2026-01-06 21:13:53 +08:00
// 延迟启动,避免同时打开冲突
Thread.Sleep(cameraIndex * 200);
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, "正在打开...", Color.Orange)));
2026-01-05 19:21:33 +08:00
// 1. 创建设备信息
MyCamera.MV_CC_DEVICE_INFO device =
(MyCamera.MV_CC_DEVICE_INFO)Marshal.PtrToStructure(
deviceList.pDeviceInfo[cameraIndex], typeof(MyCamera.MV_CC_DEVICE_INFO));
// 2. 创建相机对象
cameras[cameraIndex] = new MyCamera();
// 3. 创建设备
int ret = cameras[cameraIndex].MV_CC_CreateDevice_NET(ref device);
if (ret != MyCamera.MV_OK)
{
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, $"创建设备失败: {ret}", Color.Red)));
2026-01-05 19:21:33 +08:00
return false;
}
2026-01-06 21:13:53 +08:00
// 4. 打开设备(设置超时时间)
2026-01-05 19:21:33 +08:00
ret = cameras[cameraIndex].MV_CC_OpenDevice_NET();
if (ret != MyCamera.MV_OK)
{
cameras[cameraIndex].MV_CC_DestroyDevice_NET();
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, $"打开设备失败: {ret}", Color.Red)));
2026-01-05 19:21:33 +08:00
return false;
}
2026-01-06 21:13:53 +08:00
// 5. 简化设置,只设置必要的参数
2026-01-05 19:21:33 +08:00
cameras[cameraIndex].MV_CC_SetEnumValue_NET("AcquisitionMode",
(uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS);
cameras[cameraIndex].MV_CC_SetEnumValue_NET("TriggerMode",
(uint)MyCamera.MV_CAM_TRIGGER_MODE.MV_TRIGGER_MODE_OFF);
return true;
}
catch (Exception ex)
{
2026-01-06 21:13:53 +08:00
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, $"异常: {ex.Message}", Color.Red)));
2026-01-05 19:21:33 +08:00
return false;
}
}
private void StartSingleCameraCapture(int cameraIndex)
{
try
{
if (cameras[cameraIndex] == null || grabbingFlags[cameraIndex])
return;
// 检查相机是否已正确打开
if (!IsCameraValid(cameraIndex))
{
UpdateCameraStatus(cameraIndex, "相机未正确初始化", Color.Red);
return;
}
// 1. 准备采集资源
if (!PrepareForGrabbing(cameraIndex))
{
UpdateCameraStatus(cameraIndex, "采集准备失败", Color.Red);
return;
}
2026-01-06 21:13:53 +08:00
// 2. 初始化录制系统
InitializeRecording(cameraIndex);
2026-01-06 13:34:31 +08:00
2026-01-06 21:13:53 +08:00
// 3. 设置采集标志
2026-01-05 19:21:33 +08:00
grabbingFlags[cameraIndex] = true;
2026-01-06 21:13:53 +08:00
// 4. 创建采集线程
2026-01-05 19:21:33 +08:00
int index = cameraIndex;
grabThreads[cameraIndex] = new Thread(() => GrabThreadProc(index));
grabThreads[cameraIndex].Name = $"CameraGrabThread_{cameraIndex}";
grabThreads[cameraIndex].IsBackground = true;
2026-01-06 21:13:53 +08:00
// 5. 启动保存线程(如果使用队列方式)
StartSaveThread(cameraIndex);
2026-01-05 19:21:33 +08:00
2026-01-06 21:13:53 +08:00
// 启动采集线程
grabThreads[cameraIndex].Start();
2026-01-05 19:21:33 +08:00
2026-01-06 21:13:53 +08:00
// 6. 开始采集
2026-01-05 19:21:33 +08:00
int ret = cameras[cameraIndex].MV_CC_StartGrabbing_NET();
if (ret != MyCamera.MV_OK)
{
UpdateCameraStatus(cameraIndex, $"开始采集失败: {ret}", Color.Red);
grabbingFlags[cameraIndex] = false;
return;
}
2026-01-06 21:13:53 +08:00
UpdateCameraStatus(cameraIndex, "正在采集和录制...", Color.Green);
2026-01-05 19:21:33 +08:00
}
catch (Exception ex)
{
UpdateCameraStatus(cameraIndex, $"开始采集异常: {ex.Message}", Color.Red);
Console.WriteLine($"启动相机{cameraIndex + 1}异常: {ex.ToString()}");
}
}
2026-01-06 21:13:53 +08:00
private void InitializeRecording(int cameraIndex)
{
try
{
// 确保保存目录存在
Directory.CreateDirectory(_videoSavePath);
// 创建以时间戳命名的子目录 - 添加相机编号前缀
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string cameraFolder = Path.Combine(_videoSavePath, $"Camera{cameraIndex + 1}_{timestamp}");
recordingPaths[cameraIndex] = cameraFolder;
// 创建frames子目录
string framesFolder = Path.Combine(cameraFolder, "frames");
Directory.CreateDirectory(framesFolder);
// 初始化图片路径列表
imagePaths[cameraIndex] = new List<string>();
frameCounters[cameraIndex] = 0; // 重置帧计数器
recordingFlags[cameraIndex] = true;
// 更新录制状态
UpdateRecordingStatus(cameraIndex, $"正在录制到: Camera{cameraIndex + 1}_{timestamp}", Color.Blue);
Console.WriteLine($"相机{cameraIndex + 1}录制已初始化,保存路径: {cameraFolder}");
}
catch (Exception ex)
{
Console.WriteLine($"初始化录制失败(相机{cameraIndex + 1}: {ex.Message}");
recordingFlags[cameraIndex] = false;
UpdateRecordingStatus(cameraIndex, "录制初始化失败", Color.Red);
}
}
/// <summary>
/// 更新录制状态显示
/// </summary>
private void UpdateRecordingStatus(int cameraIndex, string message, Color color)
{
if (recordingStatusLabels[cameraIndex].InvokeRequired)
{
recordingStatusLabels[cameraIndex].Invoke(new Action(() =>
{
recordingStatusLabels[cameraIndex].Text = $"录制状态:{message}";
recordingStatusLabels[cameraIndex].ForeColor = color;
}));
}
else
{
recordingStatusLabels[cameraIndex].Text = $"录制状态:{message}";
recordingStatusLabels[cameraIndex].ForeColor = color;
}
}
/// <summary>
/// 启动保存线程(使用队列方式保存图片)
/// </summary>
private void StartSaveThread(int cameraIndex)
{
saveThreadRunning[cameraIndex] = true;
saveThreads[cameraIndex] = new Thread(() => SaveImageThreadProc(cameraIndex));
saveThreads[cameraIndex].Name = $"SaveThread_{cameraIndex}";
saveThreads[cameraIndex].IsBackground = true;
saveThreads[cameraIndex].Start();
}
/// <summary>
/// 保存图片线程处理
/// </summary>
private void SaveImageThreadProc(int cameraIndex)
{
while (saveThreadRunning[cameraIndex] || !frameQueues[cameraIndex].IsEmpty)
{
try
{
if (frameQueues[cameraIndex].TryDequeue(out Bitmap frame))
{
SaveFrameAsImage(cameraIndex, frame);
frame.Dispose();
}
else
{
Thread.Sleep(10); // 队列为空时休眠
}
}
catch (Exception ex)
{
Console.WriteLine($"保存图片线程异常(相机{cameraIndex + 1}: {ex.Message}");
}
}
}
private void SaveFrameAsImage(int cameraIndex, Bitmap frame)
{
try
{
if (!recordingFlags[cameraIndex] || string.IsNullOrEmpty(recordingPaths[cameraIndex]))
return;
int currentFrame = frameCounters[cameraIndex]++;
string framesFolder = Path.Combine(recordingPaths[cameraIndex], "frames");
// 添加相机编号到文件名,方便识别
string imagePath = Path.Combine(framesFolder, $"Cam{cameraIndex + 1}_frame_{currentFrame:D6}.jpg");
// 使用高质量压缩保存图片
ImageCodecInfo jpgEncoder = GetEncoderInfo("image/jpeg");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 85L); // 85%质量
lock (locks[cameraIndex])
{
frame.Save(imagePath, jpgEncoder, encoderParams);
}
imagePaths[cameraIndex].Add(imagePath);
// 每50帧更新一次录制状态
if (currentFrame % 50 == 0)
{
UpdateRecordingStatus(cameraIndex, $"已录制 {currentFrame} 帧", Color.Green);
Console.WriteLine($"相机{cameraIndex + 1}已保存 {currentFrame} 帧");
}
}
catch (Exception ex)
{
Console.WriteLine($"保存图片失败(相机{cameraIndex + 1}: {ex.Message}");
}
}
// 获取图像编码器
private ImageCodecInfo GetEncoderInfo(string mimeType)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.MimeType == mimeType)
return codec;
}
return null;
}
2026-01-06 10:34:48 +08:00
2026-01-05 19:21:33 +08:00
/// <summary>
/// 检查相机是否有效
/// </summary>
private bool IsCameraValid(int cameraIndex)
{
try
{
return cameras[cameraIndex] != null;
}
catch
{
return false;
}
}
2026-01-06 21:13:53 +08:00
2026-01-05 19:21:33 +08:00
private void StopSingleCameraCapture(int cameraIndex)
{
try
{
if (cameras[cameraIndex] == null || !grabbingFlags[cameraIndex])
return;
// 1. 停止采集标志
grabbingFlags[cameraIndex] = false;
// 2. 等待采集线程结束
if (grabThreads[cameraIndex] != null && grabThreads[cameraIndex].IsAlive)
{
grabThreads[cameraIndex].Join(1000);
}
// 3. 停止相机采集
cameras[cameraIndex].MV_CC_StopGrabbing_NET();
// 4. 清理资源
CleanupCameraResources(cameraIndex);
UpdateCameraStatus(cameraIndex, "已停止采集", Color.Blue);
2026-01-06 21:13:53 +08:00
// 5. 创建视频文件
if (imagePaths[cameraIndex] != null && imagePaths[cameraIndex].Count > 0)
{
// 立即创建视频文件
string videoPath = CreateVideoFromImages(cameraIndex);
if (!string.IsNullOrEmpty(videoPath))
{
UpdateRecordingStatus(cameraIndex, $"视频已保存: {Path.GetFileName(videoPath)}", Color.Green);
// 显示视频文件位置
MessageBox.Show($"相机{cameraIndex + 1}视频已保存到:\n{videoPath}\n\n共录制 {imagePaths[cameraIndex].Count} 帧",
"录制完成",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
else
{
Console.WriteLine($"相机{cameraIndex + 1}没有保存任何帧,跳过视频转换");
UpdateRecordingStatus(cameraIndex, "没有录制到帧", Color.Orange);
}
// 6. 停止录制标志
recordingFlags[cameraIndex] = false;
2026-01-05 19:21:33 +08:00
}
catch (Exception ex)
{
UpdateCameraStatus(cameraIndex, $"停止采集异常: {ex.Message}", Color.Red);
2026-01-06 21:13:53 +08:00
UpdateRecordingStatus(cameraIndex, "录制出错", Color.Red);
2026-01-05 19:21:33 +08:00
}
}
private bool PrepareForGrabbing(int cameraIndex)
{
try
{
// 获取图像参数
MyCamera.MVCC_INTVALUE_EX width = new MyCamera.MVCC_INTVALUE_EX();
int ret = cameras[cameraIndex].MV_CC_GetIntValueEx_NET("Width", ref width);
if (ret != MyCamera.MV_OK) return false;
MyCamera.MVCC_INTVALUE_EX height = new MyCamera.MVCC_INTVALUE_EX();
ret = cameras[cameraIndex].MV_CC_GetIntValueEx_NET("Height", ref height);
if (ret != MyCamera.MV_OK) return false;
MyCamera.MVCC_ENUMVALUE pixelFormat = new MyCamera.MVCC_ENUMVALUE();
ret = cameras[cameraIndex].MV_CC_GetEnumValue_NET("PixelFormat", ref pixelFormat);
if (ret != MyCamera.MV_OK) return false;
// 创建Bitmap
PixelFormat bitmapFormat = IsMonoPixelFormat(pixelFormat.nCurValue) ?
PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb;
// 确保清理旧的Bitmap
if (displayBitmaps[cameraIndex] != null)
{
displayBitmaps[cameraIndex].Dispose();
displayBitmaps[cameraIndex] = null;
}
// 创建新的Bitmap
displayBitmaps[cameraIndex] = new Bitmap((int)width.nCurValue, (int)height.nCurValue, bitmapFormat);
// 如果是8位灰度图设置调色板
if (bitmapFormat == PixelFormat.Format8bppIndexed)
{
ColorPalette palette = displayBitmaps[cameraIndex].Palette;
for (int i = 0; i < palette.Entries.Length; i++)
{
palette.Entries[i] = Color.FromArgb(i, i, i);
}
displayBitmaps[cameraIndex].Palette = palette;
}
// 分配显示缓冲区
uint bufferSize = bitmapFormat == PixelFormat.Format8bppIndexed ?
(uint)(width.nCurValue * height.nCurValue) :
(uint)(width.nCurValue * height.nCurValue * 3);
// 确保清理旧的缓冲区
if (displayBufs[cameraIndex] != IntPtr.Zero)
{
Marshal.FreeHGlobal(displayBufs[cameraIndex]);
displayBufs[cameraIndex] = IntPtr.Zero;
}
displayBufs[cameraIndex] = Marshal.AllocHGlobal((int)bufferSize);
displayBufSizes[cameraIndex] = bufferSize;
frameInfos[cameraIndex] = new MyCamera.MV_FRAME_OUT_INFO_EX();
return true;
}
catch (Exception ex)
{
Console.WriteLine($"准备相机{cameraIndex + 1}失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 判断是否为单色像素格式
/// </summary>
private bool IsMonoPixelFormat(uint pixelFormat)
{
switch (pixelFormat)
{
case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8:
case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10:
case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12:
case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono10_Packed:
case (uint)MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono12_Packed:
return true;
default:
return false;
}
}
/// <summary>
2026-01-06 21:13:53 +08:00
/// 创建视频文件并返回视频路径
/// </summary>
private string CreateVideoFromImages(int cameraIndex)
{
try
{
string framesFolder = Path.Combine(recordingPaths[cameraIndex], "frames");
// 检查文件夹是否存在
if (!Directory.Exists(framesFolder))
{
Console.WriteLine($"相机{cameraIndex + 1}的frames文件夹不存在: {framesFolder}");
return null;
}
// 获取所有jpg文件
string[] imageFiles = Directory.GetFiles(framesFolder, $"Cam{cameraIndex + 1}_*.jpg");
if (imageFiles.Length == 0)
{
// 尝试查找任何jpg文件兼容之前的命名方式
imageFiles = Directory.GetFiles(framesFolder, "*.jpg");
if (imageFiles.Length == 0)
{
Console.WriteLine($"相机{cameraIndex + 1}没有找到jpg图片文件");
return null;
}
}
Console.WriteLine($"相机{cameraIndex + 1}找到 {imageFiles.Length} 张图片");
// 创建视频文件路径
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string videoPath = Path.Combine(recordingPaths[cameraIndex], $"Camera{cameraIndex + 1}_{timestamp}.mp4");
// 创建转换批处理文件
string batFilePath = CreateConversionBatchFile(cameraIndex, framesFolder, videoPath, imageFiles.Length);
if (File.Exists(batFilePath))
{
// 自动运行转换
var process = System.Diagnostics.Process.Start(batFilePath);
// 等待转换完成最大等待30秒
if (process != null)
{
process.WaitForExit(30000);
// 检查视频文件是否生成
if (File.Exists(videoPath))
{
FileInfo videoInfo = new FileInfo(videoPath);
Console.WriteLine($"视频文件已创建: {videoPath} ({videoInfo.Length / 1024 / 1024} MB)");
// 创建视频文件的快捷方式到根目录,方便查找
CreateVideoShortcut(cameraIndex, videoPath);
return videoPath;
}
}
}
return null;
}
catch (Exception ex)
{
Console.WriteLine($"创建视频失败(相机{cameraIndex + 1}: {ex.Message}");
return null;
}
}
/// <summary>
/// 在录制根目录创建视频文件的快捷方式(文本文件)
2026-01-05 19:21:33 +08:00
/// </summary>
2026-01-06 21:13:53 +08:00
private void CreateVideoShortcut(int cameraIndex, string videoPath)
{
try
{
string shortcutPath = Path.Combine(_videoSavePath, $"Camera{cameraIndex + 1}_最新视频.txt");
using (StreamWriter sw = new StreamWriter(shortcutPath, false, Encoding.Default))
{
sw.WriteLine("相机视频文件位置信息");
sw.WriteLine("=========================");
sw.WriteLine($"相机编号: {cameraIndex + 1}");
sw.WriteLine($"视频文件: {Path.GetFileName(videoPath)}");
sw.WriteLine($"文件大小: {new FileInfo(videoPath).Length / 1024 / 1024} MB");
sw.WriteLine($"录制时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
sw.WriteLine($"视频路径: {videoPath}");
sw.WriteLine();
sw.WriteLine("图片文件夹:");
sw.WriteLine($" {Path.Combine(recordingPaths[cameraIndex], "frames")}");
}
Console.WriteLine($"已创建视频位置信息文件: {shortcutPath}");
}
catch (Exception ex)
{
Console.WriteLine($"创建快捷方式失败: {ex.Message}");
}
}
/// <summary>
/// 创建转换批处理文件
/// </summary>
private string CreateConversionBatchFile(int cameraIndex, string framesFolder, string videoPath, int imageCount)
{
try
{
string batFilePath = Path.Combine(recordingPaths[cameraIndex], $"Convert_Camera{cameraIndex + 1}.bat");
using (StreamWriter sw = new StreamWriter(batFilePath, false, Encoding.Default))
{
sw.WriteLine("@echo off");
sw.WriteLine($"echo 正在转换相机{cameraIndex + 1}的{imageCount}张图片为视频...");
sw.WriteLine($"set INPUT_PATTERN={framesFolder}\\Cam{cameraIndex + 1}_*.jpg");
sw.WriteLine($"set OUTPUT_FILE={videoPath}");
sw.WriteLine($"set FRAMERATE={_frameRate}");
sw.WriteLine();
sw.WriteLine("echo 正在转换,请稍候...");
sw.WriteLine("ffmpeg -framerate %FRAMERATE% -pattern_type glob -i \"%INPUT_PATTERN%\" -c:v libx264 -pix_fmt yuv420p \"%OUTPUT_FILE%\"");
sw.WriteLine("if %ERRORLEVEL% EQU 0 (");
sw.WriteLine(" echo 转换成功!");
sw.WriteLine(" echo 视频文件: %OUTPUT_FILE%");
sw.WriteLine(") else (");
sw.WriteLine(" echo 转换失败");
sw.WriteLine(")");
}
Console.WriteLine($"已创建转换批处理文件: {batFilePath}");
return batFilePath;
}
catch (Exception ex)
{
Console.WriteLine($"创建批处理文件失败: {ex.Message}");
return null;
}
}
2026-01-05 19:21:33 +08:00
private void GrabThreadProc(int cameraIndex)
{
MyCamera camera = cameras[cameraIndex];
MyCamera.MV_FRAME_OUT frameOut = new MyCamera.MV_FRAME_OUT();
MyCamera.MV_PIXEL_CONVERT_PARAM convertParam = new MyCamera.MV_PIXEL_CONVERT_PARAM();
MyCamera.MV_DISPLAY_FRAME_INFO displayInfo = new MyCamera.MV_DISPLAY_FRAME_INFO();
2026-01-06 21:13:53 +08:00
// 帧率控制变量
DateTime lastFrameTime = DateTime.Now;
TimeSpan frameInterval = TimeSpan.FromMilliseconds(1000.0 / _frameRate);
2026-01-06 10:34:48 +08:00
2026-01-05 19:21:33 +08:00
while (grabbingFlags[cameraIndex])
{
try
{
// 获取图像缓冲区
int ret = camera.MV_CC_GetImageBuffer_NET(ref frameOut, 1000);
if (ret == MyCamera.MV_OK)
{
2026-01-06 21:13:53 +08:00
// 控制帧率
DateTime currentTime = DateTime.Now;
if (currentTime - lastFrameTime < frameInterval)
{
// 帧率过快,跳过此帧
camera.MV_CC_FreeImageBuffer_NET(ref frameOut);
continue;
}
lastFrameTime = currentTime;
Bitmap frameCopy = null;
2026-01-05 19:21:33 +08:00
lock (locks[cameraIndex])
{
// 更新帧信息
frameInfos[cameraIndex] = frameOut.stFrameInfo;
// 像素格式转换
convertParam.nWidth = frameOut.stFrameInfo.nWidth;
convertParam.nHeight = frameOut.stFrameInfo.nHeight;
convertParam.enSrcPixelType = frameOut.stFrameInfo.enPixelType;
convertParam.pSrcData = frameOut.pBufAddr;
convertParam.nSrcDataLen = frameOut.stFrameInfo.nFrameLen;
convertParam.pDstBuffer = displayBufs[cameraIndex];
convertParam.nDstBufferSize = displayBufSizes[cameraIndex];
if (displayBitmaps[cameraIndex].PixelFormat == PixelFormat.Format8bppIndexed)
{
convertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_Mono8;
}
else
{
convertParam.enDstPixelType = MyCamera.MvGvspPixelType.PixelType_Gvsp_BGR8_Packed;
}
camera.MV_CC_ConvertPixelType_NET(ref convertParam);
2026-01-06 10:34:48 +08:00
// 更新Bitmap
2026-01-05 19:21:33 +08:00
BitmapData bitmapData = displayBitmaps[cameraIndex].LockBits(
new Rectangle(0, 0, (int)frameOut.stFrameInfo.nWidth, (int)frameOut.stFrameInfo.nHeight),
ImageLockMode.ReadWrite, displayBitmaps[cameraIndex].PixelFormat);
CopyMemory(bitmapData.Scan0, convertParam.pDstBuffer,
(uint)(bitmapData.Stride * displayBitmaps[cameraIndex].Height));
displayBitmaps[cameraIndex].UnlockBits(bitmapData);
2026-01-06 21:13:53 +08:00
// 创建帧的副本用于保存(避免共享资源)
if (recordingFlags[cameraIndex])
{
frameCopy = new Bitmap(displayBitmaps[cameraIndex]);
}
2026-01-05 19:21:33 +08:00
}
2026-01-06 10:34:48 +08:00
// 使用SDK显示
2026-01-05 19:21:33 +08:00
displayInfo.hWnd = pictureBoxes[cameraIndex].Handle;
displayInfo.pData = frameOut.pBufAddr;
displayInfo.nDataLen = frameOut.stFrameInfo.nFrameLen;
displayInfo.nWidth = frameOut.stFrameInfo.nWidth;
displayInfo.nHeight = frameOut.stFrameInfo.nHeight;
displayInfo.enPixelType = frameOut.stFrameInfo.enPixelType;
// 在UI线程中调用显示
if (this.InvokeRequired)
{
this.Invoke(new Action(() =>
{
camera.MV_CC_DisplayOneFrame_NET(ref displayInfo);
}));
}
else
{
camera.MV_CC_DisplayOneFrame_NET(ref displayInfo);
}
2026-01-06 21:13:53 +08:00
// 将帧添加到队列用于保存
if (recordingFlags[cameraIndex] && frameCopy != null)
{
frameQueues[cameraIndex].Enqueue(frameCopy);
}
2026-01-05 19:21:33 +08:00
// 释放图像缓冲区
camera.MV_CC_FreeImageBuffer_NET(ref frameOut);
}
}
catch (Exception ex)
{
Console.WriteLine($"相机{cameraIndex + 1}采集异常: {ex.Message}");
}
}
}
2026-01-06 10:34:48 +08:00
2026-01-05 19:21:33 +08:00
/// <summary>
2026-01-06 21:13:53 +08:00
/// 清理相机资源
2026-01-05 19:21:33 +08:00
/// </summary>
private void CleanupCameraResources(int cameraIndex)
{
2026-01-06 10:34:48 +08:00
// 清理显示缓冲区
2026-01-05 19:21:33 +08:00
if (displayBufs[cameraIndex] != IntPtr.Zero)
{
Marshal.FreeHGlobal(displayBufs[cameraIndex]);
displayBufs[cameraIndex] = IntPtr.Zero;
}
2026-01-06 10:34:48 +08:00
// 清理Bitmap
2026-01-05 19:21:33 +08:00
if (displayBitmaps[cameraIndex] != null)
{
displayBitmaps[cameraIndex].Dispose();
displayBitmaps[cameraIndex] = null;
}
displayBufSizes[cameraIndex] = 0;
2026-01-06 10:34:48 +08:00
2026-01-06 21:13:53 +08:00
// 清空队列
while (frameQueues[cameraIndex].TryDequeue(out Bitmap frame))
{
frame.Dispose();
}
2026-01-05 19:21:33 +08:00
}
/// <summary>
/// 更新相机状态显示
/// </summary>
private void UpdateCameraStatus(int cameraIndex, string message, Color color)
{
if (statusLabels[cameraIndex].InvokeRequired)
{
statusLabels[cameraIndex].Invoke(new Action(() =>
{
statusLabels[cameraIndex].Text = $"状态:{message}";
statusLabels[cameraIndex].ForeColor = color;
}));
}
else
{
statusLabels[cameraIndex].Text = $"状态:{message}";
statusLabels[cameraIndex].ForeColor = color;
}
}
/// <summary>
/// 更新系统状态
/// </summary>
private void UpdateSystemStatus(string message)
{
// 这里可以添加系统状态栏更新逻辑
Console.WriteLine($"系统状态: {message}");
}
private void AutoCameraForm_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
// 停止状态检查线程
if (_statusCheckThread != null && _statusCheckThread.IsAlive)
{
_statusCheckThread.Join(1000);
}
2026-01-06 21:13:53 +08:00
// 停止所有采集和录制
2026-01-05 19:21:33 +08:00
for (int i = 0; i < MAX_CAMERAS; i++)
{
grabbingFlags[i] = false;
2026-01-06 21:13:53 +08:00
recordingFlags[i] = false;
saveThreadRunning[i] = false;
2026-01-05 19:21:33 +08:00
if (grabThreads[i] != null && grabThreads[i].IsAlive)
{
grabThreads[i].Join(1000);
}
2026-01-06 21:13:53 +08:00
if (saveThreads[i] != null && saveThreads[i].IsAlive)
{
saveThreads[i].Join(2000);
}
2026-01-05 19:21:33 +08:00
if (cameras[i] != null)
{
cameras[i].MV_CC_StopGrabbing_NET();
cameras[i].MV_CC_CloseDevice_NET();
cameras[i].MV_CC_DestroyDevice_NET();
}
CleanupCameraResources(i);
}
// 反初始化SDK
MyCamera.MV_CC_Finalize_NET();
}
catch (Exception ex)
{
MessageBox.Show($"清理资源时发生异常: {ex.Message}", "警告",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
}