-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathWndMain.cs
More file actions
499 lines (448 loc) · 19.1 KB
/
WndMain.cs
File metadata and controls
499 lines (448 loc) · 19.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
/*
* OpenLuckyRandom by @WhatDamon
* Licensed under Apache License 2.0
* Copyright © 2024-present Damon Lu
* https://github.com/WhatDamon/OpenLuckyRandom
*/
using OpenCvSharp;
using OpenCvSharp.Extensions;
using Serilog;
using System.Diagnostics;
using System.Reflection;
namespace OpenLuckyRandom
{
public partial class WndMain : Form
{
// 初始化
private VideoCapture capture;
private Mat frame = new Mat();
private CascadeClassifier faceCascade;
private int detectionInterval = 3;
private int lastDetectionFrameIndex = -1;
private int frameIndex = 0;
private int nextIndex = 0;
private int frameThickness = 6;
private readonly string[] cascadeFiles = {
"lbpcascade_frontalface.xml",
"lbpcascade_frontalface_improved.xml",
"haarcascade_frontalface_default.xml",
"haarcascade_frontalface_alt.xml",
"haarcascade_frontalface_alt2.xml"
};
private Dictionary<int, Rect> indexedFaceRectangles = new Dictionary<int, Rect>();
public WndMain()
{
// 窗体初始化
InitializeComponent();
Log.Verbose("Finished Initialize WndMain");
// 组件值定义
frameThicknessNum.Value = frameThickness;
cascadesComboBox.SelectedIndex = 0;
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);
// 设置窗体标题
this.Text = $"OpenLuckyRandom {Assembly.GetExecutingAssembly().GetName().Version} ({Misc.Runtime.GetArch()}) {(Misc.Runtime.IsDebug() ? "[Debug]" : "")}";
Log.Verbose($"Setting WndMain Title to {this.Text}");
// 摄像初始化
CameraDevicesLoad();
Log.Verbose("Finished devices load at WndMain Init");
LoadFaceCascade(cascadeFiles[0]);
Log.Verbose("Finished cascade load at WndMain Init");
Log.Information("WndMain Init Finished");
}
// 加载摄像头设备
private async Task CameraDevicesLoad()
{
// 确保只在窗体加载完成后执行
if (!this.IsHandleCreated)
{
await Task.Delay(100);
await CameraDevicesLoad();
Log.Verbose("Reloading CameraDevicesLoad");
return;
}
await Task.Run(() =>
{
cameraComboBox.Invoke(new Action(() => cameraComboBox.Items.Clear()));
Log.Verbose("Clear cameraComboBox items");
bool _cameraFound = false;
foreach (var i in FindCamera.EnumDevices.Devices)
{
// 添加设备名称到下拉列表
cameraComboBox.Invoke(new Action(() => cameraComboBox.Items.Add(i)));
Log.Verbose($"Add {i} to camera devices list");
// 检查是否找到特定摄像头
if (i.ToString() == "Smart_Camera" || !_cameraFound) // “Smart_Camera”是希沃一体机顶部摄像头名称
{
cameraComboBox.Invoke(new Action(() => cameraComboBox.SelectedItem = i));
_cameraFound = true;
Log.Information($"Found {i} while searching devices");
}
}
if (cameraComboBox.Items.Count == 0)
{
Invoke(new Action(() => currentStatusLabel.Text = "未找到摄像头"));
cameraComboBox.Invoke(new Action(() => cameraComboBox.Enabled = false));
Log.Warning("No camera found");
}
else
{
Invoke(new Action(() => cameraComboBox.SelectedIndex = 0)); // 默认选择第一个
Invoke(new Action(() => currentStatusLabel.Text = "就绪"));
Log.Information("Camera found, ready");
}
});
}
// 加载人脸级联分类器
private async Task LoadFaceCascade(string fileName)
{
// 确保只在窗体加载完成后执行
if (!this.IsHandleCreated)
{
await Task.Delay(100);
await LoadFaceCascade(fileName);
Log.Verbose("Reloading LoadFaceCascade");
return;
}
await Task.Run(() =>
{
// 释放之前的 CascadeClassifier 实例
if (faceCascade != null)
{
faceCascade.Dispose();
faceCascade = null;
Log.Verbose("Dispose faceCascade");
}
string xmlPath = Path.Combine(Application.StartupPath, "cascades/", fileName);
Log.Verbose($"Loading faceCascade from {xmlPath}");
try
{
faceCascade = new CascadeClassifier(xmlPath);
if (faceCascade.Empty())
{
Invoke(new Action(() => currentStatusLabel.Text = $"人脸级联分类器 {fileName} 空,加载失败"));
Log.Error($"CascadeClassifier {fileName} is empty");
}
else
{
Invoke(new Action(() => currentStatusLabel.Text = $"成功加载人脸级联分类器: {fileName}"));
Log.Information($"CascadeClassifier {fileName} is loaded successfully");
}
}
catch (Exception ex)
{
Invoke(new Action(() => currentStatusLabel.Text = $"加载人脸级联分类器时发生错误: {ex.Message}"));
Log.Error($"Error loading CascadeClassifier {fileName}: {ex.Message}");
}
});
}
// 级联分类器修改
private void cascadesComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
Log.Verbose("cascadesComboBox SelectedIndexChanged");
LoadFaceCascade(cascadeFiles[cascadesComboBox.SelectedIndex]);
}
// 点击刷新摄像头按钮
private void refreshCameraBtn_Click(object sender, EventArgs e)
{
Log.Verbose("refreshCameraBtn Clicked");
CameraDevicesLoad();
}
// 初始化摄像头
private void InitializeCapture(int cameraIndex)
{
// 释放旧的摄像头资源
if (capture != null)
{
capture.Release();
capture = null;
Log.Verbose("Dispose capture");
}
// 初始化新的摄像头
capture = new VideoCapture(cameraIndex);
if (!capture.IsOpened())
{
currentStatusLabel.Text = "无法打开摄像头";
Log.Warning("Cannot open camera");
return;
}
else
{
currentStatusLabel.Text = "就绪";
Log.Information("Camera opened successfully");
}
// 启动计时器
streamTimer.Start();
Log.Verbose("Start streamTimer");
}
// 点击切换配置面板按钮
private void toggleConfigBtn_Click(object sender, EventArgs e)
{
Log.Verbose("toggleConfigBtn Clicked");
if (configPanel.Visible)
{
configPanel.Visible = false;
toggleConfigBtn.Text = "显示配置面板";
Log.Verbose("Show configPanel");
}
else
{
configPanel.Visible = true;
toggleConfigBtn.Text = "隐藏配置面板";
Log.Verbose("Hide configPanel");
}
}
// 摄像头选择更改
private void cameraComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
InitializeCapture(cameraComboBox.SelectedIndex);
}
// 点击应用帧率按钮
private void applyFpsBtn_Click(object sender, EventArgs e)
{
Log.Verbose("applyFpsBtn Clicked");
streamTimer.Interval = (int)Math.Max(1, 1000 / streamFpsNum.Value);
if (streamFpsNum.Value < faceRecognFpsNum.Value)
{
faceRecognFpsNum.Value = streamFpsNum.Value;
currentStatusLabel.Text = "人脸识别帧率不能大于视频流帧率";
Log.Warning($"Face recognition FPS cannot be greater than stream FPS, Stream FPS: {streamFpsNum.Value}, Face Recognition FPS: {faceRecognFpsNum.Value}");
}
detectionInterval = (int)Math.Max(1, streamFpsNum.Value / faceRecognFpsNum.Value);
Log.Verbose($"Detection Interval: {detectionInterval}");
}
// 点击边框厚度按钮
private void applyframeThicknessBtn_Click(object sender, EventArgs e)
{
Log.Verbose("applyframeThicknessBtn Clicked");
frameThickness = Convert.ToInt32(frameThicknessNum.Value);
Log.Information($"Set Frame Thickness: {frameThickness}");
}
// 检测人脸
private void DetectFaces()
{
try
{
lastDetectionFrameIndex = frameIndex;
double scale = 0.75;
OpenCvSharp.Size newSize = new OpenCvSharp.Size(Math.Round(frame.Width * scale), Math.Round(frame.Height * scale));
using (Mat resizedFrame = new Mat())
{
Cv2.Resize(frame, resizedFrame, newSize);
Rect[] detectedFaces = faceCascade.DetectMultiScale(resizedFrame, scaleFactor: 1.1, minNeighbors: 5, minSize: new OpenCvSharp.Size(30, 30));
indexedFaceRectangles.Clear();
nextIndex = 0;
foreach (var rect in detectedFaces)
{
int x = (int)(rect.X * scale);
int y = (int)(rect.Y * scale);
int width = (int)(rect.Width * scale);
int height = (int)(rect.Height * scale);
indexedFaceRectangles[nextIndex] = new Rect(x, y, width, height);
nextIndex++;
}
Log.Information($"Detected {indexedFaceRectangles.Count} faces");
}
faceRecogStatusLabel.Text = $"检测到 {indexedFaceRectangles.Count} 张人脸";
}
catch (Exception ex)
{
faceRecogStatusLabel.Text = $"检测人脸时发生错误: {ex.Message}";
Log.Error($"Error detecting faces: {ex.Message}");
}
}
// 在 currentCamera 上绘制人脸矩形
private void DrawFacesOnPictureBox()
{
try
{
using (Bitmap bitmap = BitmapConverter.ToBitmap(frame))
using (Graphics g = Graphics.FromImage(bitmap))
{
foreach (var kvp in indexedFaceRectangles)
{
var rect = kvp.Value;
int index = kvp.Key;
g.DrawRectangle(new Pen(Color.Red, frameThickness), rect.X, rect.Y, rect.Width, rect.Height);
}
cameraCurrent.Image?.Dispose();
cameraCurrent.Image = bitmap;
}
}
catch (Exception ex)
{
currentStatusLabel.Text = $"绘制人脸矩形时发生错误: {ex.Message}";
}
}
// 视频流计时器
private void streamTimer_Tick(object sender, EventArgs e)
{
// 检查 faceCascade 是否已正确加载
if (faceCascade == null || faceCascade.Empty())
{
currentStatusLabel.Text = "人脸级联分类器未加载";
return;
}
// 从摄像头读取一帧图像
try
{
capture.Read(frame);
if (frame.Empty())
{
frameIndex++;
bool shouldDetect = (frameIndex - lastDetectionFrameIndex) >= detectionInterval;
if (shouldDetect)
{
DetectFaces();
}
DrawFacesOnPictureBox();
}
}
catch (Exception ex)
{
currentStatusLabel.Text = $"发生错误: {ex.Message}";
streamTimer.Stop(); // 停止计时器,防止爆内存或者CPU
Log.Verbose($"streamTimer Stopped: {ex.Message}");
}
}
// 窗口关闭
private void WndMain_FormClosing(object sender, FormClosingEventArgs e)
{
// 确保计时器停止后再释放资源
if (streamTimer != null)
{
streamTimer.Stop();
streamTimer.Dispose();
Log.Verbose("streamTimer Stop & Released");
}
// 确保在UI线程上执行释放资源
this.Invoke((MethodInvoker)delegate
{
if (capture != null)
{
capture.Release();
Log.Verbose("Camera Released");
}
if (faceCascade != null)
{
faceCascade.Dispose();
Log.Verbose("Cascade Released");
}
});
Log.Information("Resources Disposed, Closing...");
Log.Information("----------");
Log.CloseAndFlushAsync();
}
// 点击随机抽选按钮
private void randomBtn_Click(object sender, EventArgs e)
{
Log.Verbose("randomBtn Clicked");
// 暂停计时器
streamTimer.Stop();
Log.Verbose("streamTimer Stop");
// 组件控制
configGroupBox.Enabled = false;
randomBtn.Enabled = false;
backBtn.Enabled = true;
Log.Verbose("Components Disable");
try
{
if (indexedFaceRectangles.Count > 0)
{
// 随机选择一个索引
Random random = new Random();
int emphasizedIndex = random.Next(indexedFaceRectangles.Count);
Log.Verbose($"Random number: {emphasizedIndex} in {indexedFaceRectangles.Count}");
// 获取对应的Rect对象
Rect selectedFace = indexedFaceRectangles[emphasizedIndex];
Log.Verbose($"Get rectangle: {selectedFace}");
// 使用其他颜色(例如绿色)绘制矩形框并添加紫色箭头
using (Mat frameCopy = frame.Clone())
{
int frameThickness = 2;
// 强调幸运儿
Cv2.Rectangle(frameCopy, selectedFace, new Scalar(150, 255, 150), frameThickness + 4);
Cv2.Rectangle(frameCopy, selectedFace, Scalar.Green, frameThickness - 1);
// 绘制紫色箭头
OpenCvSharp.Point endPoint = new OpenCvSharp.Point(selectedFace.X + selectedFace.Width / 2, selectedFace.Y - 20);
OpenCvSharp.Point startPoint = new OpenCvSharp.Point(endPoint.X, endPoint.Y - Math.Max(selectedFace.Height / 2, 50));
double arrowLength = 20;
double angle = Math.PI / 6;
OpenCvSharp.Point leftTip = new OpenCvSharp.Point(
(int)(endPoint.X - arrowLength * Math.Cos(angle)),
(int)(endPoint.Y - arrowLength * Math.Sin(angle))
);
OpenCvSharp.Point rightTip = new OpenCvSharp.Point(
(int)(endPoint.X + arrowLength * Math.Cos(angle)),
(int)(endPoint.Y - arrowLength * Math.Sin(angle))
);
Cv2.Line(frameCopy, startPoint, endPoint, new Scalar(255, 0, 119, 215), frameThickness);
Cv2.Line(frameCopy, endPoint, leftTip, new Scalar(255, 0, 119, 215), frameThickness);
Cv2.Line(frameCopy, endPoint, rightTip, new Scalar(255, 0, 119, 215), frameThickness);
// 将修改后的帧转换为Bitmap并在PictureBox中显示
using (Bitmap bitmap = BitmapConverter.ToBitmap(frameCopy))
using (Graphics g = Graphics.FromImage(bitmap))
{
cameraCurrent.Image?.Dispose(); // 确保之前的图像被释放
cameraCurrent.Image = bitmap;
}
}
currentStatusLabel.Text = $"就你啦!索引值为 {emphasizedIndex} 的脸";
Log.Information($"Selected face: {emphasizedIndex}");
}
}
catch (Exception ex)
{
currentStatusLabel.Text = $"发生错误: {ex}";
Log.Error("Error: {0}", ex);
// 组件控制
configGroupBox.Enabled = true;
randomBtn.Enabled = true;
backBtn.Enabled = false;
Log.Verbose("Components enabled");
}
}
// 点击返回按钮
private void backBtn_Click(object sender, EventArgs e)
{
Log.Verbose("backBtn clicked");
// 重新启动计时器
streamTimer.Start();
Log.Verbose("Stream timer started");
// 组件控制
configGroupBox.Enabled = true;
randomBtn.Enabled = true;
backBtn.Enabled = false;
currentStatusLabel.Text = "就绪";
Log.Verbose("Components enabled");
Log.Information("Back button clicked");
}
// 关于
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
Log.Verbose("aboutToolStripMenuItem Clicked");
Misc.aboutDialogShow(this);
Log.Information("About dialog shown");
}
// 打开开源仓库
private void repoToolStripMenuItem_Click(object sender, EventArgs e)
{
Log.Verbose("repoToolStripMenuItem Clicked");
Misc.openRepoURL();
Log.Information("Open repo");
}
// 打开日志
private void openLogToolStripMenuItem_Click(object sender, EventArgs e)
{
Log.Verbose("openLogToolStripMenuItem Clicked");
if (File.Exists("olr.log"))
{
Process.Start("notepad.exe", "olr.log");
}
else
{
currentStatusLabel.Text = "日志文件不存在";
Log.Error("Log file does not exist, but where?");
}
}
}
}