<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>0ju-un.log</title>
        <link>https://velog.io/</link>
        <description>공대생주은이, 공주은 | 컴퓨터공학 | 딥러닝</description>
        <lastBuildDate>Fri, 06 Jan 2023 12:08:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. 0ju-un.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/0ju-un" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[yolov5 + deepsort를 이용한 car counting]]></title>
            <link>https://velog.io/@0ju-un/yolov5-deepsort%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-car-counting</link>
            <guid>https://velog.io/@0ju-un/yolov5-deepsort%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-car-counting</guid>
            <pubDate>Fri, 06 Jan 2023 12:08:35 GMT</pubDate>
            <description><![CDATA[<p>... 진행중 ...</p>
<p><a href="https://github.com/0ju-un/TTs_vehicle-counting-yolov5/blob/main/README.md">github</a></p>
<p>reference
<a href="https://github.com/mikel-brostrom/Yolov5_StrongSORT_OSNet">https://github.com/mikel-brostrom/Yolov5_StrongSORT_OSNet</a></p>
<p>yolov5 + deep sort 알고리즘 git clone
install requirements.txt</p>
<pre><code class="language-python">git clone --recurse-submodules https://github.com/mikel-brostrom/Yolov5_DeepSort_Pytorch.git

pip install -r requirements.txt</code></pre>
<p>객체의 중앙 표시</p>
<p>plot.py</p>
<pre><code class="language-python">def box_label(self, box, label=&#39;&#39;, color=(128, 128, 128), txt_color=(255, 255, 255)):
        # Add one xyxy box to image with label
        if self.pil or not is_ascii(label):
            self.draw.rectangle(box, width=self.lw, outline=color)  # box
            if label:
                w, h = self.font.getsize(label)  # text width, height
                outside = box[1] - h &gt;= 0  # label fits outside box
                self.draw.rectangle(
                    (box[0], box[1] - h if outside else box[1], box[0] + w + 1,
                     box[1] + 1 if outside else box[1] + h + 1),
                    fill=color,
                )
                # self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor=&#39;ls&#39;)  # for PIL&gt;8.0
                self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font)
        else:  # cv2
            p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
            pc = (int(box[0]) + int((int(box[2]) - int(box[0]))/2),int(box[1]) + int((int(box[3]) - int(box[1]))/2))
            cv2.rectangle(self.im, p1, p2, color, thickness=self.lw, lineType=cv2.LINE_AA)
            cv2.line(self.im,pc,pc,color,20)
            if label:
                tf = max(self.lw - 1, 1)  # font thickness
                w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[0]  # text width, height
                outside = p1[1] - h - 3 &gt;= 0  # label fits outside box
                p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3
                cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA)  # filled
                cv2.putText(self.im,
                            label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2),
                            0,
                            self.lw / 3,
                            txt_color,
                            thickness=tf,
                            lineType=cv2.LINE_AA)</code></pre>
<p>계수선(conting line)을 지나는 차량 수 구하기</p>
<p>track.py</p>
<pre><code class="language-python">line = [(400,500), (1000, 500)]

# Return true if line segments AB and CD intersect
def line_intersect(A,B,C,D):
    return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D)

def ccw(A,B,C):
    return (C[1]-A[1]) * (B[0]-A[0]) &gt; (B[1]-A[1]) * (C[0]-A[0])

prev_list = {}
count_list = set()
counter = 0</code></pre>
<pre><code class="language-python"># draw boxes for visualization &amp; count vehicles
                if len(outputs[i]) &gt; 0:
                    for j, (output) in enumerate(outputs[i]):
                        bbox = output[0:4]
                        id = output[4]
                        cls = output[5]
                        conf = output[6]
                        bbox_center_x = output[0] + (output[2] - output[0]) / 2
                        bbox_center_y = output[1] + (output[3] - output[1]) / 2
                        p1 = (bbox_center_x, bbox_center_y)

                        if id in prev_list.keys():
                            p2 = prev_list[id]
                            if line_intersect(p1,p2,line[0],line[1]):
                                counter += 1
                                # count_list.add(id)
                                prev_list.pop(id)
                            else:
                                prev_list[id] = p1 # update previous position
                        else:
                            prev_list[id] = p1

                        if show_vid:  # Add bbox to image
                            c = int(cls)  # integer class
                            id = int(id)  # integer id
                            label = None if hide_labels else (f&#39;{id} {names[c]}&#39; if hide_conf else \
                                (f&#39;{id} {conf:.2f}&#39; if hide_class else f&#39;{id} {names[c]} {conf:.2f}&#39;))
                            color = colors(c, True)
                            annotator.box_label(bbox, label, color=color)</code></pre>
<p>위의 코드를 추가하여</p>
<p>이전 프레임에서의 위치(p2)와 현재 위치(p1)을 이은 직선이 계수선과 교차할 경우 count가 증가하도록 함</p>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/011a3ad2-908f-4c73-a4d2-663f9b1f9684/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PyTorch를 사용하여 FPN으로 한글 손글씨에서 자모 분류하기 ~dataset 제작부터 모델 학습까지]]></title>
            <link>https://velog.io/@0ju-un/PyTorch%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-FPN%EC%9C%BC%EB%A1%9C-%ED%95%9C%EA%B8%80-%EC%86%90%EA%B8%80%EC%94%A8%EC%97%90%EC%84%9C-%EC%9E%90%EB%AA%A8-%EB%B6%84%EB%A5%98%ED%95%98%EA%B8%B0-dataset-%EC%A0%9C%EC%9E%91%EB%B6%80%ED%84%B0-%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%EC%98%88%EC%B8%A1%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@0ju-un/PyTorch%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-FPN%EC%9C%BC%EB%A1%9C-%ED%95%9C%EA%B8%80-%EC%86%90%EA%B8%80%EC%94%A8%EC%97%90%EC%84%9C-%EC%9E%90%EB%AA%A8-%EB%B6%84%EB%A5%98%ED%95%98%EA%B8%B0-dataset-%EC%A0%9C%EC%9E%91%EB%B6%80%ED%84%B0-%EB%AA%A8%EB%8D%B8-%ED%95%99%EC%8A%B5-%EC%98%88%EC%B8%A1%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Tue, 17 May 2022 14:31:25 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>졸업 프로젝트인 태블릿PC용 글씨 연습 어플에서 글씨 분석 기능을 위해 사용한 딥러닝 기술에 대해 써 본 글입니다. 
