카메라 스트림 분석 - dingdongdengdong/astra_ws GitHub Wiki

사용자님께서 index.js 파일을 제공해주셨습니다. 이 파일을 통해 웹 페이지에서 카메라 스트림이 어떻게 처리되고 표시되는지, 그리고 원격 조종 카메라가 어떤 것인지 더 명확하게 이해할 수 있습니다.

index.js를 통한 카메라 스트림 처리 분석

index.js 파일에서 start() 함수 내의 pc.addEventListener('track', ...) 부분을 보면, WebRTC peer connection을 통해 수신되는 비디오 트랙을 처리하는 로직이 있습니다:

  pc.addEventListener('track', function (evt) {
    if (evt.track.kind === 'video') {
      if (evt.transceiver.mid === '0') {
        document.getElementById('video-head').srcObject = new MediaStream([evt.track]);
        document.getElementById('video-head').play();
      } else if (evt.transceiver.mid === '1') {
        document.getElementById('video-wrist-left').srcObject = new MediaStream([evt.track]);
        document.getElementById('video-wrist-left').play();
      } else if (evt.transceiver.mid === '2') {
        document.getElementById('video-wrist-right').srcObject = new MediaStream([evt.track]);
        document.getElementById('video-wrist-right').play();
      } else {
        toastr.error("Unsupported mid")
      }
    }
  });

이 코드는 다음과 같이 동작합니다:

  • evt.transceiver.mid === '0': mid가 '0'인 비디오 트랙은 video-head HTML 요소에 연결됩니다.
  • evt.transceiver.mid === '1': mid가 '1'인 비디오 트랙은 video-wrist-left HTML 요소에 연결됩니다.
  • evt.transceiver.mid === '2': mid가 '2'인 비디오 트랙은 video-wrist-right HTML 요소에 연결됩니다.

webserver.py에서 pc.addTransceiver를 호출할 때, head 트랙에 mid: 0, wrist_left 트랙에 mid: 1, wrist_right 트랙에 mid: 2가 할당됩니다. 따라서 index.jswebserver.py가 보내는 비디오 스트림을 올바른 <video> 태그에 매핑하는 역할을 합니다.

index.js의 원격 조종 카메라 관련 부분

index.js 파일에서 사용자님의 "내 원격 조종 카메라" (컴퓨터 웹캠)와 직접적으로 관련된 부분은 capture() 함수와 canvas-imshow 요소입니다.

index.html에서 Start Capture 버튼은 capture() 함수를 호출합니다. index.jscapture() 함수는 다음과 같은 중요한 역할을 합니다:

async function capture() {
  // ... (생략) ...

  const captureMediaStream = await navigator.mediaDevices.getUserMedia({ video: {
    facingMode: "environment",
    width: { exact: 640 },
    height: { exact: 360 },
  } });

  const captureVideo = document.createElement('video');
  captureVideo.srcObject = captureMediaStream;
  await captureVideo.play();

  // ... (생략) ...

  const interval = setInterval(() => {
    // ... (생략) ...
    try {
      if (typeof(pedalChannel) === 'undefined') {
        return;
      }
      if (typeof(handChannel) === 'undefined') {
        return;
      }
      
      const res = process_image(captureVideo, cv); // OpenCV.js를 통해 이미지 처리
      if (res === null) {
        return;
      }
      const { 
        camera_matrix_data, 
        distortion_coefficients_data, 
        corners_data, 
        ids_data, 
        canvas_image_data, 
        error_message,
        detection_timing_ms,
      } = res;

      if (corners_data !== null && ids_data !== null) {
        handCommTarget.dispatchEvent(new CustomEvent('message', { detail: [
          camera_matrix_data,
          distortion_coefficients_data,
          corners_data,
          ids_data
        ]}));
        // ... (생략) ...
        handChannel.send(JSON.stringify([
          camera_matrix_data,
          distortion_coefficients_data,
          corners_data,
          ids_data
        ])); // handChannel을 통해 데이터 전송
      }
      // ... (생략) ...
      cv.imshow('canvas-imshow', canvas_image_data); // 캔버스에 이미지 표시
      // ... (생략) ...
    }
    // ... (생략) ...
  }, 30); // 약 30FPS로 처리
}

핵심 내용:

  1. navigator.mediaDevices.getUserMedia({ video: ... }): 이 부분이 웹 브라우저가 사용자 컴퓨터의 웹캠(video0에 해당)에 접근하여 비디오 스트림을 가져오는 코드입니다.
  2. process_image(captureVideo, cv): 캡처된 웹캠 비디오(captureVideo)를 OpenCV.js(cv)를 사용하여 로컬에서 처리합니다. 이 처리 과정에서 ARUCO 마커(corners_data, ids_data) 등을 감지하여 원격 조종을 위한 데이터를 추출합니다.
  3. cv.imshow('canvas-imshow', canvas_image_data): 처리된 이미지는 canvas-imshow라는 ID를 가진 <canvas> 요소에 표시됩니다. 이것이 바로 웹 페이지의 "Capture Playback" 섹션에 나타나는 영상입니다.
  4. handChannel.send(JSON.stringify(...)): corners_dataids_data (ARUCO 마커 정보)가 감지되면, 이 데이터는 handChannel이라는 DataChannel을 통해 webserver.py로 전송됩니다. 이는 사용자님의 손 움직임이 로봇에 제어 신호로 전달되는 방식입니다.

결론

"총 4개의 카메라 데이터 중 맨 오른쪽 위에 있는 카메라 데이터가 내 원격조종 카메라입니다"라고 말씀하신 부분과 index.js를 종합해 볼 때, 다음과 같습니다:

  • 원격 조종 카메라 (사용자 컴퓨터 웹캠): index.html의 "Capture Playback" 섹션 아래 canvas-imshow에 표시되는 영상입니다. 이 카메라는 navigator.mediaDevices.getUserMedia를 통해 브라우저가 직접 접근하며, 그 영상은 로컬에서 OpenCV.js로 처리되어 손 제스처를 위한 hand 데이터 채널을 통해 로봇으로 제어 신호를 보냅니다. 이 영상은 webserver.py를 통해 스트리밍되는 다른 3개의 로봇 카메라와는 별개입니다.
  • 로봇에 달린 3개의 카메라: 이 카메라들은 webserver.py를 통해 웹으로 스트리밍되어 video-head, video-wrist-left, video-wrist-right <video> 태그에 각각 표시됩니다. video-head는 메인 화면에, video-wrist-leftvideo-wrist-right는 그 아래에 나란히 표시될 것입니다.

따라서, teleop_web_node.py를 통해 실행되는 전체 시스템은 video_head (ROS 토픽 cam_head/image_raw에서 오는 데이터)를 웹으로 스트리밍하는 것과 동시에, 웹 페이지에서 로컬 웹캠을 사용하여 손 제스처를 인식하고 이를 hand 데이터 채널을 통해 로봇으로 전송하는 두 가지 형태의 카메라 활용을 병행하고 있습니다.

⚠️ **GitHub.com Fallback** ⚠️