전제 및 가정

지오레퍼런싱(georeference)을 하기 위해서는 다음 세 가지 정보가 꼭 필요하다.

  1. 좌표계 정보(EPSG 코드가 명시되어 있으면 가장 좋다.)
    1. 한국에서 쓰는 좌표계는 몇 가지가 있는데, 중부 원점(5186)을 가장 많이 쓰고 조선소의 경우 동부 원점으로 기술된 경우가 있다.
    2. 한국 내에서 영상의 지리 정보를 기술할 때 표준과 상관없이 기술하는 경우가 대다수이므로, 이럴 때는 다음을 확인해 보자. 한국에서 정의된 좌표계는 좌표계의 원점을 (0,0)으로 뒀을 때, 이를 좌표계 상에서 동쪽으로 얼마, 북쪽으로 얼마 이동한 가상 원점을 사용한다. 예를 들어 동쪽으로 200,000m 북쪽으로 600,000m 이동했다고 적혀있으면 epsg:5186에 해당하는 중부 원점이다.
  2. 좌표계에서의 영상의 좌상단 좌표
    1. gsd : pixel 2 geo 할 때 1pixel이 좌표계 상에서는 얼마에 해당하는지 그 비율을 서술함

1. 이미지를 georeference 하기

목적

  • 아예 지리 정보가 없는 이미지를 지도 위에 놓기
    • 예 : inference 생성 결과는 단순 이미지일 뿐, 좌표계 정보가 없다.
  • 파이썬으로 처리할 수 있는 방법이 없을까?

원리

  • GCP를 지정해서 지리정보가 없는 이미지와 그 이미지를 놓을 위치 간의 참조를 만든다.
  • 평면 대 평면을 매핑하면 된다.
  • 특정 지역을 매핑하는 것이 아니라면, 이미지의 가장자리 4개 꼭지점과 이미지를 놓을 지도 위 영역 가장자리 4개 꼭지점을 매핑한다.
  • 매칭 순서
    • 최소 점 3개가 필요하다(평면을 이루는 최소 단위)
    • 픽셀 좌표계 x좌표 ⇒ longitude
    • 픽셀 좌표계 y좌표 ⇒ latitude
    • ccw인지 cw인지는 상관없고 하나로 통일해서 매칭하면 된다
  • 주의할 점
    • 두 좌표계 간 원점이 다르다 : 픽셀 좌표계는 원점이 좌상단, 위경도 좌표계는 원점이 중앙에 위치한다. (원점은 0,0)
    • 경도 값은 중간을 0으로, 북반구가 양의 값으로 90까지 증가하고 남반구가 음의 값으로 -90까지 감소한다.
    • 픽셀 좌표계는 상단에서 하단으로 값이 증가한다.
    • 위 두 사항 때문에 좌표값을 매칭할 때 y값과 경도 값을 매칭하는 일이 헷갈린다. 아래 그림에서도 볼 수 있듯 좌상단 꼭지점의 y값은 '값'으로 봤을 때 max값이다.



