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;      }  }

  至此,系統程式碼告一段路,一起來看看軟體效果吧!(請自動忽略影片內容,以及筆記型電腦攝影機帶來的渣渣像素

   最後,由於系統引用文件較多,壓縮後源碼文件仍然很大,如果有需要源碼的朋友,可以微信公眾號聯繫部落客,源碼可以免費贈予~!有疑問的也可以CALL我一起探討,最最後,如果覺得本篇博文對您或者身邊朋友有幫助的,麻煩點個關注!贈人玫瑰,手留余香,您的支援就是我寫作最大的動力,感謝您的關注,期待和您一起探討!再會!