5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Google Colaboratoryで外付けカメラを使う

Last updated at Posted at 2023-10-01

はじめに

今回は、Google Colaboratory(無料でGPUを提供してくれる神様)で外付けカメラを使う方法を紹介したいと思います.

CPUしか使えないPCの強き味方でお馴染みのColabですが、
Colab環境下で内蔵カメラ、外付けカメラを使うには一癖あります.
以前、少し苦闘したので、お役に立てたら幸いです.

え?PythonならOpenCVをインポートして

cap = cv2.VideoCapture(0)

「この1行で解決でしょ!」
そう思ったあなた!天才!!!!
でも実はColabではうまく実行してくれません, Oh...

Colabでカメラを使うにはJavaScriptが必要になります.

「JavaScriptわからないよ〜〜〜助けてどら○も〜〜〜〜ん」
大丈夫!
Colab君が、内蔵カメラのフレームを取得するコードを提供しているので
それをちょこっと書き換えれば、外付けカメラを使うことができます.

環境

始める前に・・・

SafariでColabを使わない!

⇦Safariは、今回使うコードの一部、Navigator.getUserMedia()メソッドに
対応していないからです.
そうとは知らずにSafariを使い続け、時間を溶かしました.

ブラウザ 互換性
Chrome
Edge
FireFox
Opera
Safari ×

目次

・Colabが提供しているコードを見てみよう
・外付けカメラのIDを取得しよう
・実行してみよう
・おまけ
 -

Colabが提供しているコードを見てみよう

コードの挿入方法

「そもそも、そのコードはどこにあるのさ!」という方向け👈 順を追って説明します!

スクリーンショット 2023-10-01 2.38.30.png
① 画面左下の<>をクリック
Camera Captureをクリック
挿入をクリック
これでOK!

コードの中身

from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode

def take_photo(filename='photo.jpg', quality=0.8):
  js = Javascript('''
    async function takePhoto(quality) {
      const div = document.createElement('div');
      const capture = document.createElement('button');
      capture.textContent = 'Capture';
      div.appendChild(capture);

      const video = document.createElement('video');
      video.style.display = 'block';
      const stream = await navigator.mediaDevices.getUserMedia({video: true});

      document.body.appendChild(div);
      div.appendChild(video);
      video.srcObject = stream;
      await video.play();

      // 画面を調整
      google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

      // キャプチャボタンが押されるまで待機
      await new Promise((resolve) => capture.onclick = resolve);

      const canvas = document.createElement('canvas');
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext('2d').drawImage(video, 0, 0);
      stream.getVideoTracks()[0].stop();
      div.remove();
      return canvas.toDataURL('image/jpeg', quality);
    }
    ''')
  display(js)
  data = eval_js('takePhoto({})'.format(quality))
  binary = b64decode(data.split(',')[1])
  with open(filename, 'wb') as f:
    f.write(binary)
  return filename
from IPython.display import Image
try:
  filename = take_photo()
  print('Saved to {}'.format(filename))
  
  # キャプチャしたフレームを表示
  display(Image(filename))
except Exception as err:

  # 下記に該当する場合、エラー表示:
  # ・ウェブカメラを持っていない
  # ・ページにアクセスの許可を承諾していない
  print(str(err))

JavaScript側とPython側の処理をざっくりとまとめると

js側:内蔵カメラの映像とcaptureボタンを表示
    captureボタンを押した時の画像を切り取ってPython側に渡す
py側:受け取ったフレームを表示

このコード内で、外付けカメラを使うために変更すべき箇所はJavaScript内のこの部分です.

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

{video: true}
ここでは、使用するデバイスを引数で指定しています.
カメラを使いたいのでvideoの部分はOKですが
trueだと、デフォルトで内蔵カメラを指してしまいます.

今回は内蔵カメラではなく外付けカメラを使いたいので、
外付けカメラのIDを取得して、true部分に置換する必要があります.

「じゃあ、どうやってカメラのIDを調べるのさ!」↓

外付けカメラのIDを取得しよう

どうやるんだい!?
まずは、js内に下記のコードを追加します.