- 참고 자료 : [https://www.earthdatascience.org/courses/use-data-open-source-python/intro-raster-data-python/fundamentals-raster-data/raster-metadata-in-python/](https://www.earthdatascience.org/courses/use-data-open-source-python/intro-raster-data-python/fundamentals-raster-data/raster-metadata-in-python/)

구현 코드

from osgeo import gdal, ogr, osr

def georef_inference(raster_path, infer_path) :
    '''
    raster_path : 대상이 되는 원본 영상의 경로
    infer_path : 생성한 inference의 경로
    '''

    '''
    원본 영상에서 inference에 매칭할 기준점들 추출(가장자리 4개 점)-> epsg코드 파싱으로 얻기 -> 원본 영상이랑 inference랑 대응되는 점들 gdal.GCP 생성으로 mapping
    -> SetGCP로 inference georeference 하기
    '''
    origin_ds = gdal.Open(raster_path)
    geoTransform = origin_ds.GetGeoTransform()
    minx = geoTransform[0]
    maxy = geoTransform[3]
    maxx = minx + geoTransform[1] * origin_ds.RasterXSize
    miny = maxy + geoTransform[5] * origin_ds.RasterYSize
    origin_points = [minx, miny, maxx, maxy]

    crs_str = origin_ds.GetProjectionRef()
    res = crs_str.replace("[",',').replace("]",',').replace('\"',',').split(',')
    num_list = [x for x in res if x.isdecimal() == True]
    crs_num = num_list[-1]

    target_ds = gdal.Open(infer_path)
    sr = osr.SpatialReference()
    sr.ImportFromEPSG(int(crs_num))
    target_maxx = target_ds.RasterXSize
    target_maxy = target_ds.RasterYSize
    target_points = [0, 0, target_maxx, target_maxy]

        '''
        geotransform[0] = top left x
        geotransform[1] = w-e pixel resolution
        geotransform[2] = 0
        geotransform[3] = top left y
        geotransform[4] = 0
        geotransform[5] = n-s pixel resolution (negative value)
        '''

    gcps = [gdal.GCP(origin_points[0],origin_points[3],0,0,0),
            gdal.GCP(origin_points[0],origin_points[1],0,0,target_points[3]),
            gdal.GCP(origin_points[2],origin_points[1],0,target_points[2],target_points[3]),
            gdal.GCP(origin_points[2],origin_points[3],0,target_points[2],target_points[1])
           ]
    target_ds.SetGCPs(gcps, sr.ExportToWkt())
    target_ds.SetGeoTransform(origin_ds.GetGeoTransform())
    target_ds = None

2. 처음부터 georeference 하기

목적

  • 이미지 생성시 georeference하는 방법
  • 다른 이미지를 읽어와서 생성하는 게 아니라, array에 담긴 데이터를 georeference 정보를 담아서 저장

원리

코드

'''
    original_img_path : georeference 정보를 가져올 이미지의 경로
    img_write_path : 이미지를 저장할 경로
  im_array : numpy array로 저장된 데이터
'''
def save_img_with_georef(original_img_path, img_write_path, im_array) :
    original_img = gdal.Open(original_img_path)
        # 이미지는 c,h,w의 형태로 들어오는 것을 기대한다. 
    num_channel = im_array.shape[0]
    nrows = im_array.shape[1]
    ncols = im_array.shape[2]

        # gdal에서 파일 생성(create)이 지원되는 드라이버는 geotiff가 대표적이다.
    driver = gdal.GetDriverByName('GTiff')
    output_img = driver.Create(img_write_path, ncols, nrows, num_channel ,gdal.GDT_Int16, ['COMPRESS=LZW'])

    output_img.SetGeoTransform(original_img.GetGeoTransform())
    for i in range(1, num_channel+1) :
        output_img.GetRasterBand(i).WriteArray(im_array[i-1])

    output_img.SetProjection(original_img.GetProjection())
    output_img = None

3. 이미지 좌표 → 지도 좌표

목적

패치 단위로 이미지를 읽어와 얻어낸 이미지 좌표를 지도 좌표로 바꾸고자 할 때

원리

패치의 x, y offset만큼 평행이동하고 (픽셀 : 실제 거리) 간의 비율인 gsd를 곱해 이미지의 크기를 조정한다.

gdal에서 geotransform을 얻을 때 좌상단의 x/y좌표, 이미지의 가로/세로 gsd를 구할 수 있다.

이를 이용해서 변환에 필요한 행렬을 계산한다.

코드

Affine transformation

'''
from shapely import affinity 
from shapely.geometry import Polygon
from shaeply.geometry import Point
'''

def canvas2WorldCoord(geom, geo_transform,xoff=0, yoff=0) :
    tl_x, x_gsd, _, tl_y, _y, y_gsd = geo_transform

    geom = affinity.affine_transform(geom, [1, 0, 0, 1, xoff, yoff])
    return affinity.affine_transform(geom, [x_gsd, 0, 0, y_gsd, tl_x, tl_y])

4. 이미지 + gis 정보가 metadata로 따로 있을 때

목적

이미지를 전달 받았는데, 이미지는 이미지 대로 찍고 georeference 정보는 이미지와 별개로 다른 파일/시스템에 저장되어 있는 경우에 이미지에 georefence 적용하기

원리

이 글의 대전제 부분에도 명시했지만, 이미지의 좌상단 점을 기준으로 원본 좌표계에 먼저 자리를 잡고 gsd에 따라 영상의 비율을 수정하는 작업을 거친다. 좌표계를 모른다면 답이 없다.

코드

from osgeo import gdal, osr
from typing import List

def set_geo_reference(input_path : str , output_path : str , epsg_code : int , geo_transform : List[float]) : 
    input_ds = gdal.Open(input_path, gdal.GA_Update)
    input_ds.SetGeoTransform(geo_transform)
    srs = osr.SpatialReference()
    srs.ImportFromEPSG(epsg_code)
    input_ds.SetProjection(srs.ExportToWkt())
    input_ds = None

def get_epsg_code(path : str) -> str :
    ds = gdal.Open(path)
    prj = ds.GetProjection()
    prj = osr.SpatialReference(wkt=prj)
    return int(prj.GetAuthorityCode(None))

def set_reproject(input_path : str, output_path : str , target_epsg_code : int) : 
    cmd_dict = {'input_path' : input_path, 'output_path' : output_path, 'source_srs' : f'{get_epsg_code(input_path)}', 'target_srs' : f'EPSG:{target_epsg_code}'}
    base_command = 'gdalwarp {input_path} {output_path} -s_srs {input_srs} -t_srs {output_srs}'
    process = subprocess.run(base_command.format(input_path=cmd_dict['input_path'],output_path=cmd_dict['output_path'], input_srs=cmd_dict['source_srs'], output_srs=cmd_dict['target_srs']), shell=True, check=True)

설명

  1. set_geo_reference
  2. 인자로 받아들인 epsg code와 geo_transform을 통해 georeference를 하는 함수
  3. get_epsg_code
  4. georefence가 된 영상의 epsg code를 얻어내는 함수
  5. set_reproject
  6. georeference가 된 영상을 다른 좌표계로 reprojection 시키는 함수

환경 설정

혹시 본인이 가지고 있는 docker에서 gdal 2.4.0을 사용하고, pyproj 3.2 미만 버전을 사용한다면

error 6: unable to load proj.4 library (libproj.so), creation of ogrcoordinatetransformation failed.

라는 에러를 볼 수 있다. 이 때 해결법은 더 높은 버전의 pyproj를 깔아보고, 그래도 안되면 다음을 실행한다. proj의 개발 버전을 까는 명령어이다.

sudo apt update
sudo apt install libproj-dev

5. 아예 아무 정보도 없을 때

목적

crs(srs)정보가 누락된 경우, 이를 원본 영상에 할당하고 싶을 때.

코드

gdal_edit.py -a_srs EPSG:4326 {파일 이름}

Q1.  왜 블로그가 텅텅 비었소?

A. 정리를 딴 데다 하고 있었다. 

 

이 블로그를 잊고 있었다...! 라기 보다는

구현 결과라던가 업무 후 나오는 팁들은 모두 

사내 노션에다 메뉴얼화 해서 정리하고 있었다. 

이 메뉴얼이 꽤 쌓이다 보니, 메뉴얼들을 조각모음 할 필요성도 느끼고

나도 여태 구글링으로 여러 사람들의 팁을 잘 받았기 때문에

공리주의 차원에서 여기에 공유를 하겠다. 

 

Q2. 그래서 22년까지 뭐 했는데?

A. 인생 제 3막, 절찬리에 시작! 

 

인생 1막, 대학교 까지는 부모님이 낳아주고 키워주셨다면

인생 2막, 대학원-20대 중후반(왜 인생 2막이라 하냐면 많은 것을 배웠고, 바꿨고, 바꿈당했기 때문)

에서는 GIS라는 낯선 세계로 발을 들이게 된다. 

인생 1막은 놀랍게도 기억이 뜨문뜨문하다. 생각해보면 너무 공부벌레로만 살아서 그런 것 같다. 

(그럴 일은 전무해서 하는 말이지만) 다시 돌아간다면 반항도 좀 하고, 더 넓은 세상을 보려고 하고, 

(장학금만 아니었다면) 학점에'만' 그렇게 목을 매지 않았을 거다. 

 

인생 2막

첫 회사에서 이직하기 전까지는 완전히 공간 정보의 세계를 유영했다. 

대학원에서는 OGC 데이터들, geometry 데이터들 등 데이터 처리와 설계, 실증을 많이 했었다. 

오랫만에 본 기하학과 데이터 설계는 차곡 차곡 무언가를 착착 정리하거나 과정을 따라가서 새로운 것을 만드는 

내 성격 상 꽤 마음에 들었다.

기하의 정의, 실제 상황에서의 실증, 나아가서 이 데이터 구조가 이전의 어떤 데이터 처리 체계 혹은 관리 체계를 개선할 수 있는가. 

생각지도 못한 일도 많이 했다. 

첫 직장에서도 돌아보면 짧은 기간에 많은 경험을 했다. 

2D 위에서만 놀 줄 알았는데 첫 직장에서 사수님이 전해주신 3D 데이터 처리 지식을 배울 수 있었다. 

물론 그래픽스 전공이 아니라서 3D 데이터 저장이라던가 처리, 시각화 정도에 그치지만.

거기에 미니 프로젝트 PM겸 1인 개발자까지(실질적으로) 해보면서 대략 프로젝트가 어떻게 굴러가는지 몸으로 떼우면서 배웠다. 

무수한 할말들이 남아 있지만, 배웠던 좋은 경험을 꼽아보라면 한 사람이 모든 것을 다 해야 하는 상황에서 어떻게 해야 하는지

PM과 개발자가 같은 사람이 된다면 어떤 일이 벌어지는지 등. 두 번 다시는 이런 일은 내 사전에 없게 만들고 싶지만 세상은 녹록치 않더라. 

인생 2막을 거치면서 저연차에 비기너였지만 짧은 기간 내에 참 다양한 경험을 했다. 

 

 

컴퓨터 전공인데 왜 대학원 때 spatial database를 선택했냐. 매우 특이하다. 는 말을 많이 들었다. 

지금도 많이 듣는다. 공간정보학 전공이 컴퓨터를 배워서 개발하는 경우는 있지만 역의 경우는 거의 드물다고. 

나도 학석 둘다 같은 컴퓨터공학을 나왔고, 석사는 데이터 과학과 엔지니어링을 반반 섞은 듯한 느낌이다. 

(다시 한번 말하지만 석사'도' 컴퓨터공학이다. 연구실 이름에 낚이지 말라.)

 

본론으로 돌아가서 내가 왜 GIS를 선택했냐 질문을 받을 때 늘 대답하는 말이 있다. 

'사람의 삶은 공간이 규정한다. 삶과 공간의 개념은 떼어놓을 수 없다.'

 

현재진행형, 인생 3막.

인생 3막, 20대 후반부터 현재 진행중. 

공간정보 관련 일을 할 수 있다면 어디던 상관없다고 생각했다.

내가 더 성장할 수 있는 곳, 그리고 내가 성장함이 곧 회사의 성장을 의미하는 곳. 

내가 회사에 어떤 기여를 할 수 있는지 치열하게 생각할 테니 회사도 내게 성장의 기회를 달라. 

그렇게 해서 대전의 위성영상 관련 스타트업에 취업하게 되었다. 

완전히 새로운 곳. 

심지어 대전은 대학교때 딱 한번 가보고 두번째였다. 

작년 여름부터 합류해서 지금 겨울까지 거진 1년 반이 다 되어간다. 

원래 하던 C++와 Javascript를 뒤로 하고 Python을 집어드니 참 어색했다. 

새 오피스, 밝은 톤의 회사 오피스(대부분 하얀색), 새 동료, 새 일들. 

처음에 닥친 일도 '내가 할 수 있을까?' 수없이 고민하고 밤을 지새웠는데 결국 해냈다. 

그 과정에서 느낀 것은, 

좋은 엔지니어는 어떤 조건을 갖춰야 하는가?

예전 같았으면 뭐가 됬던 고객이 원하는 기능을 꾸역 꾸역 개발하는 엔지니어가 능력있는 게 아닐까? 생각했지만

지금은 조금 바뀌었다. 

 

1. (고객과) 소통할 줄 알아야 한다.
2. 요구사항을 현재 상황과 자원, 고객의 뜻에 따라 구현할 수 있도록 정의할 수 있어야 한다.
3. 주어진 자원과 현재 상황 내에서 빠르게 서비스를 제공할 수 있게 설계 및 구현해야 한다. (최신 기술 도입이 전부는 아니다!)
4. 숲도 중요하다. 나무도 중요하다. 헷갈린다면 다시 숲을 바라보자. 전체 그림을 잘 잡자.

특히 3번에 대해 많이 배웠다. 

이전에는 다루던 데이터 용량이 크지 않았는데, 이제는 제약이 걸리면서 어떻게 하면 효율적으로 처리할 수 있는지 머리를 굴리면서

동시에 현 가용 자원으로 어떻게 고객이 원하는 바를 데드라인까지 수행하는지를 예전보다 많이 고려하게 되었다. 

화려한 기술이 아니더라도.

 

그래서 지금은 대전에서 GIS 데이터 엔지니어로 vector, raster 데이터 다루면서 살고 있다. 

데이터 전처리, 2D 데이터 기반 공간 연산, 공간 데이터 분석 및 시각화까지. 

 

Q3.  다시 블로그로 돌아온 이유는?

A. 여태까지 한 거 정리 싹 다 하고, 이제는 좀 나누려고. 

어느정도 익숙해지니 기술적으로 한 단계 더 나아가고 싶었다.

그래서 내부에만 두던 메뉴얼을 GIS 엔지니어가 아닌 일반 개발자, 엔지니어가 봐도 이해가 잘 되게끔 정리 하려고 한다. 

글이 뭉텅이로 올라온다면 전에 쓴 메뉴얼을 재조립했을 가능성이 있다. 

메뉴얼은 내가 다음에 같은 일을 할 때, 내가 이전에 고려했던 선택지와 당시 상황을 돌아보고 빠른 판단을 내리기 위해 작성하고 있다. 

이 블로그를 찾는 사람들이 내 메뉴얼, 혹은 기술문서들을 읽고 자신에게 맞는 선택지를 찾을 수 있길 바란다. 

'Develope' 카테고리의 다른 글

[Javascript] 빈 문자열과 조건문  (0) 2022.01.24

GIS에서 흔하게 볼 수 있는 데이터 포멧은 shp, kml, geojson이 있는데 구글어스에 뭔가를 올려서 보고싶다면 kml을 피할 수 없다.

xml 문서답게 kml은 그 트리 구조부터 attribute까지 사람을 헷갈리게 하는 경우가 많다.

그래서 이번에 특정 조건 하에 kml 파일을 대량 생산하면서 배운 것들을 여기에 정리하려 한다.

1. 쓰기

이전에 코드를 받아 써서 simplekml으로 파일을 썼는데 주의할 점이 있다.
루프당 kml을 생성한다면 반드시 kml 문서 instance를 초기화했는지 확인하자.
그렇지 않다면 나처럼 눈덩이처럼 불어버린 kml을 마주할 수 있게 된다.

'''
- key(str) : 문서 식별자
- input_df(dataframe) : kml로 쓸 내용들을 담은 dataframe
- root_folder_path : kml 문서들을 저장할 폴더 주소
'''
def create_single_kml_file(key, input_df, root_folder_path) : 
    // 이 아래에서 이렇게 kml 문서(xml문서로 치면 root) instance를 생성한다. 
    kml = simplekml.Kml()
    for i in range(len(input_df)) : 
        row_name = input_df.iloc[i]['name']
        f = kml.newfolder(name = row_name)
        ...
        pol = f.newpolygon(name='Polygon')
        pol.name= row_name
        pol.outerboundaryis=input_df.iloc[i]['geometry'].exterior.coords
        pol.description=description
        ...

    kml_name = f'aimo_label_{create_date}_{key}.kml'
    kml.save(f'{root_folder_path}/{kml_name}')

2. 읽기

simplekml은 문서 생성만 가능하다.
그래서 찾아보니 pykml이라는 것이 있어, 이걸로 읽어봤다.
문서 생성은 simplekml이, 범용성은 pykml이 좋아보인다.
pykml로 문서를 생성하려면 xml의 트리 구조와 문서의 기본 문법을 다 알아야 하기 때문이다.
pykml이 lxml에 기반해서 문서를 생성하면 simplekml은 이를 class로 감싸서 생성을 더 용이하게 만든 느낌..?
앞서 생성한 kml파일에 문제가 생겨서 이를 읽어서 수정할 일이 생겨서 읽었다. 다음과 같이 읽는다.

import os 
from pykml import parser

p = os.path.join(root_folder, k)
with open(p) as f:
# read root of doc
    doc = parser.parse(f)
    # read the folders under root 
    for folder in doc.getroot().getchildren()[0].getchildren() : 
    ...

3. 수정 및 반영

수정

파일 2개를 생성했는데 각 파일에 중복으로 들어간 정보가 있어서 조건에 따라 이를 제거하기로 했다.
폴더 단위로 겹치는 정보가 있었다.
트리 구조에서 정보를 제거하는 것과 같이, 해당 정보를 제거하기 위해서는 상위 요소로 올라가서 상위 요소에서 해당 정보를 제거하면 된다.
이 때 상위 요소를 parent, 해당 정보를 children이라 부른다.

for k in kml_list : 
    with open(os.path.join(root_folder,k)) as f:
        doc = parser.parse(f)
        for folder in tqdm(doc.getroot().getchildren()[0].getchildren()) : 
            if folder.name not in id_list :
                id_list.append(folder.name)
            else : 
                folder_parent = folder.getparent()
                folder_parent.remove(folder)
        doc_list.append(doc) 

반영

트리 구조를 살려서 저장해야 하기 때문에 lxml을 써서 트리를 string으로 변환한 후 저장한다.
etree.tostring(doc_list[i],encoding = "unicode", pretty_print=True)
에서 encoding="unicode"가 없으면 etree.tostring의 결과는 byte로 저장된다.

from lxml import etree
for i in range(len(doc_list)) : 
    with open(os.path.join(root_folder,kml_list[i]),'w') as f: 
        f.write(etree.tostring(doc_list[i],encoding = "unicode", pretty_print=True))

4. 결론

  • 나는 kml만 생성하면 돼! : simplekml
  • 나는 kml 읽고 쓰기 다 해야 해 : pykml + lxml

GIS 엔지니어로 일하다 보면 공간 정보와 지도는 뗄래야 뗄수 없는 관계이기 때문에 지도 위에서 데이터 시각화를 많이 한다. 

나도 2차원에서는 openlayers나 leaflat을, 3차원은 Cesium.js를 다뤘다. 이들의 공통점은 모두 javascript 기반으로 구현했다는 점이다. 그래서 javascript도 다루고, java도 다루고, c++도 다뤘던 적이 있었다. 지금은 python까지 하고 있지만. 

 

지인이 공부하다가 다음의 경우를 물어봤다. 

빈 문자열은 조건문에서 어떻게 다뤄질까?

답은 null, undefined, 빈문자열 모두 false를 리턴하게 된다. 

 

const test1 = ''
const test2 = ""

const name = "hyemi"

const res1 = test1 || name
const res2 = test2 || name
const res3 = null || name
const res4 = undefined || name

넷 모두 결과는 name값이 나오는 것을 확인할 수 있다. 간단한 배움! 

참고한 글에 따르면 문자열이 비었는지 확인하려면 다음과 같이 구현하길 권장한다. 

 

if( !name ) : console.log("no name")

 

https://negabaro.github.io/archive/js-isEmpty

'Develope' 카테고리의 다른 글

[번외] 22년 근황  (0) 2022.11.13

PostGIS에 저장되어있는 Polygon에 대해 가장 긴 변의 길이를 구하고 싶다는 요청이 들어왔다. 

이에 구글링하면서 레퍼런스를 찾았고 그 결과에 대해 좀 더 덧붙여서 기록하려고 한다. 

 

원본

레퍼런스 : https://stackoverflow.com/questions/7595635/how-to-convert-polygon-data-into-line-segments-using-postgis

 

SELECT ST_AsText( ST_MakeLine(sp,ep) )
FROM
   -- extract the endpoints for every 2-point line segment for each linestring
   -- call two points as start point and end point from each boundary(linestring)
   (SELECT
      ST_PointN(geom, generate_series(1, ST_NPoints(geom)-1)) as sp,
      ST_PointN(geom, generate_series(2, ST_NPoints(geom)  )) as ep
    FROM
       -- extract the individual linestrings
       -- ST_Dump : multi-part to single-parts ex) Multipolygon to a set of polygons
      (SELECT (ST_Dump(ST_Boundary(geom))).geom
       FROM mypolygontable
       ) AS linestrings
    ) AS segments;
  • ST_Dump : 한 feature를 이루고 있는 하위 단계의 feature들이 존재한다면 ST_Dump는 이를 하위 단계의 feature들의 집합으로 변환해서 내보낸다. multipolygon의 경우 여러 polygon들이 모여서 multipolygon을 이루기 때문에 ST_Dump(multipolygon)의 결과는 polygon의 집합이다. 
  • generate_series : generate_series(start, stop, step) 
  • ST_PointN : N번째 점을 얻는 함수. 

