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[] imagePaths = new List[MAX_CAMERAS]; // 临时保存图片路径 private int[] frameCounters = new int[MAX_CAMERAS]; // 帧计数器 // 添加:帧队列用于异步保存(可选) private ConcurrentQueue[] frameQueues = new ConcurrentQueue[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(); frameQueues[i] = new ConcurrentQueue(); } // 异步初始化相机,不阻塞窗体显示 this.Shown += (s, e) => { Task.Run(() => InitializeCameras()); }; } /// /// 初始化UI界面 /// 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 initTasks = new List(); 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))); } } /// /// 启动状态检查线程 /// private void StartStatusCheckThread() { _statusCheckThread = new Thread(CheckRunStatusLoop) { IsBackground = true }; _statusCheckThread.Start(); } /// /// 状态检查循环 - 添加异常处理 /// 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 { // 忽略异常 } } } /// /// 开始所有相机采集 /// private void StartAllCamerasCapture() { if (!_camerasInitialized) return; UpdateSystemStatus("测试开始,正在启动相机采集..."); for (int i = 0; i < MAX_CAMERAS; i++) { if (cameras[i] != null && !grabbingFlags[i]) { StartSingleCameraCapture(i); } } UpdateSystemStatus("所有相机已开始采集"); } /// /// 停止所有相机采集 /// 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(); 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); } } /// /// 更新录制状态显示 /// 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; } } /// /// 启动保存线程(使用队列方式保存图片) /// 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(); } /// /// 保存图片线程处理 /// 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; } /// /// 检查相机是否有效 /// 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; } } /// /// 判断是否为单色像素格式 /// 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; } } /// /// 创建视频文件并返回视频路径 /// 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; } } /// /// 在录制根目录创建视频文件的快捷方式(文本文件) /// 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}"); } } /// /// 创建转换批处理文件 /// 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}"); } } } /// /// 清理相机资源 /// 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(); } } /// /// 更新相机状态显示 /// 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; } } /// /// 更新系统状态 /// 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); } } } }