어안 이미지에서 직사각형으로 변환🐟📷

18097 단어 fisheyeimage
최근에 저는 Varjo VR 이어폰을 사용하고 있는데 어안 카메라 이미지를 직사각형 이미지로 전환하는 방법을 배울 수 있습니다.이 개념은 매우 간단하지만, 나는 몇 시간 동안 발버둥쳤다.그래서 나는 이 문장이 다른 비슷한 일을 하는 사람들을 도울 수 있기를 바란다.
최종 결과는 이렇다.

the photo from this page

광로 물리학


우선, 우리가 Fish-eye lends로 사진을 찍을 때, 우리는 반드시 광로의 물리 원리를 알아야 한다.내가 인터넷에서 검색해 보니 빛의 경로가 마치 다음 사진과 같다.

파란 선은 사진을 찍는 비행기이고, 붉은 선은 사진을 찍는 비행기다.우리는 가상의 반구가 우리가 빛을 이해하는 경로를 돕는다고 말할 수 있다.반구 중심에 도달하는 광로는 사진으로 포착될 것이다.광속이 반구 표면에 접촉할 때, 그것들은 굴절이 발생하여 광전 영상 검측기 (붉은 선) 에 들어간다.수직 축의 각도와 사진 평면(붉은 선)의 거리와 비례해야 한다.
우리 수학 전환을 좀 합시다.파란색 선은 변환의 결과 평면입니다.빨간색 평면은 카메라가 촬영한 영상이다.반구의 반지름은 R이고 수직 축의 각도는 Φ(대문자φ)이다.다음 그림과 마찬가지로 그림의 파란색 동그라미를 변환하는 것은 카메라 그림의 빨간색 사각형과 상응해야 한다.Φ는 파란색 점이 변환 결과 이미지의 가장자리에 있을 때의 각도입니다.파란색 점이 어디로 이동하든지 각도는 Φ보다 크면 안 된다.

우리 한 걸음 더 나아가자.지금까지 나는 2D로 설명했지만 우리는 3D로 생각해야 한다.다음 사진 보시죠.대다수의 관점은 모두 이렇다.푸른 점과 붉은 점은 각도 θ(θ)의 선상에 있어야 한다.이것은 일을 복잡하게 만들지만, 결코 그리 어렵지 않다.

파란색과 빨간색의 점은 같은 θ(θ)를 가지고 있기 때문에 우리가 점이 각 평면 중심까지의 거리를 알면 x/y 위치를 계산할 수 있다.
파란색 점에 대해서 말하자면, 바로 이렇다.

붉은 점에 대해서 말하자면, 바로 이렇다.

파란색 점과 수평면(r)의 거리는

θ의 여현과 정현은

φ(소문자φ)는

비밀 번호