저희 프로젝트는 사용자 글씨가 잘 쓰여졌는가를 글자 크기, 비율 등으로 판단하고자하기때문에 딥러닝을 사용하여 <strong>한글 이미지에서 초성, 중성, 종성(이후 글에서는 자음, 모음, 받침으로 통일하겠습니다) 영역을 추출</strong>하였습니다.</p>
<p>프로젝트에서 딥러닝 파트를 처음 맡아보았기때문에 모르는것도 많았고 그래서 간단한 것들에서도 헤매는게 많았던 프로젝트였네요... 그래서 저의 삽질기를 바탕으로 데이터셋 생성부터 모델 학습까지의 내용을 최대한 0부터 1까지 써보았습니다.
(추후 저희 프로젝트에서 사용한 또 다른 딥러닝 모델과 모델 배포등에 대한 내용도 업로드 할 예정입니다)</p>
<p>전체 코드: <a href="https://github.com/0ju-un/pytorch-fpn-segmentation">https://github.com/0ju-un/pytorch-fpn-segmentation</a></p>
<p>개발 환경 (제 <a href="https://velog.io/@0ju-un/M1%EC%97%90%EC%84%9C-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EA%B0%96%EC%B6%94%EA%B8%B0">이전 글</a>을 참고하시면 좋습니다):</p>
<ul>
<li>Apple Silicon M1</li>
<li>Python 3.8</li>
</ul>
<br/>