2개의 view가 사용되어 좀 정신없을 수 있는데 차근 차근 보면 view의 결과를 엮을 수 있을 것이다. 

 

예시코드

SELECT ST_length( ST_MakeLine(sp,ep) )
FROM
   -- extract the endpoints for every 2-point line segment for each linestring
   (SELECT
      ST_PointN(geom, generate_series(1, ST_NPoints(geom)-1)) as sp,
      ST_PointN(geom, generate_series(2, ST_NPoints(geom)  )) as ep
    FROM
       -- extract the individual linestrings
      (SELECT (ST_Dump(ST_Transform(ST_Boundary(ST_Polygon('LINESTRING(75 29, 77 29, 77 25, 75 25, 75 29)'::geometry, 4326)), 3857))).geom

       ) AS linestrings
    ) AS segments;

결과

222638.98158654384
499901.41056706756
222638.98158654384
499901.41056706756

ST_length는 대상의 좌표계의 unit에 따라 길이를 계산하기 때문에, meter로 계산하려면 3857로 transform해줘야 한다. 

20201231

 

글쓰기 시간, git에 대해서 배우는 시간, python jupyter notebook을 접한 날.

오후에는 git에 대해서 간략하게 내가 설명하는 시간을 가졌다. 내가 누구에게 설명할 자격이 있을까. 

 

