Programming/Qt

[도서 실습] Qt 5 and OpenCV 4 Computer Vision – ImageEditor (Plugin Mechanism – Affine)

변화의 물결1 2024. 3. 13. 00:03

 

 

 

안녕하세요.

 

이미지 에디터 마지막 내용으로 Affine 함수를 사용해보려고 합니다. 이전에 이미지를 회전해 보았다면, 이제는 이지를 약간 틀어져 보이게 해 보겠습니다. 표현이 맞을지 모르겠으나, 평면의 이미지를 약간 입체적으로 보이는 느낌으로 만든다라고 할까요. ^^

 

 


 

1. 소스 코드 시작 전

 

1) 아핀 변환(Affine Transformation)

 

  선형 변환에 이동 변환까지 포함된 변환입니다. 선의 수평성을 유지하며, 변환 전의 서로 평행한 선은 변환 후에도 평행함을 의미합니다. 즉, 길이의 비와 평행성이 보존되는 변환입니다. 사각형을 평행사변형으로 변환하는 것을 아핀 변환으로 간주합니다.

 

 아핀 변환은 점 사이의 거리 비율뿐만 아니라 선의 수평성을 유지하는 모든 변환입니다. (예: 선의 중간점이 변환 후에도 중간점으로 유지됨). 그래서 길이의 비와 평행성이 보존됩니다. 그러나 반드시 거리와 각도를 유지하는 것은 아닙니다.

 

 따라서 변환, 회전, 크기 조정 등과 같이 지금까지 나온 모든 기하학적 변환은 위의 모든 속성이 이러한 변환에서 보존되기 때문에 모두 아핀 변환이라고 할 수 있습니다. 간단한 용어로 이해하려면 아핀 변환을 회전, 크기 조정 및 전단의 구성으로 생각할 수 있습니다.

 

2) 아핀 변환 방법

 

 

 

 

 

 왼쪽은 입력용, 오른쪽은 출력용으로 표현 보면, 이 변환에서 이미지의 위쪽은 변경되지 않고 이미지의 아래쪽은 이미지의 너비와 같은 거리만큼 오른쪽으로 이동했다는 것을 알 수 있습니다. 이런 변화를 아핀 변환으로 공식을 조금 알 필요가 있습니다.

 

 일반적으로 아핀 변환은 다음과 같이 선형 변환 후 벡터 추가의 형태로 표현될 수 있습니다.

 

 

 

 

 아핀 변환 행렬의 기본형은 3 × 3 행렬이지만 세 번째 행의 값은 0, 0, 1의 값을 지닙니다. 좌변의 행렬과 우변의 행렬의 세 번째 행의 값은 항상 같은 값을 지니게 되어 OpenCV에서는 2 × 3 행렬로 표현합니다.

 행렬의 x1, y1은 변환 전 원본 이미지의 픽셀 좌표를 의미하며, x2, y2는 변환 후의 결과 이미지의 픽셀 좌표를 의미합니다.

 - "C# OpenCV 강좌 : 제 21강 - 아핀 변환" 내용

 

 

 

 변환 후의 픽셀 좌표를 계산하기 위해서는 미지수 a00, a01, a10, a11, b0, b1의 값을 알아야 합니다.

여섯 개의 미지수를 구하기 위해 세 개의 좌표를 활용해 미지수를 계산합니다. 이 미지수를 구하기 위해서 원본 3 지점의 픽셀 좌표와 이동 후 3 지점의 픽셀 좌표를 이용해서 아핀 맵 행렬을 생성합니다. (getAffineTransform)

 

  마지막으로 아핀 변환을 함수를 실행합니다.

  

 

2. 소스코드 추가하기

 

 - 새로운 라이브러리 프로젝트 생성한 후, AffinePlugin으로 프로젝트명과 클래스를 생성합니다.

 

 

 

 

 - 이전 내용과 같이 OpenCV 경로와 설정과 인터페이스 파일을 만들어 추가해 줍니다.

 - name() 함수에는 메뉴에 나타날 이름을 넣어 줍니다. 

 

 

QString AffinePlugin::name()
{
    return "Affine";
}

 

 

   - edit() 함수에서 아핀 변환을 위한 6개의 좌표를 선언해 주고 이를 통해서 아핀 맵 행렬을 getAffineTransform() 함수로 구합니다. 그리고 warpAfine() 함수를 실행해서 이미지를 변환을 실행시킵니다. 최종 결과를 output 변수로 전달합니다. 

 

 

void AffinePlugin::edit(const cv::Mat &input, cv::Mat &output)
{

    cv::Point2f triangleA[3];
    cv::Point2f triangleB[3];

    triangleA[0] = cv::Point2f(0 , 0);
    triangleA[1] = cv::Point2f(1 , 0);
    triangleA[2] = cv::Point2f(0 , 1);

    triangleB[0] = cv::Point2f(0, 0);
    triangleB[1] = cv::Point2f(1, 0);
    triangleB[2] = cv::Point2f(1, 1);

    cv::Mat affineMatrix = cv::getAffineTransform(triangleA, triangleB);
    cv::Mat result;

    cv::warpAffine(
     input, result,
     affineMatrix, input.size(), // output image size, same as input
     cv::INTER_CUBIC, // Interpolation method
     cv::BORDER_CONSTANT // Extrapolation method
     //BORDER_WRAP // Extrapolation method
    );

    output = result;

}

  

 

 - warpAffine() 함수에서 마지막 인자를 cv::BORDER_CONSTANT를 바꿔 줌으로써 변환하면서 남는 공백을 어떻게 채울지 결정할 수도 있습니다.

  예로, BORDER_WRAP 값을 사용하면 보간은 이미지의 오른쪽을 사용하여 왼쪽 테두리를 채우고 이미지의 왼쪽을 사용하여 오른쪽 테두리를 채우게 됩니다. 아래의 실행결과를 참조하시면 이해가 되실 겁니다.

  

  만약 좌우 대칭으로 만들고 싶다면 아핀 맵 행렬이 그것에 맞게 나와야 하는데, 그렇게 하려면 3개 점의 좌표가 다음과 같이 입력되어야 합니다.

 

    triangleB[0] = cv::Point2f(input.size().width -1, 0);
    triangleB[1] = cv::Point2f(0, 0);
    triangleB[2] = cv::Point2f(input.size().width-1, input.size().height-1);

 

 

3. 최종 결과

 

 - build와 run을 시키면 AffinePlugin.dll 파일이 만들어지고, ImageEditor의 debug/plugin 폴더에 복사하고 ImageEditor 실행시킵니다.

 

 

  

 

 

 - 원본 이미지를 Affine 한번 실행시킬 경우 위에서 좌표를 설정했던 것처럼 외쪽 아래 지점이 오른쪽으로 이동한 것을 알 수 있습니다.

 

 

 

 

 - 좌우대칭으로 좌표를 변경할 경우 원래 이미지가 좌우 대칭된 것을 확인할 수 있습니다.

 

 

 

- 테두리 외삽법(BorderMode)을 BORDER_WRAP로 할 경우 아래와 같이 검은색 공백을 대신 원본 이미지가 보이지 않았던 부분으로 채워졌습니다.

 

 

 

 

감사합니다.

 

 

<참고 사이트>

1. [BOOK] Qt-5-and-OpenCV-4-Computer-Vision-Projects

2. C# OpenCV 강좌 : 제 21강 - 아핀 변환

https://076923.github.io/posts/C-opencv4-21/

3.TAG ARCHIVES: CV2.GETAFFINETRANSFORM()

https://theailearner.com/tag/cv2-getaffinetransform/  

 

AffinePlugin.zip
0.00MB

반응형