차곡차곡 성 쌓기
article thumbnail
더보기

지난 2023년 진행했던 OnePIC 프로젝트에 대해 내가 담당했던 파트 부분에 대해 기록을 남기려 한다. 해당 프로젝트는 졸업 작품으로 시작했다가 공개 SW 대회까지 출전한 프로젝트로 나에게 의미가 크다.

 

OnePIC 프로젝트의 시작

사진을 찍다보면 초점이 나가있는 경우가 있다. 평소라면 초점이 나가있으면 아쉬울 뿐이지만 중요한 대회에서 상 받은는사진, 유명한 관광지에서 찍은 사진이 초점이 나가있으면 매우 속상하다. 하지만 찍은 사진에 대해 초점을 되돌리는 방법이 없어 그 사진을 포기하게 된다.

 

우리 팀은 초점을 다시 되살릴 수 없는 것을 문제점으로 삼았고, 이를 해결할 수 있는 사진의 초점을 되살릴 수 있는 프로젝트를 안드로이드 앱으로 개발 하기로 했다.

 

▶︎ 안드로이드 결정 이유

주로 사람들은 휴대폰으로 사진을 찍고 사진을 보는 것도 휴대폰으로 보기 때문에 개발 플랫폼으로 모바일 중 하나인 안드로이드를 선택했었다. 모바일 앱 같은 경우 안드로이드 뿐 아니라 Swift, Flutter도 있어 어떤 것을 사용해야 하나 했지만 안드로이드를 선택한 이유는 팀원들의 숙력도가 세 언어 중 가장 높았고, 카메라를 직접적으로 제어하는 low API를 사용해야 했기 때문에 하이브리드 앱인 Flutter는 적합하지 않다고 판단했기 때문이다.

 

초점을 되살리는 해결책

지도교수님과 의논 끝에 초점을 다시 되돌리는 해결책으로 한 번의 촬영으로 초점이 여러 곳에 맞춰진 이미지들을 촬영하여 한 장에 담아 사진에서 사용자가 원하는 곳으로 사진의 초점을 변경할 수 있도록 하였다.

 

이때 한 장에 담는 것이 중요했는데 그 이유는 초점거리에 대한 정보를 삽입하여 관리할 수 있어야 했고, 같은 순간에 찍은 사진들을 한 그룹으로 취급하여 쉽게 관리하도록 하기 위해서였다.

 

내가 담당하게 된 부분이 이 파트로 여러 장의 사진을 한 장에 담을 수 있는 포맷을 개발하는 것이였다. JPEG, PNG 여러 사진 포맷이 존재하지만 그 중 보관하고 공유하는 목적으로 가장 많이 쓰는 JPEG 포맷을 확장하여 개발하기로 선택했다. 

 

JPEG 구조

JPEG을 확장해서 이뤄야 하는 목표는 크게 2가지였다.

  1. 초점 정보와 같은 메타데이터를 JPEG 안에 삽입할 수 있어야 할 것
  2. 파일이 깨지지 않으면서 사진의 픽셀 정보 데이터를 삽입할 수 있어야할 것

두 목표를 이루기 위해 표준 JPEG 포맷 논문을 참고하여 열심히 공부했다!  https://www.iso.org/standard/18902.html

 

ISO/IEC 10918-1:1994

Information technology — Digital compression and coding of continuous-tone still images: Requirements and guidelines

www.iso.org

생각보다 구조가 복잡해서 이해하기도 어려웠다. 단순히 메타 데이터와 픽셀 데이터로 나뉠 줄 알았는데 (사실 크게 보면 맞는 말이긴 하다) 메타 데이터 종류도 다양하고 인코딩 방식 등이 포함된 트리 구조로 되어 있어서 자세한 이해가 필요했다. 또한 이런 데이터 포맷 분야의 공부는 처음 해봐서 더 어려웠던 것 같다(+ 영어 논문..) 그래도 이렇게 구조화된 포맷에 대해 공부하고 응용 하는 경험이 신기했다.

[JPEG Standard] ISOIEC 10918_1 1993(E)

 

 

공부한 내용을 바탕으로 확장에 필요한 JPEG 포맷의 특징에 대해 정리해 보자면

 

JPEG 포맷

JPEG 파일은 프레임과 헤더로 구성된다.

JPEG 파일은 크게 헤더와 프레임으로 나뉘며 헤더에는 이미지의 메타 데이터가 저장되고, 실제 이미지는 압축하여 프레임에 저장된다.

 

 

► 응용 프로그램을 위한 APP 세그먼트가 존재한다.

 SOI 마커 뒤에는 APP0부터 APP15까지 16개의 APP(Application-specific) 세그먼트가 존재하는데 이들의 목적은 JPEG을 이용하는 응용 프로그램들이 자신만의 특화된 정보를 저장할 수 있게 하는 것이다.

 

응용 프로그램이 특화된 정보를 저장하고자 하면 하나의 APP 세그먼트를 선택하고 APP 세그먼트에 고유 식별자를 삽입한 특화된 정보를 삽입하여 활용하면 된다.

 

APP 세그먼트의 대표적인 활용 예로 디지털카메라 메타 데이터를 저장하는 EXIF 포맷은 APP1 세그먼트를 활용하고, 포토샵은 APP13 세그먼트를, AdobeAPP15 세그먼트를 활용한다

 

이 두 포인트만 알고 있으면 파일이 손상되지 않으면서 JPEG에 여러 사진 뿐만 아니라 필요한 데이터를 담을 수 있다.

 

 

JPEG을 확장한 멀티 콘텐츠 파일 포맷 All-in JPEG

All-in JPEG 포맷은 JPEG 포맷을 확장하여 아래 그림과 같이 설계하였고 JPEG 포맷에 2가지 변환을 가했다.
첫째, EOI 마커 뒤에 확장 데이터를 담는다.
둘째, APP3 세그먼트에 확장된 데이터에 관한 위치, 크기 등의 메타 정보를 담는다.

 

 

▶︎ 1. EOI 마커 뒤에 확장 데이터 담기

 파일이 깨지지 않으면서 사진의 픽셀 정보 데이터를 저장하기 위해 EOI 마커 뒤에 확장 데이터를 담았다. 여기서 확장 데이터란 이미지의 픽셀 데이터를 의미한다. 

 

JPEG 포맷은 수 십개의 세그먼트 영역으로 이루어져 있는데, 이때 각 세그먼트를 구별하기 위해 2바이트 크기의 데이터를 사용하며 이를 '마커'라고 한다. 그 중 EOI 마커는 End Of Image의 뜻으로 사진의 끝을 의미한다. 이때 대부분의 JPEG 뷰어는 EOI를 파일의 끝으로 인식하여 이후 데이터를 무시하고 파일을 해석하기 때문에 EOI 뒤에 데이터를 담아도 오류 파일이라고 인식하지 않는다. 이러한 이유로 All-in JPEG은 EOI 마커 뒤에 확장 데이터르 담는 방법을 택했고, JPEG 뷰어는 All-in JPEG 파일 또한 오류 파일이라고 인식하지 않았다.

 

 

▶︎ 2. APP3 세그먼트에 확장된 데이터에 관한 위치, 크기 등의 메타 정보 담기

EOI 마커 뒤에 담겨진 이미지들을 찾아 해석할 수 있도록 APP3 세그먼트에 이미지, 텍스트, 오디오 개수와 시작 위치 정보, 크기 등의 확장 데이터 메타 정보를 저장했다. 메타 정보를 해석할 수 있도록 먼저 아래 그림과 같이 APP3에 어떠한 데이터를 넣고, 크기는 어떻게 할지 설계를 한 다음 코드로 구현했다. 

APP 세그먼트 사용에 있어 중요한 것은 사용중인 APP 세그먼트와의 충돌을 피하고 고유한 식별자를 삽입하여 활용하는 것이였다. All-in JPEG은 비교적 사용률이 적은 APP3 세그먼트에 A,I,F (All-in JPEG Format) 고유 식별자를 삽입하여 혹시나 있을 다른 포맷과의 충돌을 피했다.

 

APP3 내용을 구현한 코드의 일부를 보자면 아래처럼 거의 바이트 단위로 버퍼에 입력하는 방식으로 구현했었다. 라이브러리를 쓸 수가 없어 구현하기는 힘들었지만 장점은 다른 언어로 쉽게 호환될 수 있다.

 

• Header class

 fun convertBinaryData(isBurst : Boolean) : ByteArray {
        val buffer: ByteBuffer = ByteBuffer.allocate(getAPP3FieldLength() + 2)
        buffer.put("ff".toInt(16).toByte())
        buffer.put("e3".toInt(16).toByte())
        buffer.putShort(headerDataLength)
        // A, i, F, 0
        buffer.put(0x41.toByte())
        buffer.put(0x69.toByte())
        buffer.put(0x46.toByte())
        buffer.put(0x00.toByte())
        buffer.put(0.toByte())
        
        buffer.put(imageContentInfo.converBinaryData(isBurst))
        buffer.put(textContentInfo.convertBinaryData())
        buffer.put(audioContentInfo.converBinaryData())
        return buffer.array()
    }

 

• ImageContentInfo class

   fun converBinaryData(isBurst : Boolean): ByteArray {
        val buffer: ByteBuffer = ByteBuffer.allocate(getLength())
        
        //Image Content
        buffer.putInt(contentInfoSize)
        buffer.putInt(imageCount)
        //Image Content - Image Info
        for(j in 0..imageCount - 1){
            var imageInfo = imageInfoList.get(j)
            buffer.putInt(imageInfo.offset)
            buffer.putInt(imageInfo.metaDataSize)
            buffer.putInt(imageInfo.imageDataSize)
            buffer.putInt(imageInfo.attribute)
            buffer.putInt(imageInfo.embeddedDataSize)
            
            // Image Content - Image Info - embeddedData
            if(imageInfo.embeddedDataSize > 0){
                for(p in 0..imageInfo.embeddedDataSize/4 -1){
                    buffer.putInt(imageInfo.embeddedData.get(p))
                }
            } // end of embeddedData
            imageInfoLog(imageInfo)
        } // end of Image Info
        // end of Image Content ...
        return buffer.array()​

 

• TextContentInfo class

// TextContentInfo의 데이터를 바이트 데이터로 변환
fun convertBinaryData(): ByteArray {
    val buffer: ByteBuffer = ByteBuffer.allocate(getLength())
    buffer.putInt(contentInfoSize)
    buffer.putInt(textCount)
    for(j in 0..textCount - 1){
        var textInfo = textInfoList.get(j)
        buffer.putInt(textInfo.offset)
        buffer.putInt(textInfo.attribute)
        buffer.putInt(textInfo.dataSize)
    }
    return buffer.array()
}

 

• AudioContentInfo class

fun converBinaryData(): ByteArray {
    val buffer: ByteBuffer = ByteBuffer.allocate(contentInfoSize)
    //Audio Content
    buffer.putInt(contentInfoSize)
    buffer.putInt(dataStartOffset)
    buffer.putInt(attribute)
    buffer.putInt(datasize)
    return buffer.array()
}

 

항상 내 코드 보면서 계속 뭔가 아쉬웠다. 이해가 쉬운 코드를 짜고 싶었는데 내가 봐도 복잡하다. 여러 번 리팩토링 했지만 실력부족으로 필요한 depth가 너무 많아졌다. 본인 객체에서 본인 일을 하도록 코드를 짰던 것이 여러 객체가 모이다 보니 너무 깊어졌다ㅜ. 실력이 좀 나아지면 깔끔한 All-in JPEG 모듈을 만들어서 배포하고 싶다!

 

 

해당 포스팅은 구현한 파트에 대해 대략적으로 적어봤는데 시간이 되면 더 자세하게 적는 것을 목표로...😆

 

 

GitHub - HINAPIA/OnePic-All-in-JPEG: JPEG 확장을 통한 멀티 콘텐츠 카메라 솔루션, One Pic All-in JPEG

JPEG 확장을 통한 멀티 콘텐츠 카메라 솔루션, One Pic All-in JPEG . Contribute to HINAPIA/OnePic-All-in-JPEG development by creating an account on GitHub.

github.com

 

728x90
profile

차곡차곡 성 쌓기

@nagrang

포스팅이 좋았다면 "좋아요" 해주세요!