react-webcamのスクリーンショットで取得する画像データのサイズを小さくしてみた

2021.10.19

こんにちは、CX事業本部 IoT事業部の若槻です。

react-webcamを使用すると、Reactアプリにカメラ撮影機能を簡単に実装することができます。

今回は、react-webcamのスクリーンショットで取得する画像データのサイズを小さくしてみました。

現状の実装

下記のようなアプリケーションの実装があります。(こちらのエントリで実装したものとほぼ同じものです。)

import { useRef, useState, useCallback } from 'react';
import Webcam from 'react-webcam';
import { makeStyles } from '@material-ui/core/styles';
import './styles.css';

const useStyles = makeStyles(() => ({
  webcam: {
    position: 'absolute',
    top: '0px',
    left: '0px',
    visibility: 'hidden'
  }
}));

export const App = () => {
  const classes = useStyles();

  const [isCaptureEnable, setCaptureEnable] = useState<
    boolean
  >(false);
  const webcamRef = useRef<Webcam>(null);
  const [url, setUrl] = useState<string | null>(null);
  const capture = useCallback(() => {
    const imageSrc = webcamRef.current?.getScreenshot();
    if (imageSrc) {
      setUrl(imageSrc);
    }
  }, [webcamRef]);

  return (
    <>
      <header>
        <h1>カメラアプリ</h1>
      </header>
      {isCaptureEnable || (
        <button onClick={() => setCaptureEnable(true)}>
          開始
        </button>
      )}
      {isCaptureEnable && (
        <>
          <div>
            <button onClick={() => setCaptureEnable(false)}>
              終了
            </button>
          </div>
          <div>
            <Webcam
              audio={false}
              width={720}
              height={360}
              ref={webcamRef}
              screenshotFormat="image/jpeg"
              videoConstraints={{
                width: 720,
                height: 360,
                facingMode: 'user'
              }}
              className={classes.webcam}
            />
          </div>
          <button onClick={capture}>キャプチャ</button>
        </>
      )}
      {url && (
        <>
          <div>
            <button
              onClick={() => {
                setUrl(null);
              }}
            >
              削除
            </button>
          </div>
          <div>
            <img src={url} alt="Screenshot" />
          </div>
        </>
      )}
    </>
  );
};

この実装でreact-webcamのスクリーンショットにより取得される画像は、下記のようなピクセルサイズ720 * 360のデータとなります。

データサイズは約44KBです。

課題

アプリケーションではこの取得した画像データ(約44KB)をサーバーに送信するのですが、その際にボトルネックとなったのが、AWS Lambda関数の非同期呼び出し時のペイロードの上限256KBです。

画像3,4枚程度であれば大丈夫でしたが、それ以上の枚数を扱うユースケースがあったため、見直しをする必要性が出てきました。

対処

そこで取得するスクリーンショットの画像を縮小する対応を行いました。

アプリケーションのコードを下記のように変更します。react-webcamのwidthheightプロパティの値をそれぞれ2/3とします。

            <Webcam
              audio={false}
              width={540}
              height={270}
              ref={webcamRef}
              screenshotFormat="image/jpeg"
              videoConstraints={{
                width: 720,
                height: 360,
                facingMode: 'user'
              }}
              className={classes.webcam}
            />

するとスクリーンショットの画像もピクセルサイズ540 * 270となります。

またデータサイズも約30KBに軽減できました。

補足

videoConstraintsプロパティについて

react-webcamには他にもvideoConstraintsというプロパティがあります。このプロパティはWebAPIのMediaDevices.getUserMedia()で使用する「MediaTrackConstraints」に対応するものです。getUserMedia()を使用するとWebカメラやマイクから「メディアストリーム」を取得できます。メディアストリームの種類には音声(オーディオストリーム)と動画(ビデオストリーム)があり、動画の場合はWebカメラからストリームを取得する際の「制約」(MediaTrackConstraints)を設定できます。制約にはmax(最大値)、min(最小値)、ideal(理想値)、exact(必須値)を使用できます。

例えば下記であればwidthが理想が1280、最小が320、最大が1920でビデオストリームを取得します。

{
  video: {
    width: { min: 320, ideal: 1280, max: 1920 },
  }
}

ちなみに下記のように指定した場合は既定でidealとなります。

{
  video: {
    width: 1280,
  }
}

widthプロパティとheightプロパティについて

一方で今回設定をしたwidthプロパティとheightプロパティは、react-webcamが取得したビデオストリームを投影する「Video」要素の縦横のピクセル値を表します。

今回のreact-webcamの実装の場合

以上を踏まえて、今回の当初のreact-webcamの実装である下記の場合は、「720 * 360のビデオストリームがWebカメラから取得され、720 * 360のVideo要素に投影される」という設定となります。

            <Webcam
              audio={false}
              width={720}
              height={360}
              ref={webcamRef}
              screenshotFormat="image/jpeg"
              videoConstraints={{
                width: 720,
                height: 360,
                facingMode: 'user'
              }}
              className={classes.webcam}
            />

せっかくなので色々パターンを試してみます。

下記のようにvideoConstraintsのheightをwidthに合わせて大きくすると、

            <Webcam
              audio={false}
              width={720}
              height={360}
              ref={webcamRef}
              screenshotFormat="image/jpeg"
              videoConstraints={{
                width: 720,
                height: 720,
                facingMode: 'user'
              }}
              className={classes.webcam}
            />

このような720 * 720のスクリーンショットが取得できます。widthとheightはそれぞれ720と360ですが、react-webcamにおいてはwidthの指定のみがVideo要素のサイズに反映されるようです。

下記のようにvideoConstraintsのheightをwidthより大きくすると、

            <Webcam
              audio={false}
              width={720}
              height={360}
              ref={webcamRef}
              screenshotFormat="image/jpeg"
              videoConstraints={{
                width: 360,
                height: 720,
                facingMode: 'user'
              }}
              className={classes.webcam}
            />

このような縦長の720 * 1440のスクリーンショットが取得できます。ビデオストリームでのwidthは360ですが、これがVideo要素への投影時に2倍の720に引き伸ばされ、また比例してheightも2倍の1440となっています。

下記のようにwidthプロパティを1/2にすると、

            <Webcam
              audio={false}
              width={360}
              height={360}
              ref={webcamRef}
              screenshotFormat="image/jpeg"
              videoConstraints={{
                width: 360,
                height: 720,
                facingMode: 'user'
              }}
              className={classes.webcam}
            />

このような縦横ともに1/2の360 * 720のスクリーンショットが取得できます。

参考

以上