크롬 브라우저에서만 동작합니다.
This demo works with Chrome.


브라우저에서 웹캠 연결
MDN(Mozilla Developer Network)에서 제안한 getUserMedia() API를 사용하면 자바스크립트를 통해서 로컬 웹캠에 접근할 수 있다. 문제는 이 API의 표준화가 완료되지 않았기 때문에 브라우저간 호환성이 담보되지 않는다는 점이다. 여기에서는 크롬을 사용하는 경우만을 가정한다.

navigator.webkitGetUserMedia
  "video": true
  (stream) ->
    video = document.getElementById("video")
    video.src = window.URL.createObjectURL(stream)
    video.play()
  (error) ->
    # console.log error.message
위 코드에서 webkitGetUserMedia()는 세개의 인자를 필요로 하는데 그 중에서 2번째는 웹캠에 성공적으로 접속될 때 실행되는 콜백 함수이다. video 태그를 찾아서 src 속성에 웹캠의 미디어스트림으로 부터 만든 URL을 할당하고 video.play()를 실행하면 <video>에 웹캠 영상이 보이게 된다.
웹캠 화면 서버 전송
다음은 <canvas>를 이용해서 <video> 화면을 캡쳐한 후 웹소켓을 사용해서 서버로 전송하는 기법을 소개한다.

canvas = document.getElementById("canvas")
context = canvas.getContext("2d")
# width =$('canvas').width()
# height =$('canvas').height()
setInterval ->
  context.drawImage(video, 0, 0, width, height)
  base64String = canvas.toDataURL("img/png")
  base64String = base64String.replace('data:image/png;base64,', '')

  server.emit('frame', buffer: base64String)
, 500
context.drawImage()에 의해서 <video>에서 캡쳐된 화면이 <canvas>에 렌더링된다. 다음 과정은 <canvas>의 화면을 Base64 스트링으로 인코딩된 형태로 변환하는 과정으로 canvas.toDataURL()이 사용된다. 이렇게 구해진 Base64 스트링은 웹소켓을 사용해서 서버로 전송된다. 본 데모에서는 이 모든 과정을 매 500ms 마다 반복하도록 설정하였다.
서버에서 OpenCV 처리
서버는 간단한 node.js 어플리케이션으로 형태이다. 얼굴 인식을 담당하는 OpenCV 루틴은 C++로 작성하는 대신 OpenCV의 Node 바인딩인 node-opencv를 사용하였다.

cv = require('opencv')
client.on 'frame', (data) ->
  base64Data = data.buffer
  stringBuffer = new Buffer(base64Data, 'base64') #.toString('binary')
  # fs.writeFile("out.png", binaryData, "binary")

  cv.readImage stringBuffer, (err, im) ->
    im.detectObject 'haarcascade_frontalface_alt.xml', {}, (err, faces) ->
      for i in [0...faces.length] by 1
        face = faces[i]
      im.rectangle([face.x, face.y], [face.width, face.height], [0, 255, 0], 2) if face?

      client.emit('frame', buffer: im.toBuffer())
클라이언트의 'frame' 이벤트로 부터 Base64 스트링을 추출한 후 이를 Buffer() 형태로 저장한다. OpenCV의 readImage()를 통해 이 버퍼를 읽어드리면 그 결과는 im에 저장되는데 그 형태는 OpenCV의 Matrix 형태가 된다. 다음은 detectObject()와 haarcascade_frontalface* 분류기(classifier)를 사용해서 실제로 얼굴 인식을 수행하는 과정이 뒤따른다. 이렇게 해서 얻어진 결과를 다시 클라이언트로 보내기 위해서는 Matrix 형태의 정보를 바이너리 스트링 형태로 변환하는 절차가 필요한데 node-opencv 에서는 toBuffer()를 통해서 이를 지원하고 있다.
브라우저 렌더링

server.on 'frame', (data) ->
  uint8Arr = new Uint8Array(data.buffer)

  binaryString = String.fromCharCode.apply(null, uint8Arr)
  # binaryString = ''
  # for i in [0...uint8Arr.length] by 1
  #   binaryString += String.fromCharCode(uint8Arr[i])

  base64String = btoa(binaryString)

  img = new Image()
  img.onload = ->
    context.drawImage(img, 0, 0, width, height)
  img.src = 'data:image/png;base64,' + base64String