2022年6月26日 星期日

在Colab使用攝影機

OpenCV的VideoCapture(0)是從執行程式的電腦開啟攝像機,當你在Colab伺服器上執行程式時,Colab伺服器並沒有攝像機,因此VideoCapture(0)無法在Colab中運行,無法從本機攝影機獲取圖像。因為Colab是在瀏覽器中運行,可以利用JavaScript來開啟本機攝影機。

擷取攝影機圖像

Colab有提供擷取本機攝影機圖像的示範程式碼:在`Colab專案頁面點選「程式碼片斷」鈕,再點選「Camera Capture」項目右方的箭頭,就會將擷取本機攝影機圖像的程式碼加入新的程式碼儲存格。

執行後會開啟攝影機,使用者按「Capture」鈕會擷取當時攝影機畫面圖像。

 

擷取的圖像存於Colab的<photo.jpg>檔,點選右方功能鈕再按「下載」可將圖形檔存於本機。

程式碼為:

1 from IPython.display import display, Javascript

 2 from google.colab.output import eval_js

 3 from base64 import b64decode

 4 

 5 def take_photo(filename='photo.jpg', quality=0.8):

 6   js = Javascript('''

 7     async function takePhoto(quality) {

 8       const div = document.createElement('div');

 9       const capture = document.createElement('button');

10       capture.textContent = 'Capture';

11       div.appendChild(capture);

12 

13       const video = document.createElement('video');

14       video.style.display = 'block';

15       const stream = await navigator.mediaDevices.getUserMedia({video: true});

16 

17       document.body.appendChild(div);

18       div.appendChild(video);

19       video.srcObject = stream;

20       await video.play();

21 

22       // Resize the output to fit the video element.

23       google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

24 

25       // Wait for Capture to be clicked.

26       await new Promise((resolve) => capture.onclick = resolve);

27 

28       const canvas = document.createElement('canvas');

29       canvas.width = video.videoWidth;

30       canvas.height = video.videoHeight;

31       canvas.getContext('2d').drawImage(video, 0, 0);

32       stream.getVideoTracks()[0].stop();

33       div.remove();

34       return canvas.toDataURL('image/jpeg', quality);

35     }

36     ''')

37   display(js)

38   data = eval_js('takePhoto({})'.format(quality))

39   binary = b64decode(data.split(',')[1])

40   with open(filename, 'wb') as f:

41     f.write(binary)

42   return filename

43 

44 from IPython.display import Image

45 try:

46   filename = take_photo()

47   print('Saved to {}'.format(filename))

48   

49   # Show the image which was just taken.

50   display(Image(filename))

51 except Exception as err:

52   # Errors will be thrown if the user does not have a webcam or if they do not

53   # grant the page permission to access it.

54   print(str(err))

第5-42列為擷取攝影機圖像的函式:參數filename為儲存圖片的檔名,quality為圖像品質。

第6-36列為擷取攝影機圖像並傳回圖像的Javascript程式。

第8-11列建立「Capture」按鈕。

第13-15列開啟攝影機。

第17-20列等待攝影機啟動完成。

第23列調整攝影機畫面顯示區域,此時就會顯示攝影機動態畫面。

第26列等待使用者按「Capture」鈕,使用者按「Capture」鈕後才會繼續執行28-34列。

第28-31列擷取攝影機畫面。

第32-33列關閉攝影機畫面。

第34列傳回擷取的攝影機畫面。

第37-38列執行Javascript程式碼並取得傳回的擷取攝影機圖像。

第39列解碼擷取的攝影機圖像成二進位資料。

第40-41列儲存圖片檔案。

第42列傳回圖片檔名。

第46-47列在主程式取得並顯示圖片檔名。

第50列顯示擷取的攝影機圖像。

應用:錄影及播放影片

攝影機最重要的功能就是錄製影片,因此利用前面範例程式撰寫錄製影片程式。

執行後會開啟攝影機,使用者按「開始錄影」鈕開始錄製影片,同時按鈕文字變為「停止錄影」,使用者按「停止錄影」鈕結束錄製影片。

 

擷取的圖像存於Colab的<record.mp4>檔,點選右方功能鈕再按「下載」可將錄影檔存於本機。

錄影程式碼為:

1 from IPython.display import display, Javascript,HTML

 2 from google.colab.output import eval_js

 3 from base64 import b64decode

 4  

 5 def record_video(filename):

 6   js=Javascript("""

 7     async function recordVideo() {

 8       const options = { mimeType: "video/webm; codecs=vp9" };

 9       const div = document.createElement('div');

10       const capture = document.createElement('button');

11       capture.textContent = "開始錄影";

12       capture.style.background = "orange";

13       capture.style.color = "white";

14       div.appendChild(capture);

15  

16       const stopCapture = document.createElement("button");

17       stopCapture.textContent = "停止錄影";

18       stopCapture.style.background = "red";

19       stopCapture.style.color = "white";

20  

21       const video = document.createElement('video');

22       const recordingVid = document.createElement("video");

23       video.style.display = 'block';

24       const stream = await navigator.mediaDevices.getUserMedia({audio:true, video: true});

25      

26       let recorder = new MediaRecorder(stream, options);

27       document.body.appendChild(div);

28       div.appendChild(video);

29       video.srcObject = stream;

30       video.muted = true;

31       await video.play();

32       google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

33  

34       await new Promise((resolve) => {capture.onclick = resolve; });

35       recorder.start();

36       capture.replaceWith(stopCapture);

37  

38       await new Promise((resolve) => stopCapture.onclick = resolve);

39       recorder.stop();

40       let recData = await new Promise((resolve) => recorder.ondataavailable = resolve);

41       let arrBuff = await recData.data.arrayBuffer();

42       stream.getVideoTracks()[0].stop();

43       div.remove();

44  

45       let binaryString = "";

46       let bytes = new Uint8Array(arrBuff);

47       bytes.forEach((byte) => {

48         binaryString += String.fromCharCode(byte);

49       })

50     return btoa(binaryString);

51     }

52   """)

53   try:

54     display(js)

55     data=eval_js('recordVideo({})')

56     binary=b64decode(data)

57     with open(filename,"wb") as video_file:

58       video_file.write(binary)

59     print(f"錄影檔案:{filename}")

60   except Exception as err:

61     print(str(err))

62 

63 video_path = "record.mp4"

64 record_video(video_path)

第6-52列錄影Javascript程式碼。

第10-14列建立「開始錄影」按鈕。

第16-19列建立「停止錄影」按鈕。

第21-32列啟動攝影機拍攝。

第26列以MediaRecorder建立錄製影片物件。

第34列等待使用者按「開始錄影」鈕。

第35列開始錄影。

第36列將按鈕變為「停止錄影」鈕。

第38列等待使用者按「停止錄影」鈕。

第39列停止錄影。

第40-41列取得錄影資料。

第42-43列關閉攝影機畫面。

第45-49列將錄影資料轉為二進位。

第50列傳回錄影資料。

第54-55列執行Javascript程式碼並取得傳回的錄影資料。

第56-61列儲存錄影檔案。

下面程式會播放錄製的影片檔:

1 from IPython.display import HTML

 2 from base64 import b64encode

 3  

 4 video_path = "record.mp4"

 5 video_width = 600   

 6 video_file = open(video_path, "r+b").read()

 7 video_url = f"data:video/mp4;base64,{b64encode(video_file).decode()}"

 8 HTML(f"""<video width={video_width} controls><source src="{video_url}"></video>""")

逐一處理CAM畫面

實務上常要處理攝影機每幀畫面,例如臉部辨識時要對每一個畫面進行辨識,以框選出臉部。下面程式會取得每一個攝影畫面進行處理(不再解說程式),但因為程式是在Colab執行,必須將每一個攝影畫面上傳才能處理,因此處理速度很慢,畫面停格的很嚴重。

臉部辨識程式碼為:

from IPython.display import display, Javascript

from google.colab.output import eval_js

from base64 import b64decode, b64encode

import numpy as np

import cv2


def init_camera():

  js = Javascript('''

    var div = null;

    var video = null;

    var stream = null;

    var canvas = null;

    var img = null;


    async function initCamera() {

      div = document.createElement('div');

      document.body.appendChild(div);

      video = document.createElement('video');

      video.style.display = 'block';

      div.appendChild(video);


      stream = await navigator.mediaDevices.getUserMedia({video: true});

      video.srcObject = stream;

      await video.play();

      google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);


      canvas = document.createElement('canvas');

      canvas.width = video.videoWidth;

      canvas.height = video.videoHeight;


      img = document.createElement('img');

      img.width = video.videoWidth;

      img.height = video.videoHeight;

      div.appendChild(img);

    }


    async function takeImage(quality) {

      canvas.getContext('2d').drawImage(video, 0, 0);

      return canvas.toDataURL('image/jpeg', quality);

    }


    async function showImage(image) {

      img.src = image;

    }


  ''')


  display(js)

  eval_js('initCamera()')


def take_frame(quality=0.8):

  data = eval_js('takeImage({})'.format(quality))

  data = data.split(',')[1]

  data = b64decode(data)

  data = np.frombuffer(data, dtype=np.uint8)

  img = cv2.imdecode(data, cv2.IMREAD_UNCHANGED)

  return img


def show_frame(img, quality=0.8):

  ret, data = cv2.imencode('.jpg', img)

  data = b64encode(data)

  data = data.decode()

  data = 'data:image/jpg;base64,' + data

  eval_js('showImage("{}")'.format(data))


face_cascade = cv2.CascadeClassifier(os.path.join(cv2.data.haarcascades, 'haarcascade_frontalface_default.xml'))

init_camera()

while True:

    try:

        img = take_frame()

        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        faces = face_cascade.detectMultiScale(gray, 1.1, 4)

        for (x, y, w, h) in faces:

                cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)

        show_frame(img)

    except Exception as err:

        print('Exception:', err)


沒有留言:

張貼留言