登录
/
注册
首页
论坛
其它
首页
科技
业界
安全
程序
广播
Follow
园子
关于
博客
发1篇日志+1圆
记录
发1条记录+2圆币
发帖说明
登录
/
注册
账号
自动登录
找回密码
密码
登录
立即注册
搜索
搜索
关闭
CSDN热搜
程序园
精品问答
技术交流
资源下载
本版
帖子
用户
软件
问答
教程
代码
VIP申请
网盘
联系我们
道具
勋章
任务
设置
我的收藏
退出
腾讯QQ
微信登录
返回列表
首页
›
业界区
›
安全
›
C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈 ...
C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈现。
[ 复制链接 ]
皆炳
7 天前
1、winform 应用程序,两个picturebox空间,一个用于视频呈现,一个用于抓拍呈现。
2、引用包OpenCvSharp4、OpenCvSharp4.Extensions、OpenCvSharp4.runtime.win等。
1 // 定义一个部分类 Form2,继承自 Form 类,用于创建一个 Windows 窗体应用程序的窗口
2 public partial class Form2 : Form
3 {
4 // 修改成员变量
5 // 声明一个 volatile 修饰的 Bitmap 类型变量,用于存储最新的视频帧图像
6 // volatile 关键字确保该变量在多线程环境下的可见性,避免线程缓存导致的数据不一致问题
7 private volatile Bitmap _latestFrameBitmap;
8 // 声明一个用于线程同步的对象,用于对 _latestFrameBitmap 的访问进行加锁操作
9 private readonly object _bitmapLock = new object();
10 // 声明一个 VideoCapture 类型的变量,用于捕获摄像头的视频流
11 private VideoCapture _capture;
12 // 声明一个 CancellationTokenSource 类型的变量,用于取消异步操作
13 private CancellationTokenSource _cts;
14
15 // 窗体的构造函数,在创建 Form2 实例时自动调用
16 public Form2()
17 {
18 // 调用 Windows 窗体设计器生成的初始化方法,初始化窗体的控件和布局
19 InitializeComponent();
20 }
21
22 // 窗体加载事件处理方法,当窗体加载完成后自动触发
23 private async void Form2_Load(object sender, EventArgs e)
24 {
25 // 创建一个 VideoCapture 实例,参数 0 表示使用默认的摄像头设备
26 _capture = new VideoCapture(0);
27 // 检查摄像头是否成功打开
28 if (!_capture.IsOpened())
29 {
30 // 如果摄像头无法打开,弹出消息框提示用户
31 MessageBox.Show("无法打开摄像头!");
32 // 直接返回,不再执行后续的摄像头捕获操作
33 return;
34 }
35
36 // 创建一个 CancellationTokenSource 实例,用于取消异步操作
37 _cts = new CancellationTokenSource();
38 try
39 {
40 // 调用异步方法 StartCapturingAsync 开始捕获摄像头的视频流,并传入取消令牌
41 await StartCapturingAsync(_cts.Token);
42 }
43 catch (OperationCanceledException)
44 {
45 // 捕获 OperationCanceledException 异常,表示操作被正常取消,不做额外处理
46 }
47 catch (Exception ex)
48 {
49 // 捕获其他异常,弹出消息框显示异常信息
50 MessageBox.Show($"捕获出错: {ex.Message}");
51 }
52 }
53
54 // 异步方法,用于开始捕获摄像头的视频流
55 private async Task StartCapturingAsync(CancellationToken token)
56 {
57 // 使用 using 语句创建一个 Mat 对象,用于存储从摄像头读取的视频帧
58 // Mat 是 OpenCvSharp 库中用于表示图像矩阵的类,using 语句确保在使用完后自动释放资源
59 using (var frame = new Mat())
60 {
61 // 进入一个循环,只要取消令牌未被触发,就持续捕获视频帧
62 while (!token.IsCancellationRequested)
63 {
64 // 从摄像头读取一帧视频数据,并存储到 frame 对象中
65 _capture.Read(frame);
66 // 检查读取的帧是否为空,如果为空则跳过本次循环,继续读取下一帧
67 if (frame.Empty()) continue;
68
69 // 将 OpenCvSharp 的 Mat 对象转换为 .NET 的 Bitmap 对象,以便在 Windows 窗体中显示
70 var newBitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(frame);
71
72 // 更新最新帧
73 // 使用 lock 语句对 _bitmapLock 对象加锁,确保在多线程环境下对 _latestFrameBitmap 的访问是线程安全的
74 lock (_bitmapLock)
75 {
76 // 保存旧的 _latestFrameBitmap 对象
77 var old = _latestFrameBitmap;
78 // 将新的 Bitmap 对象赋值给 _latestFrameBitmap
79 _latestFrameBitmap = newBitmap;
80 // 如果旧的 _latestFrameBitmap 对象不为空,则释放其资源
81 old?.Dispose();
82 }
83
84 // 调用异步方法 UpdateCameraPreviewAsync 更新摄像头预览界面
85 await UpdateCameraPreviewAsync(newBitmap);
86 // 异步延迟 30 毫秒,控制视频帧的捕获频率
87 await Task.Delay(30, token);
88 }
89 }
90 }
91
92 // 异步方法,用于更新摄像头预览界面
93 private async Task UpdateCameraPreviewAsync(Bitmap bitmap)
94 {
95 // 检查 picCamera 控件是否已经被释放,如果已经释放则释放传入的 Bitmap 对象并返回
96 if (picCamera.IsDisposed)
97 {
98 bitmap.Dispose();
99 return;
100 }
101
102 try
103 {
104 // 检查当前线程是否需要通过 Invoke 方法在 UI 线程上执行操作
105 if (picCamera.InvokeRequired)
106 {
107 // 如果需要,则使用 BeginInvoke 方法在 UI 线程上调用 UpdateCamera 方法
108 picCamera.BeginInvoke(new Action(() => UpdateCamera(bitmap)));
109 }
110 else
111 {
112 // 如果不需要,则直接调用 UpdateCamera 方法
113 UpdateCamera(bitmap);
114 }
115 }
116 catch (ObjectDisposedException)
117 {
118 // 捕获 ObjectDisposedException 异常,释放传入的 Bitmap 对象
119 bitmap.Dispose();
120 }
121 }
122
123 // 方法,用于更新摄像头预览界面
124 private void UpdateCamera(Bitmap newBitmap)
125 {
126 // 检查 picCamera 控件是否已经被释放,如果已经释放则释放传入的 Bitmap 对象并返回
127 if (picCamera.IsDisposed)
128 {
129 newBitmap.Dispose();
130 return;
131 }
132
133 // 保存 picCamera 控件当前显示的图像
134 var old = picCamera.Image;
135 // 将新的 Bitmap 对象赋值给 picCamera 控件的 Image 属性,更新显示的图像
136 picCamera.Image = newBitmap;
137 // 如果旧的图像不为空,则释放其资源
138 old?.Dispose();
139 }
140
141 // 优化后的抓拍方法,当点击抓拍按钮时触发
142 private async void catchBtn_Click(object sender, EventArgs e)
143 {
144 try
145 {
146 // 声明一个 Bitmap 类型的变量,用于存储抓拍的图像
147 Bitmap snapshot = null;
148
149 // 安全获取当前帧
150 // 使用 lock 语句对 _bitmapLock 对象加锁,确保在多线程环境下对 _latestFrameBitmap 的访问是线程安全的
151 lock (_bitmapLock)
152 {
153 // 检查 _latestFrameBitmap 是否不为空
154 if (_latestFrameBitmap != null)
155 {
156 // 如果不为空,则克隆一份 _latestFrameBitmap 对象赋值给 snapshot 变量
157 snapshot = (Bitmap)_latestFrameBitmap.Clone();
158 }
159 }
160
161 // 检查 snapshot 是否为空,如果为空则弹出消息框提示用户当前没有可用的视频帧
162 if (snapshot == null)
163 {
164 MessageBox.Show("当前没有可用的视频帧");
165 return;
166 }
167
168 // 异步保存防止界面卡顿
169 // 调用 Task.Run 方法在后台线程中执行 SaveSnapshot 方法,保存抓拍的图像
170 await Task.Run(() => SaveSnapshot(snapshot));
171 }
172 catch (Exception ex)
173 {
174 // 捕获其他异常,弹出消息框显示异常信息
175 MessageBox.Show($"抓拍失败: {ex.Message}");
176 }
177 }
178
179 // 方法,用于保存抓拍的图像
180 private void SaveSnapshot(Bitmap bitmap)
181 {
182 try
183 {
184 // 调用 GenerateUniqueFileName 方法生成一个唯一的文件名
185 var fileName = GenerateUniqueFileName();
186 // 使用 using 语句确保在使用完 bitmap 对象后自动释放资源
187 using (bitmap)
188 {
189 // 将 bitmap 对象保存为 JPEG 格式的文件
190 bitmap.Save(fileName, ImageFormat.Jpeg);
191
192 // 显示预览(需要克隆新实例)
193 // 克隆一份 bitmap 对象用于在界面上显示预览
194 var previewBitmap = (Bitmap)bitmap.Clone();
195
196 // 使用 BeginInvoke 方法在 UI 线程上执行更新预览界面和显示保存成功消息框的操作
197 BeginInvoke(new Action(() =>
198 {
199 // 调用 UpdateSnapshotPreview 方法更新抓拍图像的预览界面
200 UpdateSnapshotPreview(previewBitmap);
201 // 弹出消息框显示图像保存的路径
202 MessageBox.Show($"图片已保存到:\n{fileName}");
203 }));
204 }
205 }
206 catch (Exception ex)
207 {
208 // 捕获其他异常,使用 BeginInvoke 方法在 UI 线程上弹出消息框显示异常信息
209 BeginInvoke(new Action(() =>
210 {
211 MessageBox.Show($"保存失败: {ex.Message}");
212 }));
213 }
214 }
215
216 // 新增预览更新方法,用于更新抓拍图像的预览界面
217 private void UpdateSnapshotPreview(Bitmap newBitmap)
218 {
219 // 检查 pictureBoxSnapshot 控件是否已经被释放,如果已经释放则释放传入的 Bitmap 对象并返回
220 if (pictureBoxSnapshot.IsDisposed)
221 {
222 newBitmap.Dispose();
223 return;
224 }
225
226 // 处理跨线程访问
227 // 检查当前线程是否需要通过 Invoke 方法在 UI 线程上执行操作
228 if (pictureBoxSnapshot.InvokeRequired)
229 {
230 // 如果需要,则使用 BeginInvoke 方法在 UI 线程上递归调用 UpdateSnapshotPreview 方法
231 pictureBoxSnapshot.BeginInvoke(new Action(() => UpdateSnapshotPreview(newBitmap)));
232 return;
233 }
234
235 // 更新控件并释放旧资源
236 // 保存 pictureBoxSnapshot 控件当前显示的图像
237 var old = pictureBoxSnapshot.Image;
238 // 将新的 Bitmap 对象赋值给 pictureBoxSnapshot 控件的 Image 属性,更新显示的图像
239 pictureBoxSnapshot.Image = newBitmap;
240 // 如果旧的图像不为空,则释放其资源
241 old?.Dispose();
242 }
243
244 // 方法,用于生成一个唯一的文件名
245 private string GenerateUniqueFileName()
246 {
247 // 获取用户的图片文件夹路径
248 var docs = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
249 // 获取当前时间并格式化为指定的字符串格式
250 var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmssfff");
251 // 组合图片文件夹路径、文件名前缀和时间戳,生成一个唯一的文件名
252 return Path.Combine(docs, $"Snapshot_{timestamp}.jpg");
253 }
254
255 // 窗体关闭事件处理方法,当窗体关闭时自动触发
256 private void Form2_FormClosing(object sender, FormClosingEventArgs e)
257 {
258 // 取消异步操作
259 _cts?.Cancel();
260 // 释放 CancellationTokenSource 对象的资源
261 _cts?.Dispose();
262
263 // 释放所有资源
264 // 释放 VideoCapture 对象的资源
265 _capture?.Dispose();
266 // 使用 lock 语句对 _bitmapLock 对象加锁,确保在多线程环境下对 _latestFrameBitmap 的访问是线程安全的
267 lock (_bitmapLock)
268 {
269 // 释放 _latestFrameBitmap 对象的资源
270 _latestFrameBitmap?.Dispose();
271 }
272
273 // 清理预览图
274 // 检查 picCamera 控件的 Image 属性是否不为空
275 if (picCamera.Image != null)
276 {
277 // 保存 picCamera 控件当前显示的图像
278 var img = picCamera.Image;
279 // 将 picCamera 控件的 Image 属性设置为 null
280 picCamera.Image = null;
281 // 释放旧的图像资源
282 img.Dispose();
283 }
284
285 // 新增快照预览清理
286 // 检查 pictureBoxSnapshot 控件的 Image 属性是否不为空
287 if (pictureBoxSnapshot.Image != null)
288 {
289 // 保存 pictureBoxSnapshot 控件当前显示的图像
290 var img = pictureBoxSnapshot.Image;
291 // 将 pictureBoxSnapshot 控件的 Image 属性设置为 null
292 pictureBoxSnapshot.Image = null;
293 // 释放旧的图像资源
294 img.Dispose();
295 }
296 }
297 }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复
使用道具
举报
提升卡
置顶卡
沉默卡
喧嚣卡
变色卡
千斤顶
照妖镜
相关推荐
如何优雅上线、下线?原来 大厂应用 是这样 优雅发布的!
在 .NET 中将 EF Core 升级到 9.0.5 MySQL 连接提示 get_LockReleaseBehavior
开源情报中批判性思维因人工智能而逐渐衰落
sshd服务
【鸿蒙生态学堂03】应用程序框架基础
AppFreeze与资源泄漏能力开放及常见问题定位方法介绍
人工智能将如何影响语言的演变?
微软又退出中国。。。
【UAP】使用 .NET Core App 编写 UAP
【一步步开发AI运动APP】一、写在最前
HarmonyOS 5.0 分布式数据协同与跨设备同步
MOSN(Modular Open Smart Network)是一款主要使用 Go 语言开发的云原生网络代理平台
不是哥们,26 岁程序员,去种头发了?
在Ubuntu中部署.NET 8 Minimal WebAPI项目
Datawhale速通百炼RAG应用-Task1
记录---前端图像五兄弟:网络 URL、Base64、Blob、ArrayBuffer、本地路径,全整明白!
华为云昇腾专区重磅上线!带你入门昇腾AI技术与DeepSeek实践
JavaScript基础
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
|
立即注册
回复
本版积分规则
回帖并转播
回帖后跳转到最后一页
签约作者
程序园优秀签约作者
发帖
皆炳
7 天前
关注
0
粉丝关注
13
主题发布
板块介绍填写区域,请于后台编辑
财富榜{圆}
敖可
9988
森萌黠
9996
堵赫然
9996
4
凶契帽
9996
5
处匈跑
9996
6
柴古香
9996
7
背竽
9996
8
恐肩
9994
9
里豳朝
9994
10
上官银柳
9994
查看更多