if (!navigator.mediaDevices?.enumerateDevices) {
        console.log("enumerateDevices() not supported.");
      } else {
        // 利用できるカメラやマイクのID一覧を表示
        navigator.mediaDevices
          .enumerateDevices()
          .then((devices) => {
            devices.forEach((device) => {
              console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
            });
          })
          .catch((err) => {
            console.error(`${err.name}: ${err.message}`);
          });
      }
コード追加後(どこに追加すればいいのかわからない方向け)👈
from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode

def take_photo(filename='photo.jpg', quality=0.8):
  js = Javascript('''
    async function takePhoto(quality) {
      const div = document.createElement('div');
      const capture = document.createElement('button');
      capture.textContent = 'Capture';
      div.appendChild(capture);

##########追記部分#############################################
    if (!navigator.mediaDevices?.enumerateDevices) {
        console.log("enumerateDevices() not supported.");
    } else {
        navigator.mediaDevices
          .enumerateDevices()
          .then((devices) => {
            devices.forEach((device) => {
              console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
            });
          })
          .catch((err) => {
            console.error(`${err.name}: ${err.message}`);
          });
    }
##############################################################

      const video = document.createElement('video');
      video.style.display = 'block';
      const stream = await navigator.mediaDevices.getUserMedia({video: true});

      document.body.appendChild(div);
      div.appendChild(video);
      video.srcObject = stream;
      await video.play();

      // Resize the output to fit the video element.
      google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

      // Wait for Capture to be clicked.
      await new Promise((resolve) => capture.onclick = resolve);

      const canvas = document.createElement('canvas');
      canvas.width = video.videoWidth;
      canvas.height = video.videoHeight;
      canvas.getContext('2d').drawImage(video, 0, 0);
      stream.getVideoTracks()[0].stop();
      div.remove();
      return canvas.toDataURL('image/jpeg', quality);
    }
    ''')
  display(js)
  data = eval_js('takePhoto({})'.format(quality))
  binary = b64decode(data.split(',')[1])
  with open(filename, 'wb') as f:
    f.write(binary)
  return filename

このコードを実行すると、デバイスIDがデベロッパーツールのコンソールに出力されます.実行して確認してみましょう!

どこにデベロッパーツールがあるかわからないという方向け👈

MacOS, Chromeの場合:
 ①画面上部の表示をクリック
 ②開発/管理にカーソルを当てる
 ③デベロッパーツールをクリック

windowsや他のブラウザの方はご自身で調べていただけるとありがたいです!🙇

スクリーンショット 2023-10-01 5.39.27.png

矢印部分に外付けカメラのIDが出力されています!

デバイスIDを取得できたところで、
次は、どうやってデバイスIDをコードに組み込むのか? ですね.

デバイスIDを指定

コードはこのようになります.

変更前

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

変更後

const stream = await navigator.mediaDevices.getUserMedia(
        video: {
          deviceId: { 
            exact: "416d352c2e3efdf7c92b3ae3f777a7cc4936bd6e153ef35882236911daffb2f6"
          }
        }
      });

あるいは、下記のように、新たに変数を宣言して分けても大丈夫です.

var constraints = {
        video: {
          deviceId: { 
            exact: "416d352c2e3efdf7c92b3ae3f777a7cc4936bd6e153ef35882236911daffb2f6"
          }
        }
      };
const stream = await navigator.mediaDevices.getUserMedia(constraints);

取得したデバイスIDを" "(ダブルクオーテーション)で囲み、
exact: の後に書くことで使用するデバイスを指定できます.

これで外付けカメラを使う準備OK!Good!

実行してみよう

いざ!!!!!!
[実行結果]
映像映し出し中↓
スクリーンショット 2023-10-01 17.14.15.png

Captureボタン押した後↓
スクリーンショット 2023-10-01 17.14.44.png

OK!
最近ハマってるねり梅ちゃんに出演してもらいました.ありがとう

エラーが出てしまったら

OverconstrainedError

と怒られたら、デベロッパーツールを開いて、コードに書いたデバイスIDとコンソール上のデバイスIDを見比べてみよう!
実は、デバイスIDは時間経過で勝手にIDが違うものに変わる仕様になっています.
pythonに怒られてもあなたは何も悪くないです!

注意
デバイスIDは時間が経つと変わってしまうため、うまく動作しなかった時はデベロッパーツールでIDを確認してみよう.

おまけ

