C# 實現視頻監控系統(附源碼)
去過工廠或者倉庫的都知道,在工廠或倉庫裡面,會有很多不同的流水線,大部分的工廠或倉庫,都會在不同流水線的不同工位旁邊安裝一臺電腦,一方面便於工位上的師傅把產品的重要信息錄入系統,便於公司系統數據采集分析。另一方面嚴謹的工廠或倉庫也會在每個工位上安裝攝像頭,用於采集或監控流水線上工人的操(是)作(否)習(偷)慣(懶)。
好瞭,閑話少說,咱們直入主題吧!
本系統監控系統,主要核心是使用AForge.NET提供的接口和插件(dll),感興趣的朋友也可以去他們官網查看文檔http://www.aforgenet.com/framework/documentation.html
Talk is cheap,show me the code!
系統初始化時,首先檢查工位的機臺是否開啟瞭攝像頭,具體檢測代碼如下:
/// <summary> /// 監控bind /// </summary> private void bind() { try { FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); if (videoDevices.Count <= 0) { MessageBox.Show("請連接攝像頭"); return; } else { CloseCaptureDevice(); if (!Directory.Exists(path)) Directory.CreateDirectory(path); videoSource = new VideoCaptureDevice(videoDevices[0].MonikerString); videoSource.VideoResolution = videoSource.VideoCapabilities[0]; sourcePlayer.VideoSource = videoSource; sourcePlayer.Start(); } } catch (Exception ex) { MessageBox.Show(ex.Message); } }
好瞭,攝像頭沒問題,咱在檢查網絡是否正常(這事兒可以交給運維,當然也可以通過程序控制,具體校驗網絡代碼比比皆是,此處忽略,如有興趣的朋友可以在公眾號Call我一起探討),至於為什麼要校驗網絡,一部分是用於機臺系統的數據采集,另一部分就是錄制的視頻文件不可能存儲在工位機臺上,不然流水線和工位足夠多,豈不是一個工位一個幾天的查看視頻監控嘛!咱這都是智能化時代,錄制的視頻可以保存在本地,不過為瞭方便起見,需要定時清理,定時上傳到服務器便於領導審查。視頻上傳到服務器一般用到最多的莫非兩種情況,1.網絡足夠穩定,足夠快的可以直接和服務器開個磁盤映射(共享目錄),視頻錄制完後系統直接剪切到服務器保存即可。2.把不同時段錄制的視頻先存儲到本地,然後單獨開發個定時任務FTP定時上傳即可。今天先跟大傢分享下第一種方法,第二種方法也比較簡單,有興趣的朋友可以公眾號call我一起探討。
不知不覺又扯瞭一堆廢話,都是實在人,直接上源碼吧:
/// <summary> /// 開啟或者關閉程序後將多餘文件copy到相應目錄,並開啟磁盤映射上傳到共享目錄 /// </summary> private void CopyFilesToServer() { try { //遍歷 當前PC文件夾外是否存在視頻文件,如存在,移動到目標目錄 string newPath = path + MacAddressPath + @"-Video\"; if (!Directory.Exists(newPath)) Directory.CreateDirectory(newPath); //將上一次最後一個視頻文件轉入目錄 var files = Directory.GetFiles(path, "*.wmv"); foreach (var file in files) { FileInfo fi = new FileInfo(file); string filesName = file.Split(new string[] { "\\" }, StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); fi.MoveTo(newPath + filesName); } } catch (Exception ex) { //TODO:異常拋出 } finally { uint state = 0; if (!Directory.Exists("Z:")) { //計算機名 string computerName = System.Net.Dns.GetHostName(); //為網絡共享目錄添加磁盤映射 state = WNetHelper.WNetAddConnection(computerName + @"\" + netWorkUser, netWorkPwd, netWorkPath, "Z:"); } if (state.Equals(0)) { //本地磁盤視頻文件copy到網絡共享目錄 CopyFolder(path + MacAddressPath + @"-Video\", zPath); } else { WNetHelper.WinExec("NET USE * /DELETE /Y", 0); throw new Exception("添加網絡驅動器錯誤,錯誤號:" + state.ToString()); } } }
其中CopyFolder方法代碼如下:
#region 通過共享網絡磁盤映射的方式,講文件copy到指定網盤 /// <summary> /// 通過共享網絡磁盤映射的方式,講文件copy到指定網盤 /// </summary> /// <param name="strFromPath"></param> /// <param name="strToPath"></param> public static void CopyFolder(string strFromPath, string strToPath) { //如果源文件夾不存在,則創建 if (!Directory.Exists(strFromPath)) { Directory.CreateDirectory(strFromPath); } if (!Directory.Exists(strToPath)) { Directory.CreateDirectory(strToPath); } //直接剪切moveto,本地不留副本 string[] strFiles = Directory.GetFiles(strFromPath); //循環剪切文件,此處循環是考慮每日工作站最後一個文件無法存儲到根目錄,導致出現兩個視頻文件的問題 for (int i = 0; i < strFiles.Length; i++) { //取得文件名,隻取文件名,地址截掉。 string strFileName = strFiles[i].Substring(strFiles[i].LastIndexOf("\\") + 1, strFiles[i].Length - strFiles[i].LastIndexOf("\\") - 1); File.Move(strFiles[i], strToPath + "DT-" + strFileName); } } #endregion
做完機臺檢查工作,也做好瞭視頻傳輸的工作,接下來就是視頻錄制的主角戲瞭,完整錄制視頻源碼如下
/// <summary> /// videosouceplayer 錄像 /// </summary> /// <param name="sender"></param> /// <param name="image"></param> private void sourcePlayer_NewFrame(object sender, ref Bitmap image) { try { //寫到屏幕上的時間 g = Graphics.FromImage(image); SolidBrush drawBrush = new SolidBrush(Color.Yellow); Font drawFont = new Font("Arial", 6, System.Drawing.FontStyle.Bold, GraphicsUnit.Millimeter); int xPos = image.Width - (image.Width - 15); int yPos = 10; string drawDate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); g.DrawString(drawDate, drawFont, drawBrush, xPos, yPos); //save content string videoFileName = dt.ToString("yyyy-MM-dd HHmm") + ".wmv"; if (TestDriveInfo(videoFileName)) //檢測硬盤空間足夠 { if (!stopREC) { stopREC = true; createNewFile = true; //這裡要設置為true表示要創建新文件 if (videoWriter != null) videoWriter.Close(); } else { //開始錄像 if (createNewFile) { //第二次錄像不一定是第二次開啟軟件時間(比如:連續多小時錄制),所以應該重新給新錄制視頻文件重新賦值命名 dt = DateTime.Now; videoFileFullPath = path + dt.ToString("yyyy-MM-dd HHmm") + ".wmv";//videoFileName; createNewFile = false; if (videoWriter != null) { videoWriter.Close(); videoWriter.Dispose(); } videoWriter = new VideoFileWriter(); //這裡必須是全路徑,否則會默認保存到程序運行根據錄下瞭 videoWriter.Open(videoFileFullPath, image.Width, image.Height, 30, VideoCodec.WMV1); videoWriter.WriteVideoFrame(image); } else { if (videoWriter.IsOpen) { videoWriter.WriteVideoFrame(image); } if (dt.AddMinutes(1) <= DateTime.Now) { createNewFile = true; //modify by stephen,每次寫入視頻文件後,即刻更新結束時間戳,並存入指定文件夾(目的:如果隻有關閉的時候處理此操作,就會出現大於1小時的視頻文件無法更新結束時間戳,且無法轉入指定文件夾) if (videoWriter != null) { videoWriter.Close(); videoWriter.Dispose(); } string newPath = path + MacAddressPath + @"-Video\"; if (!Directory.Exists(newPath)) Directory.CreateDirectory(newPath); string newStr = newPath + dt.ToString("yyyyMMddHHmm") + "-" + DateTime.Now.ToString("yyyyMMddHHmm") + ".wmv"; FileInfo fi = new FileInfo(videoFileFullPath); fi.MoveTo(newStr); ////轉移到網路目錄 //CopyFilesToServer(); } } } } } catch (Exception ex) { videoWriter.Close(); videoWriter.Dispose(); } finally { if (this.g != null) this.g.Dispose(); } }
其中TestDriveInfo方法是用來獲取保存視頻的磁盤信息的,具體代碼如下:
#region 獲取保存視頻的磁盤信息 /// <summary> /// 獲取保存視頻的磁盤信息 /// </summary> bool TestDriveInfo(string n) { try { DriveInfo D = DriveInfo.GetDrives().Where(a => a.Name == path.Substring(0, 3).ToUpper()).FirstOrDefault(); Int64 i = D.TotalFreeSpace, ti = unchecked(50 * 1024 * 1024 * 1024); if (i < ti) { DirectoryInfo folder = new DirectoryInfo(path + MacAddressPath + @"-Video\"); //modify by stephen,驗證當前指定文件夾是否存在元素 if (folder.Exists) { var fisList = folder.GetFiles("*.wmv").OrderBy(a => a.CreationTime); if (fisList.Any()) { List<FileInfo> fis = fisList.ToList(); if (fis.Count > 0 && fis[0].Name != n) { File.Delete(fis[0].FullName); } } } } } catch (Exception ex) { MessageBox.Show(ex.Message, "處理硬盤信息出錯"); return false; } return true; } #endregion
當然,如果工位師傅錄入產品信息有疑問的話,也可以利用系統截圖來保留證據,這個是我自己畫蛇添足的功能,反正是為瞭方便嘛,別耽誤瞭工位師傅的辦事效率,利用攝像頭截圖代碼如下:
try { string pathp = $@"{Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)}\"; if (!Directory.Exists(pathp)) Directory.CreateDirectory(pathp); if (sourcePlayer.IsRunning) { BitmapSource bitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( sourcePlayer.GetCurrentVideoFrame().GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); PngBitmapEncoder pE = new PngBitmapEncoder(); pE.Frames.Add(BitmapFrame.Create(bitmapSource)); string picName = $"{pathp}{DateTime.Now.ToString("yyyyMMddHHmmssffffff")}.jpg"; if (File.Exists(picName)) { File.Delete(picName); } using (Stream stream = File.Create(picName)) { pE.Save(stream); } } } catch (Exception ex) { MessageBox.Show(ex.Message); }
代碼比較簡單,就不寫備註瞭。當然部署系統的時候也不是一帆風順,有的工廠或者倉庫會購買第三方的攝像頭,礙於工位環境,攝像頭有可能與機臺角度偏差較大,所以我又畫蛇添足的瞭校驗攝像頭的小功能,可以左右90°上下180°畫面翻轉,具體代碼如下:
#region 設置攝像頭旋轉調整 if (image != null) { RotateFlipType pType = RotateFlipType.RotateNoneFlipNone; if (dAngle == 0) { pType = RotateFlipType.RotateNoneFlipNone; } else if (dAngle == 90) { pType = RotateFlipType.Rotate90FlipNone; } else if (dAngle == 180) { pType = RotateFlipType.Rotate180FlipNone; } else if (dAngle == 270) { pType = RotateFlipType.Rotate270FlipNone; } // 實時按角度繪制 image.RotateFlip(pType); } #endregion
當然,站在公司角度,為瞭防止工位師傅手誤(誠心)關掉視頻監控程序,我們也可以從程序的角度來防患於未然,比如禁用程序的關閉按鈕,禁用工具欄右鍵程序圖標關閉程序的操作。
我們可以重寫窗口句柄來防止,具體代碼如下:
#region 窗口句柄重寫,禁用窗體的關閉按鈕 private const int CP_NOCLOSE_BUTTON = 0x200; protected override CreateParams CreateParams { get { CreateParams myCp = base.CreateParams; myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON; return myCp; } }
至此,系統代碼告一段路,一起來看看軟件效果吧!請自動忽略視頻內容,以及筆記本攝像頭帶來的渣渣像素
作者:Stephen-kzx
出處:http://www.cnblogs.com/axing/
源碼下載:https://pan.baidu.com/s/1qxBXl4Nn7IO0SJ-mYC861Q 提取碼:b9f7
推薦閱讀:
- C#實現多文件打包壓縮(.Net Core)
- .Net Core 多文件打包壓縮的實現代碼
- C#服務器NFS共享文件夾搭建與上傳圖片文件的實現
- ASP.NET Core實現文件上傳和下載
- 一文搞懂C#實現讀寫文本文件中的數據