C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈现。
生活随笔
收集整理的這篇文章主要介紹了
C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈现。
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
1、winform 應(yīng)用程序,兩個(gè)picturebox空間,一個(gè)用于視頻呈現(xiàn),一個(gè)用于抓拍呈現(xiàn)。
2、引用包OpenCvSharp4、OpenCvSharp4.Extensions、OpenCvSharp4.runtime.win等。
1 // 定義一個(gè)部分類 Form2,繼承自 Form 類,用于創(chuàng)建一個(gè) Windows 窗體應(yīng)用程序的窗口
2 public partial class Form2 : Form
3 {
4 // 修改成員變量
5 // 聲明一個(gè) volatile 修飾的 Bitmap 類型變量,用于存儲(chǔ)最新的視頻幀圖像
6 // volatile 關(guān)鍵字確保該變量在多線程環(huán)境下的可見性,避免線程緩存導(dǎo)致的數(shù)據(jù)不一致問(wèn)題
7 private volatile Bitmap _latestFrameBitmap;
8 // 聲明一個(gè)用于線程同步的對(duì)象,用于對(duì) _latestFrameBitmap 的訪問(wèn)進(jìn)行加鎖操作
9 private readonly object _bitmapLock = new object();
10 // 聲明一個(gè) VideoCapture 類型的變量,用于捕獲攝像頭的視頻流
11 private VideoCapture _capture;
12 // 聲明一個(gè) CancellationTokenSource 類型的變量,用于取消異步操作
13 private CancellationTokenSource _cts;
14
15 // 窗體的構(gòu)造函數(shù),在創(chuàng)建 Form2 實(shí)例時(shí)自動(dòng)調(diào)用
16 public Form2()
17 {
18 // 調(diào)用 Windows 窗體設(shè)計(jì)器生成的初始化方法,初始化窗體的控件和布局
19 InitializeComponent();
20 }
21
22 // 窗體加載事件處理方法,當(dāng)窗體加載完成后自動(dòng)觸發(fā)
23 private async void Form2_Load(object sender, EventArgs e)
24 {
25 // 創(chuàng)建一個(gè) VideoCapture 實(shí)例,參數(shù) 0 表示使用默認(rèn)的攝像頭設(shè)備
26 _capture = new VideoCapture(0);
27 // 檢查攝像頭是否成功打開
28 if (!_capture.IsOpened())
29 {
30 // 如果攝像頭無(wú)法打開,彈出消息框提示用戶
31 MessageBox.Show("無(wú)法打開攝像頭!");
32 // 直接返回,不再執(zhí)行后續(xù)的攝像頭捕獲操作
33 return;
34 }
35
36 // 創(chuàng)建一個(gè) CancellationTokenSource 實(shí)例,用于取消異步操作
37 _cts = new CancellationTokenSource();
38 try
39 {
40 // 調(diào)用異步方法 StartCapturingAsync 開始捕獲攝像頭的視頻流,并傳入取消令牌
41 await StartCapturingAsync(_cts.Token);
42 }
43 catch (OperationCanceledException)
44 {
45 // 捕獲 OperationCanceledException 異常,表示操作被正常取消,不做額外處理
46 }
47 catch (Exception ex)
48 {
49 // 捕獲其他異常,彈出消息框顯示異常信息
50 MessageBox.Show($"捕獲出錯(cuò): {ex.Message}");
51 }
52 }
53
54 // 異步方法,用于開始捕獲攝像頭的視頻流
55 private async Task StartCapturingAsync(CancellationToken token)
56 {
57 // 使用 using 語(yǔ)句創(chuàng)建一個(gè) Mat 對(duì)象,用于存儲(chǔ)從攝像頭讀取的視頻幀
58 // Mat 是 OpenCvSharp 庫(kù)中用于表示圖像矩陣的類,using 語(yǔ)句確保在使用完后自動(dòng)釋放資源
59 using (var frame = new Mat())
60 {
61 // 進(jìn)入一個(gè)循環(huán),只要取消令牌未被觸發(fā),就持續(xù)捕獲視頻幀
62 while (!token.IsCancellationRequested)
63 {
64 // 從攝像頭讀取一幀視頻數(shù)據(jù),并存儲(chǔ)到 frame 對(duì)象中
65 _capture.Read(frame);
66 // 檢查讀取的幀是否為空,如果為空則跳過(guò)本次循環(huán),繼續(xù)讀取下一幀
67 if (frame.Empty()) continue;
68
69 // 將 OpenCvSharp 的 Mat 對(duì)象轉(zhuǎn)換為 .NET 的 Bitmap 對(duì)象,以便在 Windows 窗體中顯示
70 var newBitmap = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(frame);
71
72 // 更新最新幀
73 // 使用 lock 語(yǔ)句對(duì) _bitmapLock 對(duì)象加鎖,確保在多線程環(huán)境下對(duì) _latestFrameBitmap 的訪問(wèn)是線程安全的
74 lock (_bitmapLock)
75 {
76 // 保存舊的 _latestFrameBitmap 對(duì)象
77 var old = _latestFrameBitmap;
78 // 將新的 Bitmap 對(duì)象賦值給 _latestFrameBitmap
79 _latestFrameBitmap = newBitmap;
80 // 如果舊的 _latestFrameBitmap 對(duì)象不為空,則釋放其資源
81 old?.Dispose();
82 }
83
84 // 調(diào)用異步方法 UpdateCameraPreviewAsync 更新攝像頭預(yù)覽界面
85 await UpdateCameraPreviewAsync(newBitmap);
86 // 異步延遲 30 毫秒,控制視頻幀的捕獲頻率
87 await Task.Delay(30, token);
88 }
89 }
90 }
91
92 // 異步方法,用于更新攝像頭預(yù)覽界面
93 private async Task UpdateCameraPreviewAsync(Bitmap bitmap)
94 {
95 // 檢查 picCamera 控件是否已經(jīng)被釋放,如果已經(jīng)釋放則釋放傳入的 Bitmap 對(duì)象并返回
96 if (picCamera.IsDisposed)
97 {
98 bitmap.Dispose();
99 return;
100 }
101
102 try
103 {
104 // 檢查當(dāng)前線程是否需要通過(guò) Invoke 方法在 UI 線程上執(zhí)行操作
105 if (picCamera.InvokeRequired)
106 {
107 // 如果需要,則使用 BeginInvoke 方法在 UI 線程上調(diào)用 UpdateCamera 方法
108 picCamera.BeginInvoke(new Action(() => UpdateCamera(bitmap)));
109 }
110 else
111 {
112 // 如果不需要,則直接調(diào)用 UpdateCamera 方法
113 UpdateCamera(bitmap);
114 }
115 }
116 catch (ObjectDisposedException)
117 {
118 // 捕獲 ObjectDisposedException 異常,釋放傳入的 Bitmap 對(duì)象
119 bitmap.Dispose();
120 }
121 }
122
123 // 方法,用于更新攝像頭預(yù)覽界面
124 private void UpdateCamera(Bitmap newBitmap)
125 {
126 // 檢查 picCamera 控件是否已經(jīng)被釋放,如果已經(jīng)釋放則釋放傳入的 Bitmap 對(duì)象并返回
127 if (picCamera.IsDisposed)
128 {
129 newBitmap.Dispose();
130 return;
131 }
132
133 // 保存 picCamera 控件當(dāng)前顯示的圖像
134 var old = picCamera.Image;
135 // 將新的 Bitmap 對(duì)象賦值給 picCamera 控件的 Image 屬性,更新顯示的圖像
136 picCamera.Image = newBitmap;
137 // 如果舊的圖像不為空,則釋放其資源
138 old?.Dispose();
139 }
140
141 // 優(yōu)化后的抓拍方法,當(dāng)點(diǎn)擊抓拍按鈕時(shí)觸發(fā)
142 private async void catchBtn_Click(object sender, EventArgs e)
143 {
144 try
145 {
146 // 聲明一個(gè) Bitmap 類型的變量,用于存儲(chǔ)抓拍的圖像
147 Bitmap snapshot = null;
148
149 // 安全獲取當(dāng)前幀
150 // 使用 lock 語(yǔ)句對(duì) _bitmapLock 對(duì)象加鎖,確保在多線程環(huán)境下對(duì) _latestFrameBitmap 的訪問(wèn)是線程安全的
151 lock (_bitmapLock)
152 {
153 // 檢查 _latestFrameBitmap 是否不為空
154 if (_latestFrameBitmap != null)
155 {
156 // 如果不為空,則克隆一份 _latestFrameBitmap 對(duì)象賦值給 snapshot 變量
157 snapshot = (Bitmap)_latestFrameBitmap.Clone();
158 }
159 }
160
161 // 檢查 snapshot 是否為空,如果為空則彈出消息框提示用戶當(dāng)前沒有可用的視頻幀
162 if (snapshot == null)
163 {
164 MessageBox.Show("當(dāng)前沒有可用的視頻幀");
165 return;
166 }
167
168 // 異步保存防止界面卡頓
169 // 調(diào)用 Task.Run 方法在后臺(tái)線程中執(zhí)行 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 // 調(diào)用 GenerateUniqueFileName 方法生成一個(gè)唯一的文件名
185 var fileName = GenerateUniqueFileName();
186 // 使用 using 語(yǔ)句確保在使用完 bitmap 對(duì)象后自動(dòng)釋放資源
187 using (bitmap)
188 {
189 // 將 bitmap 對(duì)象保存為 JPEG 格式的文件
190 bitmap.Save(fileName, ImageFormat.Jpeg);
191
192 // 顯示預(yù)覽(需要克隆新實(shí)例)
193 // 克隆一份 bitmap 對(duì)象用于在界面上顯示預(yù)覽
194 var previewBitmap = (Bitmap)bitmap.Clone();
195
196 // 使用 BeginInvoke 方法在 UI 線程上執(zhí)行更新預(yù)覽界面和顯示保存成功消息框的操作
197 BeginInvoke(new Action(() =>
198 {
199 // 調(diào)用 UpdateSnapshotPreview 方法更新抓拍圖像的預(yù)覽界面
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 // 新增預(yù)覽更新方法,用于更新抓拍圖像的預(yù)覽界面
217 private void UpdateSnapshotPreview(Bitmap newBitmap)
218 {
219 // 檢查 pictureBoxSnapshot 控件是否已經(jīng)被釋放,如果已經(jīng)釋放則釋放傳入的 Bitmap 對(duì)象并返回
220 if (pictureBoxSnapshot.IsDisposed)
221 {
222 newBitmap.Dispose();
223 return;
224 }
225
226 // 處理跨線程訪問(wèn)
227 // 檢查當(dāng)前線程是否需要通過(guò) Invoke 方法在 UI 線程上執(zhí)行操作
228 if (pictureBoxSnapshot.InvokeRequired)
229 {
230 // 如果需要,則使用 BeginInvoke 方法在 UI 線程上遞歸調(diào)用 UpdateSnapshotPreview 方法
231 pictureBoxSnapshot.BeginInvoke(new Action(() => UpdateSnapshotPreview(newBitmap)));
232 return;
233 }
234
235 // 更新控件并釋放舊資源
236 // 保存 pictureBoxSnapshot 控件當(dāng)前顯示的圖像
237 var old = pictureBoxSnapshot.Image;
238 // 將新的 Bitmap 對(duì)象賦值給 pictureBoxSnapshot 控件的 Image 屬性,更新顯示的圖像
239 pictureBoxSnapshot.Image = newBitmap;
240 // 如果舊的圖像不為空,則釋放其資源
241 old?.Dispose();
242 }
243
244 // 方法,用于生成一個(gè)唯一的文件名
245 private string GenerateUniqueFileName()
246 {
247 // 獲取用戶的圖片文件夾路徑
248 var docs = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
249 // 獲取當(dāng)前時(shí)間并格式化為指定的字符串格式
250 var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmssfff");
251 // 組合圖片文件夾路徑、文件名前綴和時(shí)間戳,生成一個(gè)唯一的文件名
252 return Path.Combine(docs, $"Snapshot_{timestamp}.jpg");
253 }
254
255 // 窗體關(guān)閉事件處理方法,當(dāng)窗體關(guān)閉時(shí)自動(dòng)觸發(fā)
256 private void Form2_FormClosing(object sender, FormClosingEventArgs e)
257 {
258 // 取消異步操作
259 _cts?.Cancel();
260 // 釋放 CancellationTokenSource 對(duì)象的資源
261 _cts?.Dispose();
262
263 // 釋放所有資源
264 // 釋放 VideoCapture 對(duì)象的資源
265 _capture?.Dispose();
266 // 使用 lock 語(yǔ)句對(duì) _bitmapLock 對(duì)象加鎖,確保在多線程環(huán)境下對(duì) _latestFrameBitmap 的訪問(wèn)是線程安全的
267 lock (_bitmapLock)
268 {
269 // 釋放 _latestFrameBitmap 對(duì)象的資源
270 _latestFrameBitmap?.Dispose();
271 }
272
273 // 清理預(yù)覽圖
274 // 檢查 picCamera 控件的 Image 屬性是否不為空
275 if (picCamera.Image != null)
276 {
277 // 保存 picCamera 控件當(dāng)前顯示的圖像
278 var img = picCamera.Image;
279 // 將 picCamera 控件的 Image 屬性設(shè)置為 null
280 picCamera.Image = null;
281 // 釋放舊的圖像資源
282 img.Dispose();
283 }
284
285 // 新增快照預(yù)覽清理
286 // 檢查 pictureBoxSnapshot 控件的 Image 屬性是否不為空
287 if (pictureBoxSnapshot.Image != null)
288 {
289 // 保存 pictureBoxSnapshot 控件當(dāng)前顯示的圖像
290 var img = pictureBoxSnapshot.Image;
291 // 將 pictureBoxSnapshot 控件的 Image 屬性設(shè)置為 null
292 pictureBoxSnapshot.Image = null;
293 // 釋放舊的圖像資源
294 img.Dispose();
295 }
296 }
297 }
總結(jié)
以上是生活随笔為你收集整理的C# USB 摄像头 OpenCV 视频picBox呈现,抓拍图像保存呈现。的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python之面向对象继承和派生
- 下一篇: 安卓线性布局LinearLayout