<h2 id="관련연구">관련연구</h2>
<p>이번 글에서는 관련 연구에 대한 것은 짧게 요약하여 적었기때문에 이런 기술을 썼구나 정도로 읽어주시면 좋을 것 같습니다. :)</p>
<h3 id="segmentation">Segmentation</h3>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/e4234bf1-79dd-47c2-a0a4-7968e9eeddab/image.png" alt=""></p>
<p>segmentation은 이미지내에서 픽셀 단위로 해당하는 클래스로 분류하는 것을 목적으로 합니다. 
위 그림을 보면 classification, object detection, 그리고 segmentation의 차이를 한 눈에 이해할 수 있는데요.
저희 프로젝트의 경우 한글 음절에서의 자음, 모음, 받침의 위치가 필요하기때문에 세그멘테이션 기술을 적용하였습니다.</p>
<h3 id="fpn">FPN</h3>
<p>FPN은 Feature Pyramid Network의 준말입니다. 
사실 처음엔 U-NET으로 세그멘테이션을 진행하였는데요. 실제 손글씨 이미지로 추론해보니 잘못 인식되는 경우가 많았습니다. 🥲
FPN에 대해 간단히 설명해보면, FPN은 각 레벨에서 독립적으로 특징을 추출하여서 고해상도와 저해상도 특징 맵을 결합합니다. 즉 하나의 이미지에서 다양한 크기의 특징 피라미드를 얻어 객체를 탐지합니다. 이렇게 추출된 각 특징들을 upsampling한 후에 pixel-wise summation을 통해 픽셀 단위의 세그멘테이션 결과를 냅니다. 서로 다른 크기의 특징 맵에서의 예측을 통해 정확도를 높일 수 있는 것이죠 
(혹시 틀린 내용이 있다면 언제든 댓글로 말씀주세요)</p>
<p>이는 아래에서 파이토치로 구현해 볼 예정입니다 ^_^</p>
<p>글씨를 감지하려면 좀 더 전체를 보아야할 것 같아서 정확도를 높이고자 FPN 모델을 사용하였습니다.
아래는 전체 모델 구조입니다. 자세한 내용은 <a href="https://arxiv.org/pdf/1901.02446.pdf">논문</a>을 참고하시면 좋을 것 같습니다.</p>
<h2 id="dataset-생성">Dataset 생성</h2>
<p>한글 자모 이미지 인식에서 가장 힘들었던 점은 관련 데이터셋이 없었다는 것입니다.
ai허브 등에 가보면 한글 손글씨 데이터셋이 아주 잘 만들어져있지만 안타깝게도 위 데이터셋은 단어, 음절 단위까지의 어노테이션만을 제공합니다..
그래서 손글씨 폰트를 사용하여 데이터셋을 직접 만들었습니다.</p>
<p>음절 이미지 몇만장을 하나하나 라벨링하기엔 시간과 비용이 부족했기때문에 음절을 이루는 음소(초성, 중성, 종성)에 대한 마스크를 생성한 후 이를 합치는 것이 이번 데이터셋 생성의 메인 아이디어입니다.</p>
<h3 id="초성-중성-종성-요소-이미지-생성">초성, 중성, 종성 요소 이미지 생성</h3>
<p>초성, 중성, 종성 이미지를 만드는 방법은 간단합니다. 음절 이미지를 만든 후, 각 음소만 남기고 지웠습니다. &#39;가&#39; 이미지를 두 장 만들어서 ㄱ과 ㅏ를 각각 남기는 식으로요. 즉 노가다입니다.</p>
<p>애초에 음소만 이미지로 만들 수도 있겠지만, 위치와 모양이 달라 이러한 방법을 썼습니다.
예를 들어 &#39;각&#39;만 보더라도 초성의 ㄱ과 종성의 ㄱ의 위치와 모양이 다른 경우 등을 고려하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/08262ea7-53f9-4b59-b963-bfdc89b68bea/image.png" alt=""></p>
<p>이런 식으로요! </p>
<p>이미지 생성은 NAVER CLOVA의 손글씨 폰트를 사용하였습니다.</p>
<pre><code class="language-python">## 1. resize input images
input_src_list = os.listdir(input_src_dir)
for dir in input_src_list:
    if dir == &#39;.DS_Store&#39;:
        continue
    src_dir = os.path.join(input_src_dir, dir)
    img_dir = os.path.join(input_images_dir, dir)
    if not os.path.exists(img_dir):
        os.mkdir(img_dir)
    src_list = [file for file in os.listdir(src_dir) if file.endswith(&#39;.png&#39;)]
    src_list.sort()
    for i, src in enumerate(src_list):
        src_path = os.path.join(src_dir, src)
        img_path = os.path.join(img_dir, src)
        img = Image.open(src_path).convert(&#39;L&#39;)
        img = img.point(lambda p: 255 if p &lt; threshold else 0)
        img.save(img_path, &#39;PNG&#39;)</code></pre>
<p>생성한 요소 이미지는 배경은 0, 글씨는 1로 이진화 해줍니다.
이미지 크기도 224 x 224로 변경해주었습니다
흑백 이미지에서 0은 검정, 255는 흰색을 나타내기때문에 픽셀값이 170보다 낮으면 0이되도록 thresholding해줍니다.</p>
<h3 id="음절-데이터셋-생성">음절 데이터셋 생성</h3>
<pre><code class="language-python"># main.py
    for syllable in ks_list:
        character_index = jamo.getIndex(syllable) # 초성 중성 종성과 이미지위치 매핑
        img = np.zeros(img_size, dtype=np.uint8)
        mask = np.zeros(img_size, dtype=np.float32)
        # 초성, 중성, 종성 이미지 가져와 하나의 음절 이미지 생성
        for i, index in enumerate(character_index):
            input_img = Image.open(os.path.join(input_img_dir, img_list[index])).convert(&#39;L&#39;)
            input_img = np.asarray(input_img)
            # 이미지 변형
            trans_element = transform_element[i]
            input_img = trans_element(image=input_img)[&#39;image&#39;]
            # mask[i][input_img != 0] = 1
            # 이미지, 마스크 생성
            mask[input_img != 0] = i + 1
            img[input_img != 0] = 255
        transformed = transform(image=img, mask=mask)
        data.append(transformed[&#39;image&#39;])
        target.append(transformed[&#39;mask&#39;])
        img = Image.fromarray(transformed[&#39;image&#39;])</code></pre>
<p>요소 이미지에서 글씨 부분이 <strong>자음이면 1 , 모음이면 2, 받침이면 3</strong>으로 마스크를 생성합니다.</p>
<p>한글 음절은 무려 11,172자의 조합이 가능합니다. 그 중 자주 쓰이는 음절 2350자에 대해 음절 데이터를 만들었습니다.</p>
<p>넘파이를 이용해해 간단한 코드로 이미지가 0(배경)이 아니라면 라벨값으로 바꾸어 줄 수 있습니다.
<br/>
그런데 값을 그대로 합쳐버리면 모두 자음, 모음, 받침의 글씨 모양들이 모두 같은 위치, 모양으로 조합이 되어버립니다.
따라서 image augmentation으로 음절로 합치기 전후로 이미지에 변화를 줍니다.</p>
<pre><code class="language-python">import albumentations as A

## Transform Function
# for 음절 이미지
transform = A.Compose([
    A.ShiftScaleRotate(
                    shift_limit=0.03,
                    scale_limit=(-0.2, 0.2),
                    rotate_limit=0,#(-10, 10),
                    p=0.6),
    A.Affine(shear=[-5, 5], p=0.6),
    A.PiecewiseAffine(scale=0.02, p=1.0)
])
# for 자음
transform_0 = A.Compose([
    A.ShiftScaleRotate(
                    shift_limit_x=(-0.02, 0.005),
                    shift_limit_y=(0, 0.01),
                    scale_limit=(-0.2, 0.1),
                    rotate_limit=5,
                    p=0.7),
    A.PiecewiseAffine(scale=0.01, p=1.0)
])
# for 모음
transform_1 = A.Compose([
    A.ShiftScaleRotate(
                    shift_limit_x=(0, 0.02),
                    shift_limit_y=(0, 0.01),
                    scale_limit=(-0.2, 0.1),
                    rotate_limit=5,
                    p=0.7),
    A.PiecewiseAffine(scale=0.01, p=1.0)
])
# for 받침
transform_2 = A.Compose([
    A.ShiftScaleRotate(
                    shift_limit_x=(-0.005, 0.02),
                    shift_limit_y=(-0.025, -0.015),
                    scale_limit=(-0.2, 0.1),
                    rotate_limit=5,
                    p=1.0),
    A.PiecewiseAffine(scale=0.01, p=1.0)
])
transform_element= [transform_0, transform_1, transform_2]</code></pre>
<p>augmentation은 albumentations을 사용하였습니다.
자음, 모음, 받침 변형은 위치와 크기에만 변화를 주었습니다.(회전을 주면 글씨가 중구난방이 되어버리더군요...)
자음같은 경우 너무 내려가면 안되는 등 한글이 가지는 기하학적인 특징이 있기때문에 각각에 대한 transform 함수를 정의해주었습니다.</p>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/a9eb7618-39e1-41d1-9c3f-6ee749eb3fe5/image.png" alt=""></p>
<p>짠! 데이터셋이 만들어졌습니다. 제법 손으로 쓴 글자같지않나요?
합쳐지면서 받침등이 너무 겹쳐 글시 같지 않게 나온 경우들은 삭제하여 데이터셋을 정제하였습니다.
손글씨 폰트 25종 X 음절 2350 = 총 58,750장의 자음, 모음, 받침으로 라벨링된 음절 이미지들을 생성하였습니다.</p>
<pre><code class="language-python">from sklearn.model_selection import train_test_split

## split train, val, test
print(len(data), len(target))
x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.1, shuffle=True, random_state=34)
x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.11, shuffle=True, random_state=34)
print(len(x_train), len(x_val), len(x_test))</code></pre>
<p>생성한 데이터셋은 tran, val, test으로 나누어줍니다.</p>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/6dfbde6d-7406-4de4-a12f-588f639ef017/image.png" alt="">
이런 데이터셋 폴더 안에
input_0.npy
label_0.npy
이런식으로 인풋이미지와 라벨이미지가 들어있습니다</p>
<h2 id="모델-생성---pytorch">모델 생성 - PyTorch</h2>
<p>이제 모델 만들어주어야겠죠!
참고한 깃허브는 다음과 같습니다:
<a href="https://github.com/qubvel/segmentation_models.pytorch">https://github.com/qubvel/segmentation_models.pytorch</a>
<a href="https://github.com/gasparian/multiclass-semantic-segmentation">https://github.com/gasparian/multiclass-semantic-segmentation</a></p>
<h3 id="model">model</h3>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/23871caa-7dc0-4e52-b375-fb97c9fd075a/image.png" alt="">
모델 구조는 다음과 같습니다.</p>
<p>백본은 파이토치에서 제공하는 pretrained된 resnext50을 사용하였고,
FPN으로 특징을 추출하고 segmentation을 진행합니다.</p>
<p>모델 구현은 fpn.py에 하였으며 전체 소스는 깃허브에 있습니다
너무 길기때문에 이번 글에서는 해당 코드에 대한 설명은 생략하였습니다.</p>
<pre><code class="language-python"># train.py
## 트레이닝 파라메터 설정
n_class = 3

lr = 1e-3
batch_size = 16
num_epoch = 100
num_workers = 0

mode = &quot;FPN&quot;
backbone = &quot;resnext50&quot;
</code></pre>
<p>필요한 파라미터들을 설정해 줍니다.</p>
<h3 id="dataset--dataloader">Dataset &amp; DataLoader</h3>
<pre><code class="language-python">import os
import numpy as np

from torch.utils.data import Dataset

class MyDataset(Dataset):
  def __init__(self, data_dir, transform=None):
    self.data_dir = data_dir
    self.transform = transform

    lst_data = os.listdir(self.data_dir)

    lst_label = [f for f in lst_data if f.startswith(&#39;label&#39;)]
    lst_input = [f for f in lst_data if f.startswith(&#39;input&#39;)]

    lst_label.sort()
    lst_input.sort()

    self.lst_label = lst_label
    self.lst_input = lst_input

  def __len__(self):
    return len(self.lst_input)

  def __getitem__(self, idx):
    label = np.zeros((3, 224, 224), dtype=np.float32)
    input = np.load(os.path.join(self.data_dir, self.lst_input[idx]))

    mask = np.load(os.path.join(self.data_dir, self.lst_label[idx]))
    for i in range(3):
      label[i][mask==i+1] = 1

    input = input.reshape(1, 224, 224).repeat(3, axis=0).transpose([1, 2, 0])

    if self.transform:
      input = self.transform(input)

    return [input, label]
</code></pre>
<p>파이토치에서는 데이터셋과 데이터로더 클래스를 제공하여 좀 더 편하게 데이터 샘플을 처리하도록 하고있습니다.
데이터셋을 만들면 폴더에서 인풋과 마스크를 이미지를 읽어옵니다.
현재 저희 데이터셋의 마스크는 자음:1, 모음:2, 받침:3으로 라벨링이 되어있는데요.
이를 파이토치 모델에 넣기위해선 원핫인코딩을 해주어야합니다.
모듈을 사용할 수도 있는데, 해당 코드에선 그냥 반복문으로 구현하였습니다.</p>
<p>처음엔 데이터셋을 만들 때부터 원-핫 인코딩을 적용시켰는데, 그랬더니 넘파이 파일 크기가 너무 커져 데이터셋을 읽어오는데에 문제가 있었습니다 :)..</p>
<h3 id="training">Training</h3>
<p>이제 본격적으로 학습을 해봅시다</p>
<pre><code class="language-python">## 데이터셋 생성
trans = transforms.Compose([
  transforms.ToTensor(),
  transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # imagenet
])

train_set = MyDataset(os.path.join(data_dir, &quot;train&quot;), transform=trans)
val_set = MyDataset(os.path.join(data_dir, &quot;val&quot;), transform=trans)

dataloaders = {
  &#39;train&#39;: DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=0),
  &#39;val&#39;: DataLoader(val_set, batch_size=batch_size, shuffle=True, num_workers=0)
}</code></pre>
<p>학습을 시키기 위한 데이터셋을 가져옵니다.
입력이 이미지이거나 넘파이 형식일 경우 파이토치에서 사용하는 텐서로 바꾸어주기위해 transforms.ToTensor()를 적용해주어야합니다
(배열 순서와 값을 자동으로 텐서에 맞게 조정해줍니다.)
그후 nomalization 해주는 것까지 trans 함수로 함께 지정하여줍니다.
이렇게 만든 함수를 데이터셋에 넘기면 데이터를 가져오며 해당 함수를 적용시켜줍니다.</p>
<pre><code class="language-python">## 네트워크 생성
if mode==&quot;FPN&quot;:
    model = FPN(encoder_name=backbone,
                decoder_pyramid_channels=256,
                decoder_segmentation_channels=128,
                classes=n_class,
                dropout=0.3,
                activation=&#39;softmax&#39;,
                final_upsampling=4,
                decoder_merge_policy=&#39;add&#39;)
## 네트워크 학습
model_trainer = Trainer(model=model, dataloaders=dataloaders, optimizer=optim.Adam,
                        lr=lr, batch_size=batch_size, num_epochs=num_epoch,
                        model_path=ckpt_dir, load_checkpoint=load_checkpoint)
model_trainer.start()</code></pre>
<p>네트워크를 initialize 한 후 학습을 합니다.</p>
<p>학습 관련 코드도 간단하게 살펴보겠습니다</p>
<pre><code class="language-python">    def load_model(self, ckpt_name=&quot;best_model.pth&quot;):
        &quot;&quot;&quot;Loads full model state and basic training params&quot;&quot;&quot;
        path = &quot;/&quot;.join(ckpt_name.split(&quot;/&quot;)[:-1])
        chkpt = torch.load(ckpt_name)
        self.start_epoch = chkpt[&#39;epoch&#39;]
        self.best_metric = chkpt[&#39;best_metric&#39;]

        self.net.load_state_dict(chkpt[&#39;state_dict&#39;])
        self.optimizer.load_state_dict(chkpt[&#39;optimizer&#39;])

    self.optimizer.load_state_dict(chkpt[&#39;optimizer&#39;])
        logging.info(&quot;******** State loaded ********&quot;)</code></pre>
<p>학습이 중간에 끊어질 수도 있으니 이런 식으로 모델을 로드해서 사용할 수 있도록합시다</p>
<pre><code class="language-python">    def forward(self, images, targets):
        &quot;&quot;&quot;allocate data and runs forward pass through the network&quot;&quot;&quot;
        # send all variables to selected device
        images = images.to(self.device)
        masks = targets.to(self.device)
        # compute loss
        outputs = self.net(images)
        loss = self.criterion(outputs, masks)
        return loss, outputs</code></pre>
<p>forward 함수입니다
모델에 인풋을 넣어 결과값을 받습니다.
loss는 BCEDiceLoss를 사용하였습니다.</p>
<br/>
이제 학습을 해봅시다!

<p>실제 학습은 로컬에서 할 경우 너무 오래 걸리기때문에 코랩 혹은 gpu 서버에서 진행해주었습니다.</p>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/377f77dd-4b4d-4ae3-84a9-f9e99ee2459f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/2d2c8ba6-9ee5-4cc3-9936-4519b0a6553c/image.png" alt=""></p>
<p>평가지표는 mdice와 mIoU를 사용하였습니다.
위에서 생성한 테스트 데이터셋에 대한 정확도는 다음과 같습니다</p>
<p><code>Prediction done in 38 sec.; IoU: 0.48227472603321075, Dice: 0.9645494520664216</code></p>
<p>(이상하게 iou가 유독 낮게 나오는데 당장 서비스에 적용해야하고, 추론 결과가 나쁘지않아 우선 사용하였습니다.)</p>
<h3 id="predict-sample-image">Predict Sample Image</h3>
<p>실제 손글씨 이미지를 모델에 넣고 확인해 보겠습니다
<img src="https://velog.velcdn.com/images/0ju-un/post/3b529ff8-ad10-4168-b6d4-fbbc8e386d4d/image.png" alt="">
자음, 모음, 받침으로 분류된 것을 볼 수 있습니다!</p>
<p>참고로 U-NET 모델 결과는 아래와 같습니다</p>
<p><img src="https://velog.velcdn.com/images/0ju-un/post/6b12891d-38a7-4140-9fdb-244d38b653df/image.png" alt=""></p>
<p>정확도도 그렇고 FPN이 더 개선된 것을 알 수 있습니다.</p>
<p>이렇게 FPN으로 segmentation을 해보았습니다. 😀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[M1에서 딥러닝 개발환경 갖추기 - conda, pytorch]]></title>
            <link>https://velog.io/@0ju-un/M1%EC%97%90%EC%84%9C-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EA%B0%96%EC%B6%94%EA%B8%B0</link>
            <guid>https://velog.io/@0ju-un/M1%EC%97%90%EC%84%9C-%EB%94%A5%EB%9F%AC%EB%8B%9D-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EA%B0%96%EC%B6%94%EA%B8%B0</guid>
            <pubDate>Mon, 14 Mar 2022 07:05:57 GMT</pubDate>
            <description><![CDATA[<p>Apple Silicon M1이 탑재된 맥북에서 개발 환경을 설치해보자</p>
<h2 id="파이썬-환경-구축---conda-pycharm">파이썬 환경 구축 - Conda, PyCharm</h2>
<h3 id="brew-설치">Brew 설치</h3>
<p>macOS 용 패키지 관리자 <a href="https://brew.sh/index_ko">Homebrew</a>를 설치해줍니다.</p>
<pre><code class="language-shell">% /bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;
</code></pre>
<h3 id="miniforge">MiniForge</h3>
<p>애플 실리콘 환경에서 사용하기위해선 아나콘다가 아닌 맥용 arm64(M1 맥)을 지원하는 MiniForge를 사용해야합니다. 맥용 아나콘다로 이해하면 될 것 같습니다.</p>
<pre><code class="language-shell">% brew install cask
% brew install --cask miniforge
% conda init zsh</code></pre>
<p><span style="color:gray">(참고) 설치된 MiniForge의 설치경로:
/opt/homebrew/Caskroom/miniforge</span></p>
<h4 id="가상환경-생성">가상환경 생성</h4>
<p>Conda를 통해 가상환경을 만들어 이용하자.</p>
<pre><code class="language-shell">% conda create --name [가상환경이름] python=3.8</code></pre>
<p>M1은 파이썬 3.8이상을 지원하기때문에 파이썬버전은 3.8로 해주었습니다.
Proceed ([y]/n)? 이라는 물음엔 y를 입력하면됩니다.</p>
<h3 id="pycharm">PyCharm</h3>
<p>파이썬 에디터는 파이참을 사용하겠습니다.</p>
<p><a href="https://www.jetbrains.com/ko-kr/pycharm/download/#section=mac">https://www.jetbrains.com/ko-kr/pycharm/download/#section=mac</a></p>
<p>위 사이트에 들어가 Community 버전으로 다운받아줍니다.
<br></p>
<h4 id="파이참-conda-가상환경-인터프리터-설정">파이참 Conda 가상환경 인터프리터 설정</h4>
<p>설치 후 파이참을 실행하여 New Project를 눌러 프로젝트를 생성한다.
<img src="https://images.velog.io/images/0ju-un/post/9c095330-eb8e-4ecf-ad79-82aacea87abd/image.png" alt="">(1) 하이라트되어있는 pythonProject는 프로젝트명입니다. 원하는 이름으로 변경해주세요.
(2) 인터프리터 설정을 위해 방금 Conda를 이용하여 만든 가상환경을 추가해줄겁니다. ... 를 클릭합니다.</p>
<p><img src="https://images.velog.io/images/0ju-un/post/99aeedee-f82f-4cee-a4ab-0aa0f218383f/image.png" alt=""> 우선 &#39;Conda Environment&#39; 클릭 후 아까 만든 가상환경(전 pytorch_project로 네이밍 했었습니다)을 선택해줍니다.</p>
<p><img src="https://images.velog.io/images/0ju-un/post/4792b2c6-2447-43a9-9d29-2366c8f92abe/image.png" alt=""> 터미널을 확인해보면 가상환경이 잘 설정된 것을 볼 수 있습니다.</p>
<h3 id="pytorch-설치">Pytorch 설치</h3>
<p>이제 파이참 터미널에 명령어를 입력하여 패키지들을 설치해주면됩니다. :)</p>
<h4 id="pytorch">Pytorch</h4>
<pre><code class="language-shell">% conda install -c conda-forge pytorch=1.9.0
</code></pre>
<br>

<h4 id="torchivision">torchivision</h4>
<pre><code class="language-shell">% conda install -c conda-forge torchvision=0.10.0</code></pre>
<br>

<h4 id="torchsummay">torchsummay</h4>
<pre><code class="language-shell">% conda install -c conda-forge torchvision=0.10.0
</code></pre>
<h3 id="그-외">그 외</h3>
<p>자주 쓰는 라이브러리의 설치 명령어 (추가)</p>
<h4 id="numpy">numpy</h4>
<p>import numpy as np
-- 다차원배열 처리</p>
<pre><code class="language-shell">% conda install numpy</code></pre>
<h4 id="pandas">pandas</h4>
<p>import pandas as pd
-- 데이터 분석</p>
<pre><code class="language-shell">% conda install pandas</code></pre>
<h4 id="pilpillow">PIL(pillow)</h4>
<p>from PIL import Image
-- 이미지 처리
-- torchvision과 pillow 버전 7 이상을 사용할 경우 에러가 나기때문에 6.2.1 버전을 사용했습니다.</p>
<pre><code class="language-shell">% conda install pillow=6.2.1</code></pre>
<h4 id="tqdm">tqdm</h4>
<p>import tqdm
-- 진행바표시</p>
<pre><code class="language-python">conda install tqdm</code></pre>
<h4 id="albumentations">albumentations</h4>
<p>import albumentations as A
-- Augmentation
-- <a href="https://albumentations.ai/docs/">공식문서</a> </p>
<pre><code class="language-python">conda install -c conda-forge albumentations</code></pre>
<h4 id="opencv">OpenCV</h4>
<p>import cv2
-- 컴퓨터비전에서 자주 사용</p>
<pre><code class="language-shell">% conda install -c conda-forge opencv</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[CNN을 이용하여 한글 인식하기 (1)]]></title>
            <link>https://velog.io/@0ju-un/CNN%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%ED%95%9C%EA%B8%80-%EB%B6%84%EB%A5%98%ED%95%98%EA%B8%B0-1</link>
            <guid>https://velog.io/@0ju-un/CNN%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%ED%95%9C%EA%B8%80-%EB%B6%84%EB%A5%98%ED%95%98%EA%B8%B0-1</guid>
            <pubDate>Thu, 25 Nov 2021 11:17:36 GMT</pubDate>
            <description><![CDATA[<h3 id="개인적인-서론">개인적인 서론...</h3>
<p>졸업프로젝트를 위해 이제 막 딥러닝을 공부하기 시작했습니다. 최종적으로 구현하고자하는 기능은 &#39;글씨체 교정&#39;인데요, 관련해서 저희 프로젝트를 간단하게 설명드리겠습니다.</p>
<p>ㅡ</p>
<p>저희는 [패드용 글씨체 연습 앱]을 만들고자합니다. 사용자에게 줄노트와 글씨 교본을 제공해준 후 사용자가 쓴 글을 보고 어떠한 부분을 교정해야할지 알려주는 것이 메인 기능입니다. 즉, 글자를 검출하고 자음 및 모음으로 인식을 한 후 저희가 정한 &#39;악필 기준&#39;에 따라 계산하는 과정이 필요합니다.</p>
<p>그 중 딥러닝을 통해 한글 자음 모음을 인식하기. 이것이 저의 최종 목표입니다. 하지만 문제가 있다면 제가 딥러닝에대해 아주 햇병아리라는 것입니다. 그래도 이런 말이 있지 않습니까?</p>
<h4 id="급할수록-돌아가라"><em>급할수록 돌아가라.</em></h4>
<p>..네 딥러닝에 익숙해지는 것을 목표로 차근차근 진행해보려합니다.
첫 시도는 &#39;CNN을 이용하여 한글 분류해보기&#39;입니다.
이번 포스트에서는 환경설정과 dataset에 대해 다룹니다.</p>
<hr>
<h3 id="colab">Colab</h3>
<p>코랩이란 주피터 노트북을 기반으로 웹에서 코딩을 할 수 있도록 구글에서 제공하는 서비스입니다. 구글에서 제공하는 클라우드와 가상 서버를 활용할 수 있기때문에 컴퓨터 성능에 큰 제약을 받지 않는다는 장점이 있습니다. 웹 브라우저를 통해 제어하지만 실제 코드 실행은 구글 클라우드의 가상서버에서 이루어지기 때문입니다. 무료로 GPU를 사용할 수 있는 좋은 서비스이죠!
(간단한 것들은 무료버전으로 충분하지만 무거운 작업을 수행한다면 정신 건강을 위해 유료 버전 -- Google Colab Pro을 추천합니다.)</p>
<p><a href="https://colab.research.google.com/?hl=ko">https://colab.research.google.com/?hl=ko</a>
위 링크를 통해 코랩을 사용할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/0ju-un/post/33279760-4367-42fd-bf2a-6bee8dcac3f9/image.png" alt=""></p>
<p>코랩에 노트북을 만든 후 &#39;런타임 - 런타임 유형&#39;에서 GPU 사용설정을 할 수 있습니다.</p>
<h3 id="한글-ocr에-대해">한글 OCR에 대해</h3>
<p>한글은 영어에 비해 매우 다양한 조합을 가지고 있습니다. 가능한 음절의 수는 무려 11,172자에 달합니다. 이러한 글자들을 모두 인식하는 것은 매우 낭비가 심한 일입니다. 따라서 KS X 1001 완성형에 포함되는 한글 2,350자만 사용하도록 하겠습니다. </p>
<p><em>\</em> . KS X 1001 실제로 발음 되어 한국어에서 사용빈도가 높은 글자들을 모은 것입니다.*</p>
<h3 id="dataset-생성">Dataset 생성</h3>
<p>학습에 필요한 데이터는 AI Hub에서 얻었습니다. (<a href="https://aihub.or.kr/aidata/133">https://aihub.or.kr/aidata/133</a>)
현대 한글을 가장 많이 활용하는 폰트(글자서체) 50종을 선정하여 해당 글자체의 이미지와 어노테이션 데이터를 포함한 인쇄체와 다양성을 확보하기 위해 성별, 연령층별로 손글씨 작성인력을 확보하여 직접 작성 제작한 손글씨 이미지와 어노테이션이 데이터셋에 포함되어 있습니다.</p>
<p>이제 데이터를 불러와보겠습니다.</p>
<pre><code>from google.colab import drive
drive.mount(&#39;/gdrive&#39;)</code></pre><p>구글 colab에 구글 드라이브를 마운트 해줍니다.</p>
<pre><code>import json
with open(&#39;./data/handwriting_data_info1.json&#39;) as f:
  data = json.load(f)

with open(&#39;./data/KS_2350.txt&#39;) as f:
  KS_2350 = f.read()

KS_2350 = KS_2350.split()</code></pre><p>json 라이브러리를 import하고, AI Hub에서 받아온 손글씨 데이터를 읽어옵니다.
그리고 모든 한글 음절이 아닌 2,350자만 사용할 것이기때문에 사용할 한글 음절을 따로 파일로 만들어 주었습니다.</p>
<p>그럼 데이터셋을 살펴봐볼까요?</p>
<pre><code>import json

with open(&#39;./data/handwriting_data_info1.json&#39;) as f:
  syllable_data = json.load(f)

with open(&#39;./data/KS_2350.txt&#39;) as f:
  KS_2350 = f.read()

KS_2350 = KS_2350.split()</code></pre><pre><code>syllable_data.keys() # dict_keys([&#39;info&#39;, &#39;images&#39;, &#39;annotations&#39;, &#39;licenses&#39;])</code></pre><p>AI Hub에서 제공하는 한글 손글씨 데이터는 &#39;info&#39;, &#39;images&#39;, &#39;annotations&#39;, &#39;licenses&#39;라는 key들로 이루어져 있습니다.</p>
<p>그 중 annotations를 살펴볼까요?</p>
<p><img src="https://images.velog.io/images/0ju-un/post/3e5544bd-6e0b-475e-af64-2af97da54715/image.png" alt=""></p>
<p>첫번째 데이터만 가져와보았습니다.
손글씨를 작성한 사용자와 글씨 타입, image에 접근할 id, text내용이 있네요!</p>
<p>저는 음절 단위의 태깅 데이터만 필요하기때문에 <strong>annotations 값에서 attributes[&#39;type&#39;]== &#39;글자(음절)&#39;</strong>로 골라내겠습니다.</p>
<pre><code>id = []
text = []

for i, data in enumerate(syllable_data[&#39;annotations&#39;]):
  if data[&#39;attributes&#39;][&#39;type&#39;] == &#39;글자(음절)&#39;:
    if data[&#39;text&#39;] in KS_2350:
       id.extend([data[&#39;id&#39;]])
       text.extend([data[&#39;text&#39;]])</code></pre><p>image_id와 id값이 같기때문에 id로 가져왔습니다.</p>
<pre><code>images = []

for i, ID in enumerate(id):
  if i &lt; 28345:
    Image_addr = &#39;./data/images/1_syllable/&#39;  +str(ID)+&#39;.png&#39;
  elif i &lt; 152432:
    Image_addr = &#39;/gdrive/MyDrive/Colab Notebooks/cnn/2_syllable/&#39;  +str(ID)+&#39;.png&#39;
  image = Image.open(Image_addr)
  image = image.resize((32, 32))
  img_array = np.array(image)
  images.append(img_array)</code></pre><p>이미지 파일을 불러와줍니다.
데이터 양이 많아 &#39;00001698&#39;~&#39;00192280&#39;은 1_syllable 폴더에, &#39;00200001&#39; ~ &#39;01197748&#39;은 2_syllable 폴더에 위치하고 있습니다.
이미지 크기는 32 * 32로 해주었습니다.</p>
<p>이렇게 AIHub의 데이터 중 필요한 음절만 뽑아내어 dataset을 만들었습니다.</p>
<h3 id="정수-인코딩integer-encoding">정수 인코딩(Integer Encoding)</h3>
<p>정수 인코딩은 원-핫인코딩을 위해 필요한 과정입니다.
글자는 그 자체로 index가 될 수 없기때문에 글자에 Index를 부여해주는 과정입니다.</p>
<pre><code>syllable = list(set(text))
syllable_to_index = {syllable: index for index, syllable in enumerate(syllable)}
index_to_syllable = {index: syllable for index, syllable in enumerate(syllable)}</code></pre><hr>
<h3 id="덧-anaconda에서-keras-사용하기-window">덧. anaconda에서 keras 사용하기 (window)</h3>
<p>지금까지 코랩으로 진행했지만 가상환경을 사용할 수도 있습니다. 코랩에 데이터셋을 추가하는 과정에서 데이터 누락이 일어나 아나콘다에서 데이터셋을 좀 더 다듬어보려합니다. 따라서 막간을 이용하여 아나콘다 환경구축에 대해서도 짧게 다루어보겠습니다. 참고로 위의 과정은 파일 경로를 제외하고 아나콘다에서도 동일하게 진행할 수 있습니다.</p>
<p>아나콘다는 Python 및 Numpy, Pandas, Matplotlib과 같은 데이터사이언스에서 유용한 라이브러리들을 쉽게 설치 및 관리할 수 있게 해주는 도구입니다.
가상환경을 만들어 필요한 패키지를 설치하여 같은 컴퓨터 위에서도 프로젝트를 분리하여 실행할 수 있습니다. 여러 가상환경을 구축해두고, 상황에 따라 필요한 환경을 activate하여 사용하면됩니다.</p>
<p><a href="https://www.anaconda.com/distribution/">https://www.anaconda.com/distribution/</a>
위 링크에서 아나콘다를 설치할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/0ju-un/post/1ed67095-dda5-4b94-abbd-94c07c09bef7/image.png" alt=""></p>
<p>설치가 완료되면 Anaconda Prompt를 실행시켜줍니다.</p>
<p><img src="https://images.velog.io/images/0ju-un/post/9e040bab-62f6-4176-ac36-ca55b254a1be/image.png" alt=""></p>
<p>익숙한 cmd창이 나오네요. 그럼 이제 keras를 설치하기 위한 가상환경을 만들어주도록 하겠습니다.</p>
<pre><code>conda create --name keras python=3.6</code></pre><p>&#39;keras&#39;라는 이름의 가상환경을 생성해주었습니다.
keras는 Python 2.7 ~ 3.6과 호환이 되기때문에 python은 3.6버전으로 함께 설치해주었습니다.</p>
<pre><code>conda activate keras</code></pre><p>keras 가상환경을 활성화시켜줍니다. *. 비활성화시엔 deactivate를 사용해주면됩니다</p>
<p><img src="https://images.velog.io/images/0ju-un/post/53738e72-fa45-4e65-a4d6-59b3946146bc/image.png" alt=""></p>
<p>(base)에서 (keras)로 바뀐 것을 통해 가상환경이 실행되었음을 알 수 있습니다!</p>
<p>그럼 이제 필요한 패키지들을 설치해주면됩니다.
우선 keras부터 설치해줍시다.</p>
<pre><code>conda install -c anaconda keras</code></pre><p>CPU버전으로 설치해주었습니다.
위 명령어를 입력할 경우 Tensorflow(2.x버전 -- 1.x버전과 사용법이 다르니 유의), CUDA, cuDNN이 함께 설치됩니다.</p>
<p>그 외에 필요한 패키지가 있다면 (pandas, 사이킷런 등...) keras를 설치해준 것과 마찬가지로 설치해줄 수 있습니다.</p>
<pre><code>conda install -c anaconda pandas    # 데이터 조작 및 분석에 사용    
conda install -c anaconda scikit-learn    # 사이킷런
conda install pillow            # 이미지 처리</code></pre><p>저는 우선 이렇게 설치해주었습니다.</p>
<p>이제 코드 에디터를 설치해주려는데, Jupiter notebook도 많이 사용하지만 개인적으로 spyder가 더 사용하기 편하다고 느꼈기때문에 spyder를 사용하려합니다.</p>
<pre><code>conda install spyder
spyder</code></pre><p>spyder를 설치하고 실행해줍니다</p>
<p>이제 코드를 실행할 수 있습니다. 확인용으로 keras 버전을 확인해주겠습니다.</p>
<pre><code>import tensorflow as tf
import keras as k

print(&quot;tensorflow &quot;, tf.__version__)
print(&quot;keras &quot;, k.__version__)</code></pre><p>위 코드를 통해 텐서플로우와 케라스의 버전을 확인할 수 있어야하는...데??
<span style="color: red">ModuleNotFoundError: No module named &#39;tensorflow_core.estimator&#39; for tensorflow 2.1.0</span>
라는 에러가 뜹니다.</p>
<p>모듈을 찾을 수 없다하니 spyder를 끄고 다시 cmd로 돌아가 패키지를 확인해보겠습니다. 설치된 패키지 확인은 <code>conda list</code> 명령어로 확인할 수 있습니다.</p>
<p><img src="https://images.velog.io/images/0ju-un/post/c1d992be-a702-440d-b8e0-059583157748/image.png" alt="">
현재는 버전이 같으나, 에러가 날 당시에는 tensorflow와 tensorflow-estimator의 버전이 달랐습니다. <code>conda install tensorflow-estimator=2.1.0</code>을 통해 버전을 맞춰줍니다.</p>
<p>다시 spyder를 실행하여 확인해볼까요?</p>
<p><img src="https://images.velog.io/images/0ju-un/post/3685e029-dc50-48f4-9171-73de6fdb70b1/image.png" alt="">
잘 작동하네요!
이제 가상환경에서 keras를 사용할 수 있습니다.</p>
]]></description>
        </item>
    </channel>
</rss>