WebCamera2.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. using System;
  2. using AForge.Video;
  3. using System.Drawing;
  4. using AForge.Controls;
  5. using System.Threading;
  6. using System.Windows.Forms;
  7. using AForge.Video.DirectShow;
  8. using System.Collections.Generic;
  9. namespace shjxCamera
  10. {
  11. public class WebCamera2
  12. {
  13. #region private fields
  14. private Bitmap _currFrame;
  15. #endregion
  16. #region private properties
  17. public VideoCaptureDevice VidCapDevice2 { get; set; }
  18. private VideoSourcePlayer VidPlayer2 { get; set; }
  19. /// <summary>
  20. /// 当前画面,相机开启时有效
  21. /// </summary>
  22. public Bitmap CurrFrame
  23. {
  24. get => VidCapDevice2 is not null ? _currFrame.Clone() as Bitmap : null;
  25. protected set => _currFrame = value;
  26. }
  27. /// <summary>
  28. /// 摄像头是否已打开
  29. /// </summary>
  30. public bool IsOpened
  31. {
  32. get
  33. {
  34. bool isOpened = false;
  35. if (VidPlayer2 == null)
  36. {
  37. return isOpened;
  38. }
  39. if (VidPlayer2.InvokeRequired)
  40. {
  41. VidPlayer2.Invoke(new Action(() =>
  42. {
  43. isOpened = VidPlayer2.IsRunning;
  44. }));
  45. }
  46. else
  47. {
  48. isOpened = VidPlayer2.IsRunning;
  49. }
  50. return isOpened;
  51. }
  52. }
  53. #endregion
  54. #region static
  55. private static FilterInfoCollection vidCollection;
  56. public static FilterInfoCollection GetCameras()
  57. {
  58. vidCollection = new(FilterCategory.VideoInputDevice);
  59. return vidCollection;
  60. }
  61. public static string GetCameraMoniker(int index)
  62. {
  63. return vidCollection[index].MonikerString;
  64. }
  65. public static string GetCameraName(int index)
  66. {
  67. return vidCollection[index].Name;
  68. }
  69. #endregion
  70. #region events
  71. /// <summary>
  72. /// 画面刷新事件
  73. /// </summary>
  74. public event NewFrameEventHandler NewFrameEvent;
  75. #endregion
  76. #region 构造函数
  77. /// <summary>
  78. /// 初始化摄像头
  79. /// </summary>
  80. /// <param name="cameraMoniker">摄像头名称</param>
  81. public WebCamera2(string cameraMoniker)
  82. {
  83. VidCapDevice2 = new VideoCaptureDevice(cameraMoniker);
  84. VidCapDevice2.NewFrame += GetCurrFrame;
  85. VidPlayer2 = new VideoSourcePlayer
  86. {
  87. VideoSource = VidCapDevice2
  88. };
  89. _currFrame = null;
  90. }
  91. /// <summary>
  92. /// 初始化摄像头
  93. /// </summary>
  94. public WebCamera2()
  95. {
  96. VidCapDevice2 = new VideoCaptureDevice();
  97. VidPlayer2 = new VideoSourcePlayer
  98. {
  99. VideoSource = VidCapDevice2
  100. };
  101. _currFrame = null;
  102. }
  103. #endregion
  104. #region Camera Operate
  105. /// <summary>
  106. /// 连接指定的摄像头,并打开视频信号
  107. /// </summary>
  108. /// <param name="cameraMoniker"></param>
  109. /// <returns></returns>
  110. public bool OpenCamera(string cameraMoniker)
  111. {
  112. this.VidCapDevice2.Source = cameraMoniker;
  113. VidCapDevice2.NewFrame += this.GetCurrFrame;
  114. this.VidCapDevice2.Start();
  115. return this.VidCapDevice2.IsRunning;
  116. }
  117. /// <summary>
  118. /// 打开摄像头
  119. /// </summary>
  120. /// <returns></returns>
  121. public bool OpenCamera()
  122. {
  123. if (VidCapDevice2 != null && VidCapDevice2.IsRunning == false)
  124. {
  125. VidCapDevice2.NewFrame += NewFrameEvent;
  126. VidCapDevice2.Start();
  127. }
  128. if (VidPlayer2.InvokeRequired)
  129. {
  130. VidPlayer2.Invoke(new Action(() =>
  131. {
  132. if (VidPlayer2 != null && !VidPlayer2.IsRunning)
  133. {
  134. VidPlayer2.Start();
  135. }
  136. }));
  137. }
  138. else
  139. {
  140. if (VidPlayer2 != null && !VidPlayer2.IsRunning)
  141. {
  142. VidPlayer2.Start();
  143. }
  144. }
  145. return true;
  146. }
  147. /// <summary>
  148. /// 关闭摄像头
  149. /// </summary>
  150. public void CloseCamera()
  151. {
  152. if (VidCapDevice2 != null && VidCapDevice2.IsRunning)
  153. {
  154. VidCapDevice2.SignalToStop();
  155. VidCapDevice2.WaitForStop();
  156. //VidCapDevice.Stop();
  157. VidCapDevice2.NewFrame -= NewFrameEvent;
  158. }
  159. if (VidPlayer2.InvokeRequired)
  160. {
  161. VidPlayer2.Invoke(new Action(() =>
  162. {
  163. if (VidPlayer2 == null || !VidPlayer2.IsRunning) return;
  164. VidPlayer2.SignalToStop();
  165. VidPlayer2.WaitForStop();
  166. }));
  167. }
  168. else
  169. {
  170. if (VidPlayer2 == null || !VidPlayer2.IsRunning) return;
  171. VidPlayer2.SignalToStop();
  172. VidPlayer2.WaitForStop();
  173. }
  174. }
  175. /// <summary>
  176. /// 拍照
  177. /// </summary>
  178. /// <returns></returns>
  179. public Bitmap GrabImage2()
  180. {
  181. Bitmap bmp = null;
  182. if (VidPlayer2.InvokeRequired)
  183. {
  184. VidPlayer2.Invoke(new Action(() =>
  185. {
  186. if (VidPlayer2.IsRunning)
  187. {
  188. bmp = VidPlayer2.GetCurrentVideoFrame();
  189. }
  190. else
  191. {
  192. // 探头未连接,重试
  193. int cnt = 0; // 重试10秒
  194. while (!VidPlayer2.IsRunning && cnt < 100)
  195. {
  196. VidPlayer2.Start();
  197. Thread.Sleep(100);
  198. cnt++;
  199. //PublicDatas.LogProgram.Error("摄像头未连接,正在重试" + cnt.ToString());
  200. }
  201. Thread.Sleep(300);
  202. bmp = VidPlayer2.GetCurrentVideoFrame();
  203. }
  204. }));
  205. }
  206. else
  207. {
  208. if (VidPlayer2.IsRunning)
  209. {
  210. bmp = VidPlayer2.GetCurrentVideoFrame();
  211. }
  212. else
  213. {
  214. // 探头未连接,重试
  215. int cnt = 0; // 重试10秒
  216. while (!VidPlayer2.IsRunning && cnt < 100)
  217. {
  218. VidPlayer2.Start();
  219. Thread.Sleep(100);
  220. cnt++;
  221. // PublicDatas.LogProgram.Error("摄像头未连接,正在重试" + cnt.ToString());
  222. }
  223. Thread.Sleep(300);
  224. bmp = VidPlayer2.GetCurrentVideoFrame();
  225. }
  226. }
  227. return bmp;
  228. }
  229. #endregion
  230. #region public methods
  231. /// <summary>
  232. /// 设置显示摄像头图像区域
  233. /// </summary>
  234. /// <param name="parentControl"></param>
  235. /// <param name="left"></param>
  236. /// <param name="top"></param>
  237. /// <param name="width"></param>
  238. /// <param name="height"></param>
  239. public void ShowControl(ref Panel parentControl, int left, int top, int width, int height)
  240. {
  241. if (VidPlayer2 == null) return;
  242. parentControl.Controls.Add(VidPlayer2);
  243. VidPlayer2.Location = new Point(left, top);
  244. VidPlayer2.Size = new Size(width, height);
  245. VidPlayer2.Visible = true;
  246. }
  247. /// <summary>
  248. /// 显示摄像头属性设置窗体
  249. /// </summary>
  250. /// <param name="parentWindow"></param>
  251. public void ShowProperties(IntPtr parentWindow)
  252. {
  253. VidCapDevice2.DisplayPropertyPage(parentWindow);
  254. }
  255. /// <summary>
  256. /// 设置相机属性值
  257. /// </summary>
  258. /// <param name="prop"></param>
  259. /// <param name="val"></param>
  260. /// <param name="flags"></param>
  261. public void SetProperties(CameraControlProperty prop, int val, CameraControlFlags flags)
  262. {
  263. VidCapDevice2.SetCameraProperty(prop, val, flags);// 取消自动曝光
  264. }
  265. /// <summary>
  266. /// 读取当前属性值
  267. /// </summary>
  268. /// <param name="prop"></param>
  269. /// <param name="val"></param>
  270. /// <param name="flags"></param>
  271. public void GetProperties(CameraControlProperty prop, out int val, out CameraControlFlags flags)
  272. {
  273. VidCapDevice2.GetCameraProperty(prop, out val, out flags);
  274. }
  275. /// <summary>
  276. /// 读取相机参数的取值范围
  277. /// </summary>
  278. /// <param name="prop"></param>
  279. /// <param name="minValue"></param>
  280. /// <param name="maxValue"></param>
  281. /// <param name="stepSize"></param>
  282. /// <param name="defaultValue"></param>
  283. /// <param name="flags"></param>
  284. public void GetPropertiesRange(CameraControlProperty prop, out int minValue, out int maxValue, out int stepSize, out int defaultValue, out CameraControlFlags flags)
  285. {
  286. VidCapDevice2.GetCameraPropertyRange(prop, out minValue, out maxValue, out stepSize, out defaultValue, out flags);
  287. }
  288. /// <summary>
  289. /// 设置视频属性值,亮度、对比度、白平衡、增益等
  290. /// </summary>
  291. /// <param name="prop"></param>
  292. /// <param name="val"></param>
  293. /// <param name="flags"></param>
  294. public void SetVideoProcAmpProperties(VideoProcAmpProperty prop, int val, VideoProcAmpFlags flags)
  295. {
  296. VidCapDevice2.SetVideoProcAmpProperty(prop, val, flags);
  297. }
  298. /// <summary>
  299. /// 读取当前视频参数
  300. /// </summary>
  301. /// <param name="prop"></param>
  302. /// <param name="val"></param>
  303. /// <param name="flags"></param>
  304. public void GetVideoProcAmpProperties(VideoProcAmpProperty prop, out int val, out VideoProcAmpFlags flags)
  305. {
  306. VidCapDevice2.GetVideoProcAmpProperty(prop, out val, out flags);
  307. }
  308. /// <summary>
  309. /// 读取视频参数的取值范围
  310. /// </summary>
  311. /// <param name="prop"></param>
  312. /// <param name="minValue"></param>
  313. /// <param name="maxValue"></param>
  314. /// <param name="stepSize"></param>
  315. /// <param name="defaultValue"></param>
  316. /// <param name="flags"></param>
  317. public void GetVideoProcAmpPropertiesRange(VideoProcAmpProperty prop, out int minValue, out int maxValue, out int stepSize, out int defaultValue, out VideoProcAmpFlags flags)
  318. {
  319. VidCapDevice2.GetVideoProcAmpPropertyRange(prop, out minValue, out maxValue, out stepSize, out defaultValue, out flags);
  320. }
  321. /// <summary>
  322. /// 实时将新的画面存入CurrFrame对象中
  323. /// </summary>
  324. /// <param name="sender"></param>
  325. /// <param name="e"></param>
  326. public void GetCurrFrame(object sender, NewFrameEventArgs e)
  327. {
  328. try
  329. {
  330. if (e.Frame != null)
  331. {
  332. CurrFrame = e.Frame;
  333. }
  334. }
  335. catch (Exception)
  336. {
  337. // ignored
  338. }
  339. }
  340. #endregion
  341. #region 未使用
  342. /// <summary>
  343. /// 拍照,返回CurrFrame图像
  344. /// </summary>
  345. /// <returns></returns>
  346. //public Bitmap GrabImage2()
  347. //{
  348. // return CurrFrame;
  349. //}
  350. /// <summary>
  351. /// 返回视频支持的分辨率
  352. /// </summary>
  353. /// <returns></returns>
  354. public List<string> GetVideoResolution()
  355. {
  356. List<string> resolution = new List<string>();
  357. foreach (var cap in VidCapDevice2.VideoCapabilities)
  358. {
  359. resolution.Add(string.Format("{0} x {1}", cap.FrameSize.Width, cap.FrameSize.Height));
  360. }
  361. return resolution;
  362. }
  363. /// <summary>
  364. /// 设定视频的分辨率,index可以通过GetVideoResolution()获取所有支持的分辨率来确定
  365. /// </summary>
  366. /// <param name="index">在所有支持分辨率中的序号</param>
  367. public void SetVideoResolution(int index)
  368. {
  369. if (VidCapDevice2.VideoCapabilities.Length > index)
  370. {
  371. VidCapDevice2.VideoResolution = VidCapDevice2.VideoCapabilities[index];
  372. }
  373. }
  374. #endregion
  375. // 抓取一帧图像,截取中间区域,按灰度值过滤后,计算平均RGB值
  376. public ColorPoint CaptureOne(string captureTag, int size, int gray_min, int gray_max)
  377. {
  378. Bitmap bmp = GrabImage22();
  379. if (bmp is null)
  380. {
  381. return new(0, 0, 0, captureTag);
  382. }
  383. int cntLow = 0, cntHigh = 0, cntNormal = 0;
  384. int sumR = 0, sumG = 0, sumB = 0;
  385. for (int y = bmp.Height / 2 - size / 2; y < bmp.Height / 2 + size / 2; y++)
  386. {
  387. for (int x = bmp.Width / 2 - size / 2; x < bmp.Width / 2 + size / 2; x++)
  388. {
  389. Color cc = bmp.GetPixel(x, y);
  390. int gray = Math.Min(255, (int)(cc.R * 0.299 + cc.G * 0.587 + cc.B * 0.114));
  391. if (gray < gray_min)
  392. {
  393. cntLow++;
  394. continue;
  395. }
  396. if (gray > gray_max)
  397. {
  398. cntHigh++;
  399. continue;
  400. }
  401. cntNormal++;
  402. sumR += cc.R;
  403. sumG += cc.G;
  404. sumB += cc.B;
  405. }
  406. }
  407. int avgR = 0, avgG = 0, avgB = 0;
  408. if (cntNormal > 0)
  409. {
  410. avgR = sumR / cntNormal;
  411. avgG = sumG / cntNormal;
  412. avgB = sumB / cntNormal;
  413. }
  414. // 记录颜色采样信息
  415. ColorPoint cp = new(avgR, avgG, avgB, captureTag);
  416. float total = size * size;
  417. if (!"watch".Equals(captureTag))
  418. {
  419. //logger.LogTrace($"<{captureTag}> HSB({cp.Hue,3},{cp.Saturation:F3},{cp.Brightness:F3}), RGBL({cp.R,3},{cp.G,3},{cp.B,3},{cp.Gray,3}), PLMH({total},{cntLow / total:F3},{cntNormal / total:F3},{cntHigh / total:F3}), WH:({bmp.Width},{bmp.Height})");
  420. //bmp.Save($"Data/{captureTag}.jpg", ImageFormat.Jpeg);
  421. }
  422. return cp;
  423. }
  424. // 背景底色采样
  425. public ColorPoint SampleBackground(string taskTag, int threshold_gray, int threshold_hue)
  426. {
  427. List<ColorPoint> cps = new List<ColorPoint>();
  428. for (int i = 0; i < 99; i++)
  429. {
  430. cps.Add(Sample($"{taskTag}/000S-{i + 1:D2}", threshold_gray, threshold_hue));
  431. if (cps.Count >= 5)
  432. {
  433. // 如排序后前三轮或后三轮的色调偏差小于阈值,完成采样
  434. ColorPoint[] ar = cps.ToArray();
  435. Array.Sort(ar);
  436. //Array.Reverse(ar);
  437. if (Math.Abs(ar[0].Hue - ar[2].Hue) <= threshold_hue || Math.Abs(ar[2].Hue - ar[4].Hue) <= threshold_hue)
  438. {
  439. ColorPoint cp = ar[2];
  440. return cp;
  441. }
  442. // 未达标,丢弃最早抓取的帧,继续采样
  443. cps.RemoveAt(0);
  444. }
  445. Thread.Sleep(100);
  446. }
  447. // 背景底色采样达到最大次数
  448. //logger.LogWarning($"<{taskTag}> 背景底色采样达到最大次数!");
  449. return cps.Last();
  450. }
  451. // 色值采样(抓取n帧图像合并计算)
  452. public ColorPoint Sample(string prefix, int threshold_gray, int threshold_hue)
  453. {
  454. int gray_min = threshold_gray;
  455. int gray_max = 255 - threshold_gray;
  456. List<ColorPoint> cps = new List<ColorPoint>();
  457. for (int i = 0; i < 99; i++)
  458. {
  459. string captureTag = $"{prefix}-{i + 1:D2}";
  460. int size = 200;
  461. ColorPoint cp = CaptureOne(captureTag, size, gray_min, gray_max);
  462. // 丢弃亮度过低的帧,继续采样
  463. if (cp.Gray <= gray_min)
  464. {
  465. Thread.Sleep(100);
  466. continue;
  467. }
  468. cps.Add(cp);
  469. if (cps.Count >= 5)
  470. {
  471. // 构建排列的数组
  472. ColorPoint[] ar = cps.ToArray();
  473. Array.Sort(ar);
  474. //Array.Reverse(ar);
  475. // 前三帧色调偏差小于等于阈值,取前三帧完成采样
  476. if (Math.Abs(ar[0].Hue - ar[2].Hue) <= threshold_hue)
  477. {
  478. return Merge(ar.Take(3).ToList(), prefix);
  479. }
  480. // 后三帧色调偏差小于等于阈值,取后三帧完成采样
  481. if (Math.Abs(ar[2].Hue - ar[4].Hue) <= threshold_hue)
  482. {
  483. return Merge(ar.Skip(2).Take(3).ToList(), prefix);
  484. }
  485. // 未达标,丢弃最早抓取的帧,继续采样
  486. cps.RemoveAt(0);
  487. }
  488. Thread.Sleep(100);
  489. }
  490. // 达到最大采样次数限制,返回最后一次采样结果
  491. //logger.LogWarning($"<{prefix}> 色值采样达到最大次数!");
  492. if (cps.Count > 0)
  493. {
  494. return cps.Last();
  495. }
  496. return new ColorPoint(0, 0, 0, prefix);
  497. }
  498. // 合并计算色值
  499. private static ColorPoint Merge(List<ColorPoint> list, string prefix)
  500. {
  501. int n = list.Count;
  502. if (n == 0)
  503. {
  504. return new ColorPoint(0, 0, 0, prefix);
  505. }
  506. int sampleR = 0, sampleG = 0, sampleB = 0;
  507. for (int i = 0; i < n; i++)
  508. {
  509. sampleR += list[i].R;
  510. sampleG += list[i].G;
  511. sampleB += list[i].B;
  512. }
  513. ColorPoint cp = new(sampleR / n, sampleG / n, sampleB / n, prefix);
  514. //logger.LogInformation($"<{prefix}> HSB:({cp.Hue,3},{cp.Saturation:F3},{cp.Brightness:F3}), Gray:{cp.Gray,3}, RGB:({cp.R,3},{cp.G,3},{cp.B,3})");
  515. return cp;
  516. }
  517. public Bitmap GrabImage22()
  518. {
  519. Bitmap bmp = null;
  520. if (VidPlayer2.InvokeRequired)
  521. {
  522. VidPlayer2.Invoke(new Action(() => bmp = VidPlayer2.GetCurrentVideoFrame()));
  523. }
  524. else
  525. {
  526. bmp = VidPlayer2.GetCurrentVideoFrame();
  527. }
  528. return bmp;
  529. }
  530. }
  531. }