今回紹介したカメラの機能で、こんなこともできるよ〜というご紹介です.

from IPython.display import display, Javascript
from google.colab.output import eval_js
from base64 import b64decode

def take_photo(filename1='photo1.jpg', filename2='photo2.jpg', quality=0.8):
  js = Javascript('''
    async function takePhoto(quality) {
      const div1 = document.createElement('div');
      const div2 = document.createElement('div');
      const capture = document.createElement('button');
      capture.textContent = 'Capture';
      div1.appendChild(capture);

      // デバイスIDを取得
      if (!navigator.mediaDevices?.enumerateDevices) {
        console.log("enumerateDevices() not supported.");
      } else {
        navigator.mediaDevices
          .enumerateDevices()
          .then((devices) => {
            devices.forEach((device) => {
              console.log(`${device.kind}: ${device.label} id = ${device.deviceId}`);
            });
          })
          .catch((err) => {
            console.error(`${err.name}: ${err.message}`);
          });
      }
      // Camera 1
      const video1 = document.createElement('video');
      video1.style.display = 'block';
      var constraints1 = {
        video: {
          deviceId: {
            exact: // ここにデバイスIDを書く
          }
        }
      };
      const stream1 = await navigator.mediaDevices.getUserMedia(constraints1);


      //Camera 2
      const video2 = document.createElement('video');
      video2.style.display = 'block';
      var constraints2 = {
        video:{
          deviceId: 
                exact: // ここにデバイスIDを書く
        }
      };
      const stream2 = await navigator.mediaDevices.getUserMedia(constraints2);


      document.body.appendChild(div1);
      document.body.appendChild(div2);
      div1.appendChild(video1);
      div2.appendChild(video2);
      video1.srcObject = stream1;
      video2.srcObject = stream2;
      await video1.play();
      await video2.play();

      // 画面調整
      google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

     // captureボタン押されるまで待機
      await new Promise((resolve) => capture.onclick = resolve);

      const canvas1 = document.createElement('canvas');
      canvas1.width = video1.videoWidth;
      canvas1.height = video1.videoHeight;
      canvas1.getContext('2d').drawImage(video1, 0, 0);
      stream1.getVideoTracks()[0].stop();
      div1.remove();

      const canvas2 = document.createElement('canvas');
      canvas2.width = video2.videoWidth;
      canvas2.height = video2.videoHeight;
      canvas2.getContext('2d').drawImage(video2, 0, 0);
      stream2.getVideoTracks()[0].stop();
      div2.remove();

      const canvas = [canvas1.toDataURL('image/jpeg', quality), canvas2.toDataURL('image/jpeg', quality)];
      console.log(canvas1.toDataURL('image/jpeg', quality));
      console.log('2dayo~~~');
      console.log(canvas2.toDataURL('image/jpeg', quality))

      return canvas;
    }
    ''')
  display(js)
  data = eval_js('takePhoto({})'.format(quality))
  print(data)
  binary1 = b64decode(data[0].split(',')[1])
  binary2 = b64decode(data[1].split(',')[1])

  with open(filename1, 'wb') as f:
    f.write(binary1)
  with open(filename2, 'wb') as f2:
    f2.write(binary2)
  return filename1, filename2
from IPython.display import Image
try:
  filename1, filename2 = take_photo()
  display(Image(filename1))
  display(Image(filename2))
  
except Exception as err:
  print(str(err))

2つのカメラから同時にフレームを取得するコードです.
pythonでマルチスレッドを使わずに2台同時に動かすことができます.
unnamed.png

最後に

ここまでお付き合いくださりありがとうございました!

今回の部分少し手こずったので(互換性のないSafariを使っていたのが悪い)、同じように悩む方が少しでも減ったら幸いです.

そういえば、
colab上でYOLOv5のリアルタイム検出を実装している記事は、探した限り見つかりませんでした.
要因として、Colab上でYOLOv5はコマンドラインでウェブカメを利用できないことが挙げられます.
ですが、pytorchと今回のフレーム取得を合わせてコーディングすればColab上でも実装できます!!!できました!!!!
またの機会に紹介できればと思います!!!

参考サイト

・Navigator.getUserMedia()
  https://developer.mozilla.org/ja/docs/Web/API/Navigator/getUserMedia

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?