20210104

 

오전시간 : fundamental node 수행 시간

 

파이썬의 기초에 대해서 다루는 노드였는데, 의외로 어려운 개념들이 있었다. 

 

1. 부동소수점 표현방식

IEEE의 표준을 따라서 bias를 채택해서 부동소수점을 표현하는 방식을 사용하면 다음과 같이 부동소수점을 bit에 담게 된다. 

1비트는 부호, 8비트는 지수, 32비트는 가수부에 사용된다. 

그리고 지수값을 저장할 때, 지수값을 2의 보수법으로 바꾸고 거기에 bias(127)을 더한다. bias는 2^8-1의 값. 

thrillfighter.tistory.com/349

2. 0.1 + 0.2 == 0.3 의 결과

놀랍게도 false였다. 왜냐면 1의 부동소수점 표현방식 때문에 처음에는 literal type으로 선언된 float이

계산하면서 0.3의 근접한 값을 가지게 되면서 literal 0.3과 값이 달라지게 되는 것이다.

여기에서 실제로 저 값을 계산하게 되면 0.300000000004쯤의 값을 보게 된다. 

 

 

오후 시간 : C231N 3강 ~54p

2강에 대해서 복습하고 있는 시간. 

 

C231N 3강

 

저번에는 함수 f(x) -> y를 다룰 때, f에 W가 사용된다는 것 까지 도출함. 