우리가 쓸 코드는 파란색 점의 위치에서 빨간색 점을 찾는 것이다.최종 코드는 TypeScript에서 이렇습니다.긴장하지 마세요. 제가 잠시 후에 세부 사항을 설명할게요.
  • 주의: 앞 그림의 R은 내 코드에서 1로 설정되어 있기 때문에 R이 나타나지 않습니다.왜 당신 자신의 가설이 정확한지 생각해 보세요!
  • /**
     * @param sourceArray Source image data array
     * @param sw Source image width
     * @param sh Source image height
     * @param captureAngle Catupure angle of image. Depends on camera spec. Usually it's 2 * pi.
     * @param rw Result image width
     * @param rh Result image height
     * @param resultAngle View angle of result image
     */
    convert(
      sourceArray: Uint32Array,
      sw: number,
      sh: number,
      captureAngle: number,
      rw: number,
      rh: number,
      resultAngle: number
    ) {
        const resultArray = new Uint32Array(rw * rh)
        for (let i = 0; i < rw; i++) {
          for (let j = 0; j < rh; j++) {
            const halfRectLen = Math.tan(resultAngle / 2)
            // Result position
            const x = ((i + 0.5) / rw * 2 - 1) * halfRectLen
            const y = ((j + 0.5) / rh * 2 - 1) * halfRectLen
            const r = Math.sqrt(x * x + y * y)
            // cos(theta)
            const ct = x / r
            // sin(theta)
            const st = y / r
            // Angle from vertical axis
            const phi = Math.atan(r)
            // Source position
            const sr = phi / (captureAngle / 2)
            const sx = sr * ct
            const sy = sr * st
            // Sorce image pixel index
            const si = (sx * sw + sw) / 2
            const sj = (sy * sh + sh) / 2
    
            let color = 0x000000FF // Black
            if (si >= 0 && si < sw && sj >= 0 && sj < sh) {
              color = sourceArray[si * sw + sj]
            }
            result[i * rw + j] = color
          }
        }
        return resultArray
      }
    
    1D UINT32 배열을 사용하여 이미지 데이터(source Array 매개 변수와resultArray 변수)를 저장합니다.(RGBA 형식은 4요소 배열이 아닌 정수)
    const resultArray = new Uint32Array(rw * rh)
    
    결과 배열의 길이는 폭과 높이 (픽셀 단위) 의 곱셈이어야 한다.원본 이미지 배열의 길이는 원본 이미지 픽셀과 관련이 있어야 합니다.
    중첩 순환을 사용하여 결과 이미지의 모든 픽셀 색상을 가져옵니다.
    for (let i = 0; i < rw; i++) {
      for (let j = 0; j < rh; j++) {
        // calculation
      }
    }
    
    halfRectLen는 결과 이미지의 절반 길이를 나타낸다.픽셀의 색인에 따라 파란색 점(x, y)을 계산합니다.r는 중심에서 떨어진 거리다.
    const halfRectLen = Math.tan(resultAngle / 2)
    const x = (i / rw * 2 - 1) * halfRectLen
    const y = (j / rh * 2 - 1) * halfRectLen
    const r = Math.sqrt(x * x + y * y)
    
    -1는 필수적이다. 왜냐하면 우리는 (x, y)의 위치가 중심과 맞지만 픽셀의 인덱스는 왼쪽 상단에서 시작하기 때문이다.
    const x = (i / rw * 2 - 1) * halfRectLen
    
    그림에서 원본 그림 (x ', y') 의 대응점을 계산합니다.
    // cos(theta)
    const ct = x / r
    // sin(theta)
    const st = y / r
    // Angle from vertical axis
    const phi = Math.atan(r)
    // Source position
    const sr = phi / (captureAngle / 2)
    const sx = sr * ct
    const sy = sr * st
    
    마지막으로 원본 점을 원본 이미지 픽셀의 인덱스로 변환하고 색 값을 결과 그룹에 저장합니다.캡처 각도 매개변수가 결과 각도보다 작으면 결과 이미지에 소스 이미지에 매핑되지 않은 픽셀이 포함되어야 합니다.따라서 픽셀 인덱스가 유효한 범위에 있는지 확인하십시오.
    const si = (sx * sw + sw) / 2
    const sj = (sy * sh + sh) / 2
    
    let color = 0x000000FF // Black
    if (si >= 0 && si < sw && sj >= 0 && sj < sh) {
      color = sourceArray[si * sw + sj]
    }
    result[i * rw + j] = color
    
    카메라 이미지 파일에서 픽셀 데이터를 가져와 결과 이미지를 파일로 저장하려면 고유의 방법을 사용합니다.제가 쓰는 것은 Jimp입니다.
    나는 나의 해석이 이해하기 쉽다는 것을 믿지 않지만, 이것이 너에게 도움이 되기를 바란다.
    이것은my github repo입니다.또한 AssemblyScript로 작성된 WASM 구현도 포함됩니다.관심 있는지 확인해 주세요.😉

    좋은 웹페이지 즐겨찾기