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我一起探討,最最后,如果覺得本篇博文對您或者身邊朋友有幫助的,麻煩點個關注!贈人玫瑰,手留余香,您的支持就是我寫作最大的動力,感謝您的關注,期待和您一起探討!再會!(公眾號回復:視頻或者監控獲取源碼)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM