내맘대로 공부기록.

[ Python ]

[Python] Run-Length Encoding 사용하여 이미지 픽셀 라벨링 하기.

fwanggus 2023. 2. 12. 16:18

 

 

Run-length encoding - Wikipedia

From Wikipedia, the free encyclopedia Form of lossless data compression Run-length encoding (RLE) is a form of lossless data compression in which runs of data (sequences in which the same data value occurs in many consecutive data elements) are stored as a

en.wikipedia.org

 

전체 개요

  • 단일 데이터로 복수의 데이터를 표현한다.
  • 어레이 형태로 변환하여,  Decoding 하면 특정 인덱스에서의 데이터 해석이 가능해진다.
  • Computer Vision 캐글 Competition에서 컨셉을 접하게 되었으며, Image Segmentation 문제의 제출 포맷으로도 활용된다.
  • 구체적으로는 특정 픽셀 인덱스 위치 값과, 픽셀 개수의 조합으로 Labeling 데이터를 구성하는데 사용되었으며, 실습으로  RLE 포맷을 이해할 수 있다.

 

사용해 보기

캐글의 "Severstal: Steel Defect Detection"을 활용하여 실습한다.

 

Severstal: Steel Defect Detection | Kaggle

 

www.kaggle.com

 

1. 데이터 준비 및 EncodedPixels Column 개요

 

 

  • 데이터 프레임까지 준비 후, head() 메서드로 각 Column의 값들을 출력해 본다.
  • EncodedPixels 값은 String 형태이다
  • 각 숫자 데이터는 segmentation label 이 있는 픽셀 데이터를 나타낸다
  • 즉, 해당 위치에 각 클래스에 해당되는 물체가 있다고 픽셀 데이터로 알려 주고 있는 것
  • 데이터는 순서는 픽셀 위치, 픽셀 개수 순이며, 띄어쓰기로 각 데이터를 구분한다.(규칙성이 보인다.)

 

2. Code로 적용해 보기

전체적인 흐름은, 다음 과정으로 나누어서 생각할 수 있었다.

  1. 데이터 분리하기
  2. 데이터 타입 변경 하기(String ▶ int)
  3. 픽셀 위치, 픽셀 개수 어레이로 분리하기
  4. 마스크 된 이미지 만들기(1D ▶ 2D )

2-1. 데이터 분리하기

enco1_arr = train_df.EncodedPixels[0].split(' ')
enco1_arr[:10]
# ['29102', '12', '29346', '24', '29602', '24', '29858', '24', '30114', '24']

각 어레이 요소의 데이터 타입이 string이다.

하지만, 픽셀 인덱스와 길이 값으로 사용하기 위해서는 데이터 타입 변경이 수반되어야 한다.

 

2-2. 데이터 타입 변경하기(String ▶ int) + 2-3. 픽셀 위치, 개수 어레이로 분리하기

# position : 첫번째 요소 부터 두개씩 인터벌로 가져옴
# length : 두번째 요소 부터 두개씩 인터벌로 가져옴
enco1_pos = map(int, enco1_arr[0::2])
enco1_len = map(int, enco1_arr[1::2])

원본 어레이의 요소 값이 2개씩 규칙적으로 반복되기 때문에 위와 같이 어레이를 분류해 줄 수 있다.

 

2-4. 마스크 된 이미지 만들기(1D ▶ 2D)

이 작업에서는 원본 이미지가 있으며, 해당 이미지의 사이즈(width, height)를 알고 있다고 가정한다.

해당 이미지의 변수는 'img1'으로 정의하였다.

# 기존에 정의한 이미지 데이터 정보를 이용해서, 같은 수의 픽셀을 갖는 1차원 이미지를 정의 한다.
# np.zeros 는 numpy의 이미지 매트릭스를 생성하는 메소드 이며, 모든 픽셀의 값은 0 이다.
mask_1d = np.zeros(img1.shape[0] * img1.shape[1], dtype=np.uint8)
# ndarray.flatten 을 활용하는 방법도 있을 것이다.

 

과정 2-3에서 타겟으로 하고 있는 픽셀 위치와 픽셀 갯수를 분류했다. 해당 데이터를 이용해서, 타겟 위치의 픽셀값은 255 로 대입해준다. 255 는 가장 밝은 값의 픽셀을 나타낸다.( 픽셀 범위 : 0~255 )

for po, le in zip(enco1_pos, enco1_len):
    mask_1d[po:po+le-1]=255

 

zeros 이미지에 마스킹(모든 픽셀 0의 상태, 타겟 부분만 가장 밝은 픽셀로 표시하는 것을 의미) 이 제대로 수행 됐는지 확인하기 위해, 1차원 매트릭스값의 모든 요소의 합을 구해본다.

mask_1d.sum()
# 1093440

위에서 보는 바와 같이, 원하는 위치에, 길이만큼 채워졌을 것으로 기대할 수 있다.

그럼, 이 1차원 데이터를, 원본 이미지에 맞게 reshape 해서 이미지를 완성해 본다.

 

  • reshape을 할 때 order에 주의를 기울여야 한다.
  • 공식 문서에 따르면, default는 C-like index 방식이지만,
  • 이 문제(캐글 rule)에서는 1.top ▶ down, 2.left ▶ right 방식의 인덱스 룰(RLE포맷?)을 가진다는 부분이 있다.
  • Fortran-like index order 방법을 따라야 한다.

참고한 내용은 다음과 같다.

 

Reshaping order in PyTorch - Fortran-like index ordering

In numpy, there is an ordering feature for reshaping arrays, by default it is C, but you can specify other ordering like F: a = np.arange(6).reshape((3, 2)) f = np.reshape(a, (2, 3), order='F') # F...

stackoverflow.com

 

해당 Kaggle Competition 'Evaluation' 중 해당 부분을 인용하였다.

The pixels are numbered from top to bottom, then left to right: 1 is pixel (1,1), 2 is pixel (2,1), etc.

 

고맙게도, reshape 메서드의 옵션 중에, reshape order를 지정할 수 있으며, "Fortran-like"에 해당하는 'F'를 부여함으로써 아주 쉽게 해결할 수 있다.

mask2d = mask_1d.reshape(img1.shape[0], img1.shape[1], order='F')

 


여기까지 마스크 이미지 만드는 과정은 끝이며, 결과를 확인해 보았다.

원본 이미지와 마스크 데이터를 동시에 출력해 보면 다음과 같다.

 

더욱, 분명하게 알아보기 위해서는, original 이미지에 겹쳐서 표현하는 게 좋을 것 같다.

해당 작업을 위해서, mask 이미지에 findContours 메서드를 사용하여 마스크 경계부 픽셀만 추출했다.

추출한 픽셀 데이터를 polylines 메소드를 이용하여 원본 이미지에 그려주면 다음과 같은 이미지를 출력할 수 있다.

 

반응형