그렇다면 구한 W가 적합한지 아닌지 스코어를 어떻게 구해내는가?

Loss-function에 대한 소개를 할 것이다. 

임의의 값 W가 적합한지를 찾고 싶은데, 이 과정을 최적화 과정이라고 한다.

 

Loss function은 이 W를 가지고 학습했을 때 라벨과 학습 결과를 비교하고 distance를 구해서,

각 경우의 값들을 모두 합해서 평균을 내는 일을 한다. Loss function의 결과를 줄이는 것이 목표. 

 

multi-class SVM : 여러 class를 다루기 위한 SVM 

어떻게 loss를 구하는가?

 

Loss function은 이 W를 가지고 학습했을 때 라벨과 학습 결과를 비교하고 distance를 구해서,

각 경우의 값들을 모두 합해서 평균을 내는 일을 한다. Loss function의 결과를 줄이는 것이 목표. 

 

정답이 아닌 클래스 스코어에 대해서

sum(max( 해당 클래스의 스코어 - 원래 클래스의 스코어 + 1 , 0 ))

 

근데 safety margin이 왜 1인가?

정답 스코어가 다른 스코어에 비해 얼마나 더 높은 값을 가지고 있느냐에 관심을 가지고 있기 때문에

(loss값이 얼마나 되는가), 

