找回密码
 立即注册
首页 业界区 安全 C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈 ...

C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈现。

皆炳 7 天前
1、winform 应用程序,两个picturebox空间,一个用于视频呈现,一个用于抓拍呈现。
2、引用包OpenCvSharp4、OpenCvSharp4.Extensions、OpenCvSharp4.runtime.win等。
  1.   1 // 定义一个部分类 Form2,继承自 Form 类,用于创建一个 Windows 窗体应用程序的窗口
  2.   2 public partial class Form2 : Form
  3.   3 {
  4.   4     // 修改成员变量
  5.   5     // 声明一个 volatile 修饰的 Bitmap 类型变量,用于存储最新的视频帧图像
  6.   6     // volatile 关键字确保该变量在多线程环境下的可见性,避免线程缓存导致的数据不一致问题
  7.   7     private volatile Bitmap _latestFrameBitmap;
  8.   8     // 声明一个用于线程同步的对象,用于对 _latestFrameBitmap 的访问进行加锁操作
  9.   9     private readonly object _bitmapLock = new object();
  10. 10     // 声明一个 VideoCapture 类型的变量,用于捕获摄像头的视频流
  11. 11     private VideoCapture _capture;
  12. 12     // 声明一个 CancellationTokenSource 类型的变量,用于取消异步操作
  13. 13     private CancellationTokenSource _cts;
  14. 14
  15. 15     // 窗体的构造函数,在创建 Form2 实例时自动调用
  16. 16     public Form2()
  17. 17     {
  18. 18         // 调用 Windows 窗体设计器生成的初始化方法,初始化窗体的控件和布局
  19. 19         InitializeComponent();
  20. 20     }
  21. 21
  22. 22     // 窗体加载事件处理方法,当窗体加载完成后自动触发
  23. 23     private async void Form2_Load(object sender, EventArgs e)
  24. 24     {
  25. 25         // 创建一个 VideoCapture 实例,参数 0 表示使用默认的摄像头设备
  26. 26         _capture = new VideoCapture(0);
  27. 27         // 检查摄像头是否成功打开
  28. 28         if (!_capture.IsOpened())
  29. 29         {
  30. 30             // 如果摄像头无法打开,弹出消息框提示用户
  31. 31             MessageBox.Show("无法打开摄像头!");
  32. 32             // 直接返回,不再执行后续的摄像头捕获操作
  33. 33             return;
  34. 34         }
  35. 35
  36. 36         // 创建一个 CancellationTokenSource 实例,用于取消异步操作
  37. 37         _cts = new CancellationTokenSource();
  38. 38         try
  39. 39         {
  40. 40             // 调用异步方法 StartCapturingAsync 开始捕获摄像头的视频流,并传入取消令牌
  41. 41             await StartCapturingAsync(_cts.Token);
  42. 42         }
  43. 43         catch (OperationCanceledException)
  44. 44         {
  45. 45             // 捕获 OperationCanceledException 异常,表示操作被正常取消,不做额外处理
  46. 46         }
  47. 47         catch (Exception ex)
  48. 48         {
  49. 49             // 捕获其他异常,弹出消息框显示异常信息
  50. 50             MessageBox.Show($"捕获出错: {ex.Message}");
  51. 51         }
  52. 52     }
  53. 53
  54. 54     // 异步方法,用于开始捕获摄像头的视频流
  55. 55     private async Task StartCapturingAsync(CancellationToken token)
  56. 56     {
  57. 57         // 使用 using 语句创建一个 Mat 对象,用于存储从摄像头读取的视频帧
  58. 58         // Mat 是 OpenCvSharp 库中用于表示图像矩阵的类,using 语句确保在使用完后自动释放资源
  59. 59         using (var frame = new Mat())
  60. 60         {
  61. 61             // 进入一个循环,只要取消令牌未被触发,就持续捕获视频帧
  62. 62             while (!token.IsCancellationRequested)
  63. 63             {
  64. 64                 // 从摄像头读取一帧视频数据,并存储到 frame 对象中
  65. 65                 _capture.Read(frame);
  66. 66                 // 检查读取的帧是否为空,如果为空则跳过本次循环,继续读取下一帧
  67. 67                 if (frame.Empty()) continue;
  68. 68
  69. 69                 // 将 OpenCvSharp 的 Mat 对象转换为 .NET 的 Bitmap 对象,以便在 Windows 窗体中显示
  70. 70                 var newBitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(frame);
  71. 71
  72. 72                 // 更新最新帧
  73. 73                 // 使用 lock 语句对 _bitmapLock 对象加锁,确保在多线程环境下对 _latestFrameBitmap 的访问是线程安全的
  74. 74                 lock (_bitmapLock)
  75. 75                 {
  76. 76                     // 保存旧的 _latestFrameBitmap 对象
  77. 77                     var old = _latestFrameBitmap;
  78. 78                     // 将新的 Bitmap 对象赋值给 _latestFrameBitmap
  79. 79                     _latestFrameBitmap = newBitmap;
  80. 80                     // 如果旧的 _latestFrameBitmap 对象不为空,则释放其资源
  81. 81                     old?.Dispose();
  82. 82                 }
  83. 83
  84. 84                 // 调用异步方法 UpdateCameraPreviewAsync 更新摄像头预览界面
  85. 85                 await UpdateCameraPreviewAsync(newBitmap);
  86. 86                 // 异步延迟 30 毫秒,控制视频帧的捕获频率
  87. 87                 await Task.Delay(30, token);
  88. 88             }
  89. 89         }
  90. 90     }
  91. 91
  92. 92     // 异步方法,用于更新摄像头预览界面
  93. 93     private async Task UpdateCameraPreviewAsync(Bitmap bitmap)
  94. 94     {
  95. 95         // 检查 picCamera 控件是否已经被释放,如果已经释放则释放传入的 Bitmap 对象并返回
  96. 96         if (picCamera.IsDisposed)
  97. 97         {
  98. 98             bitmap.Dispose();
  99. 99             return;
  100. 100         }
  101. 101
  102. 102         try
  103. 103         {
  104. 104             // 检查当前线程是否需要通过 Invoke 方法在 UI 线程上执行操作
  105. 105             if (picCamera.InvokeRequired)
  106. 106             {
  107. 107                 // 如果需要,则使用 BeginInvoke 方法在 UI 线程上调用 UpdateCamera 方法
  108. 108                 picCamera.BeginInvoke(new Action(() => UpdateCamera(bitmap)));
  109. 109             }
  110. 110             else
  111. 111             {
  112. 112                 // 如果不需要,则直接调用 UpdateCamera 方法
  113. 113                 UpdateCamera(bitmap);
  114. 114             }
  115. 115         }
  116. 116         catch (ObjectDisposedException)
  117. 117         {
  118. 118             // 捕获 ObjectDisposedException 异常,释放传入的 Bitmap 对象
  119. 119             bitmap.Dispose();
  120. 120         }
  121. 121     }
  122. 122
  123. 123     // 方法,用于更新摄像头预览界面
  124. 124     private void UpdateCamera(Bitmap newBitmap)
  125. 125     {
  126. 126         // 检查 picCamera 控件是否已经被释放,如果已经释放则释放传入的 Bitmap 对象并返回
  127. 127         if (picCamera.IsDisposed)
  128. 128         {
  129. 129             newBitmap.Dispose();
  130. 130             return;
  131. 131         }
  132. 132
  133. 133         // 保存 picCamera 控件当前显示的图像
  134. 134         var old = picCamera.Image;
  135. 135         // 将新的 Bitmap 对象赋值给 picCamera 控件的 Image 属性,更新显示的图像
  136. 136         picCamera.Image = newBitmap;
  137. 137         // 如果旧的图像不为空,则释放其资源
  138. 138         old?.Dispose();
  139. 139     }
  140. 140
  141. 141     // 优化后的抓拍方法,当点击抓拍按钮时触发
  142. 142     private async void catchBtn_Click(object sender, EventArgs e)
  143. 143     {
  144. 144         try
  145. 145         {
  146. 146             // 声明一个 Bitmap 类型的变量,用于存储抓拍的图像
  147. 147             Bitmap snapshot = null;
  148. 148
  149. 149             // 安全获取当前帧
  150. 150             // 使用 lock 语句对 _bitmapLock 对象加锁,确保在多线程环境下对 _latestFrameBitmap 的访问是线程安全的
  151. 151             lock (_bitmapLock)
  152. 152             {
  153. 153                 // 检查 _latestFrameBitmap 是否不为空
  154. 154                 if (_latestFrameBitmap != null)
  155. 155                 {
  156. 156                     // 如果不为空,则克隆一份 _latestFrameBitmap 对象赋值给 snapshot 变量
  157. 157                     snapshot = (Bitmap)_latestFrameBitmap.Clone();
  158. 158                 }
  159. 159             }
  160. 160
  161. 161             // 检查 snapshot 是否为空,如果为空则弹出消息框提示用户当前没有可用的视频帧
  162. 162             if (snapshot == null)
  163. 163             {
  164. 164                 MessageBox.Show("当前没有可用的视频帧");
  165. 165                 return;
  166. 166             }
  167. 167
  168. 168             // 异步保存防止界面卡顿
  169. 169             // 调用 Task.Run 方法在后台线程中执行 SaveSnapshot 方法,保存抓拍的图像
  170. 170             await Task.Run(() => SaveSnapshot(snapshot));
  171. 171         }
  172. 172         catch (Exception ex)
  173. 173         {
  174. 174             // 捕获其他异常,弹出消息框显示异常信息
  175. 175             MessageBox.Show($"抓拍失败: {ex.Message}");
  176. 176         }
  177. 177     }
  178. 178
  179. 179     // 方法,用于保存抓拍的图像
  180. 180     private void SaveSnapshot(Bitmap bitmap)
  181. 181     {
  182. 182         try
  183. 183         {
  184. 184             // 调用 GenerateUniqueFileName 方法生成一个唯一的文件名
  185. 185             var fileName = GenerateUniqueFileName();
  186. 186             // 使用 using 语句确保在使用完 bitmap 对象后自动释放资源
  187. 187             using (bitmap)
  188. 188             {
  189. 189                 // 将 bitmap 对象保存为 JPEG 格式的文件
  190. 190                 bitmap.Save(fileName, ImageFormat.Jpeg);
  191. 191
  192. 192                 // 显示预览(需要克隆新实例)
  193. 193                 // 克隆一份 bitmap 对象用于在界面上显示预览
  194. 194                 var previewBitmap = (Bitmap)bitmap.Clone();
  195. 195
  196. 196                 // 使用 BeginInvoke 方法在 UI 线程上执行更新预览界面和显示保存成功消息框的操作
  197. 197                 BeginInvoke(new Action(() =>
  198. 198                 {
  199. 199                     // 调用 UpdateSnapshotPreview 方法更新抓拍图像的预览界面
  200. 200                     UpdateSnapshotPreview(previewBitmap);
  201. 201                     // 弹出消息框显示图像保存的路径
  202. 202                     MessageBox.Show($"图片已保存到:\n{fileName}");
  203. 203                 }));
  204. 204             }
  205. 205         }
  206. 206         catch (Exception ex)
  207. 207         {
  208. 208             // 捕获其他异常,使用 BeginInvoke 方法在 UI 线程上弹出消息框显示异常信息
  209. 209             BeginInvoke(new Action(() =>
  210. 210             {
  211. 211                 MessageBox.Show($"保存失败: {ex.Message}");
  212. 212             }));
  213. 213         }
  214. 214     }
  215. 215
  216. 216     // 新增预览更新方法,用于更新抓拍图像的预览界面
  217. 217     private void UpdateSnapshotPreview(Bitmap newBitmap)
  218. 218     {
  219. 219         // 检查 pictureBoxSnapshot 控件是否已经被释放,如果已经释放则释放传入的 Bitmap 对象并返回
  220. 220         if (pictureBoxSnapshot.IsDisposed)
  221. 221         {
  222. 222             newBitmap.Dispose();
  223. 223             return;
  224. 224         }
  225. 225
  226. 226         // 处理跨线程访问
  227. 227         // 检查当前线程是否需要通过 Invoke 方法在 UI 线程上执行操作
  228. 228         if (pictureBoxSnapshot.InvokeRequired)
  229. 229         {
  230. 230             // 如果需要,则使用 BeginInvoke 方法在 UI 线程上递归调用 UpdateSnapshotPreview 方法
  231. 231             pictureBoxSnapshot.BeginInvoke(new Action(() => UpdateSnapshotPreview(newBitmap)));
  232. 232             return;
  233. 233         }
  234. 234
  235. 235         // 更新控件并释放旧资源
  236. 236         // 保存 pictureBoxSnapshot 控件当前显示的图像
  237. 237         var old = pictureBoxSnapshot.Image;
  238. 238         // 将新的 Bitmap 对象赋值给 pictureBoxSnapshot 控件的 Image 属性,更新显示的图像
  239. 239         pictureBoxSnapshot.Image = newBitmap;
  240. 240         // 如果旧的图像不为空,则释放其资源
  241. 241         old?.Dispose();
  242. 242     }
  243. 243
  244. 244     // 方法,用于生成一个唯一的文件名
  245. 245     private string GenerateUniqueFileName()
  246. 246     {
  247. 247         // 获取用户的图片文件夹路径
  248. 248         var docs = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
  249. 249         // 获取当前时间并格式化为指定的字符串格式
  250. 250         var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmssfff");
  251. 251         // 组合图片文件夹路径、文件名前缀和时间戳,生成一个唯一的文件名
  252. 252         return Path.Combine(docs, $"Snapshot_{timestamp}.jpg");
  253. 253     }
  254. 254
  255. 255     // 窗体关闭事件处理方法,当窗体关闭时自动触发
  256. 256     private void Form2_FormClosing(object sender, FormClosingEventArgs e)
  257. 257     {
  258. 258         // 取消异步操作
  259. 259         _cts?.Cancel();
  260. 260         // 释放 CancellationTokenSource 对象的资源
  261. 261         _cts?.Dispose();
  262. 262
  263. 263         // 释放所有资源
  264. 264         // 释放 VideoCapture 对象的资源
  265. 265         _capture?.Dispose();
  266. 266         // 使用 lock 语句对 _bitmapLock 对象加锁,确保在多线程环境下对 _latestFrameBitmap 的访问是线程安全的
  267. 267         lock (_bitmapLock)
  268. 268         {
  269. 269             // 释放 _latestFrameBitmap 对象的资源
  270. 270             _latestFrameBitmap?.Dispose();
  271. 271         }
  272. 272
  273. 273         // 清理预览图
  274. 274         // 检查 picCamera 控件的 Image 属性是否不为空
  275. 275         if (picCamera.Image != null)
  276. 276         {
  277. 277             // 保存 picCamera 控件当前显示的图像
  278. 278             var img = picCamera.Image;
  279. 279             // 将 picCamera 控件的 Image 属性设置为 null
  280. 280             picCamera.Image = null;
  281. 281             // 释放旧的图像资源
  282. 282             img.Dispose();
  283. 283         }
  284. 284
  285. 285         // 新增快照预览清理
  286. 286         // 检查 pictureBoxSnapshot 控件的 Image 属性是否不为空
  287. 287         if (pictureBoxSnapshot.Image != null)
  288. 288         {
  289. 289             // 保存 pictureBoxSnapshot 控件当前显示的图像
  290. 290             var img = pictureBoxSnapshot.Image;
  291. 291             // 将 pictureBoxSnapshot 控件的 Image 属性设置为 null
  292. 292             pictureBoxSnapshot.Image = null;
  293. 293             // 释放旧的图像资源
  294. 294             img.Dispose();
  295. 295         }
  296. 296     }
  297. 297 }
复制代码
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册