Files
Z714System/外科辅料和患者防护罩激光抗性测试仪/BasicDemo/AutoCameraForm.cs
2026-01-06 21:19:30 +08:00

1185 lines
46 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 MvCamCtrl.NET;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
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;
// 视频录制相关变量(修复后的版本)
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)
{
_getRunStatusCallback = getRunStatusCallback;
if (returnCallback != null)
{
OnReturnRequested += returnCallback;
}
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
// 设置窗体大小
this.Size = new Size(1900, 1000);
this.Text = "三相机自动监控系统";
this.StartPosition = FormStartPosition.CenterScreen;
this.FormClosing += AutoCameraForm_FormClosing;
// 初始化UI
InitializeUI();
// 初始化队列
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());
};
}
/// <summary>
/// 初始化UI界面
/// </summary>
private void InitializeUI()
{
this.BackColor = Color.FromArgb(240, 240, 240);
// 创建标题
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);
// 创建三个相机显示区域 - 减小高度,为状态栏留出空间
int pictureWidth = 620;
int pictureHeight = 780; // 从850减小到780
int margin = 20;
for (int i = 0; i < MAX_CAMERAS; i++)
{
// 创建容器面板 - 减小高度
Panel cameraPanel = new Panel
{
BackColor = Color.White,
BorderStyle = BorderStyle.FixedSingle,
Size = new Size(pictureWidth, 830), // 从900减小到830
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),
Location = new Point(10, 45), // Y坐标45
TextAlign = ContentAlignment.MiddleLeft
};
cameraPanel.Controls.Add(statusLabels[i]);
// 录制状态标签 - 调整位置
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]);
// 图像显示区域 - 调整位置
pictureBoxes[i] = new PictureBox
{
Size = new Size(pictureWidth - 40, pictureHeight),
Location = new Point(20, 95), // Y坐标95在录制状态标签下方
BackColor = Color.Black,
SizeMode = PictureBoxSizeMode.StretchImage,
BorderStyle = BorderStyle.FixedSingle
};
cameraPanel.Controls.Add(pictureBoxes[i]);
}
// 创建状态栏 - 保持在底部
Panel statusBar = new Panel
{
BackColor = Color.FromArgb(0, 112, 192),
Size = new Size(1900, 30),
Location = new Point(0, 970)
};
this.Controls.Add(statusBar);
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);
// 添加返回按钮
AddBackButton();
}
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
{
// 在UI线程更新状态
this.Invoke(new Action(() => UpdateSystemStatus("正在初始化SDK...")));
// 1. 初始化SDK
int ret = MyCamera.MV_CC_Initialize_NET();
if (ret != MyCamera.MV_OK)
{
this.Invoke(new Action(() =>
MessageBox.Show($"SDK初始化失败错误代码: {ret}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
return;
}
this.Invoke(new Action(() => UpdateSystemStatus("正在枚举设备...")));
// 2. 枚举设备
ret = MyCamera.MV_CC_EnumDevices_NET(
MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE,
ref deviceList);
if (ret != MyCamera.MV_OK)
{
this.Invoke(new Action(() =>
MessageBox.Show($"设备枚举失败,错误代码: {ret}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
return;
}
// 3. 检查设备数量
if (deviceList.nDeviceNum < MAX_CAMERAS)
{
this.Invoke(new Action(() =>
MessageBox.Show($"检测到 {deviceList.nDeviceNum} 个设备,需要至少 {MAX_CAMERAS} 个相机",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
return;
}
this.Invoke(new Action(() => UpdateSystemStatus($"检测到 {deviceList.nDeviceNum} 个设备,准备初始化相机...")));
// 4. 并行初始化相机(加快速度)
List<Task> initTasks = new List<Task>();
for (int i = 0; i < MAX_CAMERAS; i++)
{
int index = i; // 创建本地变量
initTasks.Add(Task.Run(() =>
{
if (InitializeSingleCamera(index))
{
this.Invoke(new Action(() =>
UpdateCameraStatus(index, "设备已就绪", Color.Blue)));
}
}));
}
// 等待所有相机初始化完成
Task.WaitAll(initTasks.ToArray());
_camerasInitialized = true;
this.Invoke(new Action(() =>
UpdateSystemStatus("所有相机已初始化就绪,等待测试开始...")));
// 5. 启动状态检查线程
StartStatusCheckThread();
}
catch (Exception ex)
{
this.Invoke(new Action(() =>
MessageBox.Show($"初始化系统时发生异常: {ex.Message}", "异常", MessageBoxButtons.OK, MessageBoxIcon.Error)));
}
}
/// <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
{
// 延迟启动,避免同时打开冲突
Thread.Sleep(cameraIndex * 200);
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, "正在打开...", Color.Orange)));
// 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)
{
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, $"创建设备失败: {ret}", Color.Red)));
return false;
}
// 4. 打开设备(设置超时时间)
ret = cameras[cameraIndex].MV_CC_OpenDevice_NET();
if (ret != MyCamera.MV_OK)
{
cameras[cameraIndex].MV_CC_DestroyDevice_NET();
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, $"打开设备失败: {ret}", Color.Red)));
return false;
}
// 5. 简化设置,只设置必要的参数
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)
{
this.Invoke(new Action(() =>
UpdateCameraStatus(cameraIndex, $"异常: {ex.Message}", Color.Red)));
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;
}
// 2. 初始化录制系统
InitializeRecording(cameraIndex);
// 3. 设置采集标志
grabbingFlags[cameraIndex] = true;
// 4. 创建采集线程
int index = cameraIndex;
grabThreads[cameraIndex] = new Thread(() => GrabThreadProc(index));
grabThreads[cameraIndex].Name = $"CameraGrabThread_{cameraIndex}";
grabThreads[cameraIndex].IsBackground = true;
// 5. 启动保存线程(如果使用队列方式)
StartSaveThread(cameraIndex);
// 启动采集线程
grabThreads[cameraIndex].Start();
// 6. 开始采集
int ret = cameras[cameraIndex].MV_CC_StartGrabbing_NET();
if (ret != MyCamera.MV_OK)
{
UpdateCameraStatus(cameraIndex, $"开始采集失败: {ret}", Color.Red);
grabbingFlags[cameraIndex] = false;
return;
}
UpdateCameraStatus(cameraIndex, "正在采集和录制...", Color.Green);
}
catch (Exception ex)
{
UpdateCameraStatus(cameraIndex, $"开始采集异常: {ex.Message}", Color.Red);
Console.WriteLine($"启动相机{cameraIndex + 1}异常: {ex.ToString()}");
}
}
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;
}
/// <summary>
/// 检查相机是否有效
/// </summary>
private bool IsCameraValid(int cameraIndex)
{
try
{
return cameras[cameraIndex] != null;
}
catch
{
return false;
}
}
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);
// 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;
}
catch (Exception ex)
{
UpdateCameraStatus(cameraIndex, $"停止采集异常: {ex.Message}", Color.Red);
UpdateRecordingStatus(cameraIndex, "录制出错", Color.Red);
}
}
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>
/// 创建视频文件并返回视频路径
/// </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>
/// 在录制根目录创建视频文件的快捷方式(文本文件)
/// </summary>
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;
}
}
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();
// 帧率控制变量
DateTime lastFrameTime = DateTime.Now;
TimeSpan frameInterval = TimeSpan.FromMilliseconds(1000.0 / _frameRate);
while (grabbingFlags[cameraIndex])
{
try
{
// 获取图像缓冲区
int ret = camera.MV_CC_GetImageBuffer_NET(ref frameOut, 1000);
if (ret == MyCamera.MV_OK)
{
// 控制帧率
DateTime currentTime = DateTime.Now;
if (currentTime - lastFrameTime < frameInterval)
{
// 帧率过快,跳过此帧
camera.MV_CC_FreeImageBuffer_NET(ref frameOut);
continue;
}
lastFrameTime = currentTime;
Bitmap frameCopy = null;
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);
// 更新Bitmap
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);
// 创建帧的副本用于保存(避免共享资源)
if (recordingFlags[cameraIndex])
{
frameCopy = new Bitmap(displayBitmaps[cameraIndex]);
}
}
// 使用SDK显示
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);
}
// 将帧添加到队列用于保存
if (recordingFlags[cameraIndex] && frameCopy != null)
{
frameQueues[cameraIndex].Enqueue(frameCopy);
}
// 释放图像缓冲区
camera.MV_CC_FreeImageBuffer_NET(ref frameOut);
}
}
catch (Exception ex)
{
Console.WriteLine($"相机{cameraIndex + 1}采集异常: {ex.Message}");
}
}
}
/// <summary>
/// 清理相机资源
/// </summary>
private void CleanupCameraResources(int cameraIndex)
{
// 清理显示缓冲区
if (displayBufs[cameraIndex] != IntPtr.Zero)
{
Marshal.FreeHGlobal(displayBufs[cameraIndex]);
displayBufs[cameraIndex] = IntPtr.Zero;
}
// 清理Bitmap
if (displayBitmaps[cameraIndex] != null)
{
displayBitmaps[cameraIndex].Dispose();
displayBitmaps[cameraIndex] = null;
}
displayBufSizes[cameraIndex] = 0;
// 清空队列
while (frameQueues[cameraIndex].TryDequeue(out Bitmap frame))
{
frame.Dispose();
}
}
/// <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);
}
// 停止所有采集和录制
for (int i = 0; i < MAX_CAMERAS; i++)
{
grabbingFlags[i] = false;
recordingFlags[i] = false;
saveThreadRunning[i] = false;
if (grabThreads[i] != null && grabThreads[i].IsAlive)
{
grabThreads[i].Join(1000);
}
if (saveThreads[i] != null && saveThreads[i].IsAlive)
{
saveThreads[i].Join(2000);
}
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);
}
}
}
}