1은 별로 상관없어지고, W 행렬곱을 하기 때문에 나중에 사라진다. 

Q. 1을 주는 이유가 값을 보정하기 위해서 주는 걸까...?

Q. 정답 스코어는 왜 빠지는데요? 

A. loss function에 영향을 줄 수도 있기 때문에. 

 

L이 0이 나오더라도 W는 하나가 아닐수 있다. 2W도 답이 될 수 있다. 

최종적으로 test data에서 L이 적게 나오는 W을 구해야 한다. 

 

Regularization : L이 단순한 값을 가질 수 있도록 정리하는 역할. => 모델이 복잡해 지는 것을 막고, 복잡한 모델에 패널티를 부여하는 역할

가정 : 더 단순한 가정이 미래에 일어날 현상을 잘 설명할 수 있기 때문에, 단순한 가정을 택하는 것이 좋다. 

overfitting을 막을 수 있다. 

이 항이 있으면 저차원 모델 + soft penalty(복잡하게 나오는 모델 계속 쓰게 되면, 불리하게 된다..?)를 추가하는 것이 된다. 

 

L2 regularization : 선형 함수를 유도하기 위한 패널티

L1 regularization : -> 단순한 모델에 적합하다. 

 

cf) softmax도 자주 쓰인다. (Multinomial Logistic Regression) -> loss값에 의미를 부여하게 된다. 

loss값을 확률 분포로 구하게 된다. 정확히 예측했다면 L이 1이 나오겠지. 

exponential -> normalize과정을 거쳐 loss값을 구하게 된다. 

softmax의 최소값은 0이고 최대값은 무한대이다. 하지만 최대값이 무한대가 나올 일이 없는게, 그러면 L값이 0에 근접해야 한다. 

 

SVM에서는 정답 스코어와 아닌 값의 차이에 대해 다루고, softmax는 -log(정답 클래스)을 도출해서 해당 클래스로 분류될 확률값을 다루게 된다. softmax의 경우 정답 클래스의 결과를 최대한 높이고, 나머지 클래스의 결과를 최대한 낮추려고 한다. 

SVM은 일정 마진을 넘으면 ok인데 softmax의 경우는 계속해서 최적화하는 방향으로 나아갈 것이다. 

 

그렇다면 실제로 어떻게 W를 구할 것인가? 

=> Optimization 

 

(추후 보강 예정)

 

 

 

 

 

 

 

 

 

'Technology > AIFFEL 대전' 카테고리의 다른 글

[AIFFEL] DAY 3  (9) 2020.12.30
[AIFFEL] DAY 1-2  (6) 2020.12.30

+ Recent posts