from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import sys
import mxnet as mx
from mxnet import ndarray as nd
import random
import argparse
import cv2
import time
import sklearn
import numpy as np


def main(args):
    print(args.include)
    include_datasets = args.include.split(',')
    print(include_datasets)
    rec_list = []
    for ds in include_datasets:
        path_imgrec = os.path.join(ds, 'train.rec')
        path_imgidx = os.path.join(ds, 'train.idx')
        imgrec = mx.recordio.MXIndexedRecordIO(path_imgidx, path_imgrec, 'r')  # pylint: disable=redefined-variable-type
        rec_list.append(imgrec)
    if not os.path.exists(args.output):
        os.makedirs(args.output)

    all_count = 0
    img_label = 0
    id_dir = args.output
    for ds_id in range(len(rec_list)):
        id_list = []
        imgrec = rec_list[ds_id]
        s = imgrec.read_idx(0)
        header, _ = mx.recordio.unpack(s)
        assert header.flag > 0
        print('header0 label', header.label)
        header0 = (int(header.label[0]), int(header.label[1]))
        seq_identity = range(int(header.label[0]), int(header.label[1]))
        pp = 0
        for identity in seq_identity:
            # id_dir = os.path.join(args.output, "%d" % (img_label))
            # os.makedirs(id_dir)
            pp += 1
            if pp % 10 == 0:
                print('processing id', pp)
            s = imgrec.read_idx(identity)
            header, _ = mx.recordio.unpack(s)
            imgid = 0
            count = 0
            for _idx in range(int(header.label[0]), int(header.label[1])): # int(header.label[0]), int(header.label[1])
                s = imgrec.read_idx(_idx)
                _header, _img = mx.recordio.unpack(s)
                _img = mx.image.imdecode(_img).asnumpy()[:, :, ::-1]  # to bgr
                image_path = os.path.join(id_dir, "%d_%d.jpg" % (img_label, count))
                cv2.imwrite(image_path, _img)
                imgid += 1
                count += 1
                all_count += 1
                if count == 20:
                    break
            if all_count >= 10000:
                break
            img_label += 1

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='do dataset merge')
    # general
    parser.add_argument('--include', default='D:/hiw_Work/MEC/Data/face_dataset/asdf', type=str, help='')
    parser.add_argument('--output', default='D:/hiw_Work/MEC/Data/face_dataset/asdf/train_3', type=str, help='')
    args = parser.parse_args()
    main(args)

Internal Covariant Shift

Internal Covariant Shift

위의 사진과 같이 input 데이터가 각 층을 지나면서 연산 전/후에 데이터 간 분포가 달라질 수 있습니다.

Batch 단위로 학습을 하게 되면 Batch 단위간에 데이터 분포의 차이가 발생할 수 있습니다.

 

예를 들어, 고양이와 강아지를 분류하는 모델을 학습시킬 때 학습 데이터로 고양이 이미지를 '러시안 블루'종만 사용하고 테스트 데이터로 '페르시안' 종의 고양이를 분류하려고 한다면 학습 데이터의 분포와 테스트 데이터의 분포가 다르기 때문에 학습시킨 모델의 분류성능은 떨어질 것이며 이처럼 학습 데이터와 테스트 데이터의 분포가 다른 것을 covariate shift라고 부릅니다.

즉, Covariate shift는 공변량 변화라고 부르며 입력 데이터의 분포가 학습할 때와 테스트할 때 다르게 나타나는 현상을 말한다. 

 


Batch Normalization

Batch Normalization

Internal Covariant Shift 현상을 방지해주기 위해서 Batch Normalization을 사용합니다.

Batch Normalization은 각 배치 데이터의 평균과 표준편차를 이용하여 평균은 0, 표준 편차는 1로 데이터의 분포를 조정해줍니다.

 

 

 

Batch Normalization 순서

 

 

위 사진은 한경훈 교수님의 유튜브에서 가져왔습니다.

여기서 주목해야 할것이 γβ 입니다.

 

 

Batch Normalization의 식입니다.

이 식을 살펴보면, (평균: 0, 표준편차: 1) → (평균: β, 표준편차: γ) 로 바꿔주는 식입니다.

이 두 변수는 저희가 지정해주는 것이 아니라 학습을 진행하며 업데이트가 되는 가중치라고 볼 수 있습니다.

 

γβ를 사용하는 이유

γ와 β : X

평균 0, 표준편자가 1인 데이터들은 위의 사진과 같이 가운데에 몰려 있을 것입니다.

그렇다면 거의 linear하기 때문에 non linear 할 수 없습니다.

 

 

γ와 β : O

그렇기 때문에 γ β를 사용하여 non linear한 부분에 위치할 수 있도록 재배치를 해주는 것입니다.

그 모델에 더욱 적절한 위치를 위해서 train parameter 로 두는것입니다.

 

위와 같이 재배치를 이룸으로써 Gradient Vanishing 현상도 완화시켜 줄 수 있게 됩니다.

 

진행 순서 입니다.

 

 

 


TEST 시에는 어떻게 사용할까

Train을 하면서 구한 평균과 분산에서 각각의 sample 평균을 사용합니다.

예를 들어 3200개의 데이터를 학습하고 32의 배치를 가지고 학습을 진행한다고 가정하면

처음 Train의 32개의 데이터에 대한 평균과 분산은 Train이 덜된 weight와 bias에 의해 쓰레기값이라고 봐도 무방한 값들이 나올 수 있습니다.

그래서 exponential moving average를 이용하여 초기에 학습된 데이터들에 대한 평균과 분산에 대해서는 가중치를 적게주어 영향을 적게 받을 수 있도록 하고 후반부에 학습된 데이터들에 대한 평균과 분산에 대해서는 가중치를 크게 주어 사용합니다.

 

 

손으로 계산

 

 

 

※ Batch Normalization의 역전파

 

 

 

 

 

 

https://velog.io/@gibonki77/Batch-Normalization-backpropagation-%EC%9C%A0%EB%8F%84%ED%95%98%EA%B8%B0

 

https://sjpyo.tistory.com/59

 

출처:

https://www.youtube.com/watch?v=iaweeYJP4WU&list=PLBiQZMT3oSxXNGcmAwI7vzh2LzwcwJpxU&index=7 

https://gaussian37.github.io/dl-concept-batchnorm/

가중치를 0으로 설정하면?

0을 가중치로 초기화 하면 모든 뉴런들이 같은 값을 나타내고, Back Propagation 과정에서 각 가중치가 동일한 값으로 Update된다.

이 Update는 학습을 진행하면서 계속 발생되고, 결국 모델은 제대로 학습되지 않는다. 또한 이렇게 동일한 값으로 Update 되는 것은 레이어를 여러 층으로 나눈 의미를 상쇄시킨다.

 

 

 


초기값 설정의 중요성

위의 표에서 초기값이 μ라면 Gradient Descent를 진행하면서 최종적으로 x에서 멈출 확률이 높습니다.

하지만 초기값이 z라면 y에 멈출 확률이 높아지게 됩니다.

이렇듯 초기값 설정에 따라서 local minimum에 빠질 수도 있는 문제가 발생하기도 합니다.

 

 

 


평균 0, 표준편차 1

그리고 데이터를 평균 0, 표준편차 1의 weights에 5개의 sigmoid 활성화 함수에 넣는다고 가정하겠습니다.

평균 0, 표준편차 1

왼쪽 사진은 데이터들이 평균 0, 표준편차 1인 weights에 5개의 sigmoid를 통과했을때의 분포입니다.

즉 오른쪽의 sigmoid 함수의 양끝에 데이터들이 몰려 있는 것입니다.

그렇다면 빨간 부분에서의 미분값은 0에 가까우므로 Gradient Vanishing 현상이 일어나게 됩니다.

 

 

 


평균 0, 표준편차 0.01

그렇다면 평균 0, 표준편차 0.01의 데이터를 살펴보겠습니다.

평균 0, 표준편차 0.01

표준편차가 1인 weights들과는 완전 다른 분포를 보입니다.

하지만 너무 한쪽으로 데이터들이 모여 있는게 문제가 됩니다.

값들이 치우쳐져 있다는 것은 다수의 뉴런이 거의 같은 값을 출력하고 있으니 여러개의 층을 구성한 이유가 없어집니다.

즉, 표현력이 제한됩니다.

또한, 가운데 부분은 linear한 부분이기 때문에 학습이 제대호 이루어지지 않습니다.

 

※ 참고사항 ※

여기서도 학습이 반복된다면 Gradient Vanishing 현상이 발생합니다!

왜냐하면 sigmoid의 가장 큰 미분값은 0.25이기 때문에 학습이 계속 된다면 Gradient Vanishing 현상 발생!

 

 

 


Xavier 초기값

위와 같은 문제들 때문에 초기값을 설정하는 방법이 나왔습니다.

대표적으로 Xavier (자비어) 초기값이 있습니다.

Xavier initialization

여기서 n_in은 입력 뉴련수, n_out은 출력 뉴런수 입니다.

즉, 평균은 0, 표준편차는 위의 식을 따르는 겁니다.

Xavier initialization

위의 표들보다 훨씬 보기 편해졌습니다.

이러한 분포를 가지고 있으면 위의 단점들을 완화 시켜 줍니다.

 

 

 


He 초기값

활성화 함수가 ReLU 일때 He initialization을 사용하면 Xavier 보다 성능이 좋다고 합니다.

He initialization

식들을 보면 간단간단 합니다.

He 초기값은 평균 0, 표준편차는 위의 식을 따릅니다.

He initialization

 

다음은 Batch Normalization에 대해 알아보겠습니다.

 

▼ 코드

더보기
import numpy as np
import matplotlib.pyplot as plt
from math import *


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def ReLU(x):
    return np.maximum(0, x)


if __name__ == '__main__':
    x = np.random.randn(1000, 100)      # 데이터
    node_num = 100                      # 각 은닉층의 노드(뉴런) 수
    hidden_layer_size = 5               # 은닉층 5개
    activations = {}                    # 활성화 결과 저장

    for i in range(hidden_layer_size):
        if i != 0:
            x = activations[i-1]
        # w = np.random.randn(node_num, node_num) * 1       # 표준편차 1
        # w = np.random.randn(node_num, node_num) * 0.01      # 표준편차 0.01
        # w = np.random.randn(node_num, node_num) * sqrt(1.0 / (node_num + node_num))       # Xavier
        w = np.random.randn(node_num, node_num) * sqrt(2.0 / node_num)      # He

        a = np.dot(x, w)
        z = sigmoid(a)
        # z = ReLU(a)
        activations[i] = z

    # 히스토그램 그리기
    for i, a in activations.items():
        plt.subplot(1, len(activations), i+1)
        plt.title(str(i+1) + "-layer")
        plt.xlim(0.1, 1)
        plt.ylim(0, 7000)
        if i != 0:
            ax = plt.gca()
            ax.axes.yaxis.set_ticks([])
        plt.hist(a.flatten(), 30, range=(0, 1))

    plt.show()

 

 

출처:

https://www.youtube.com/watch?v=LQ8Rm09jgAE&list=PLBiQZMT3oSxXNGcmAwI7vzh2LzwcwJpxU&index=6

'Deep Learning > deep learning' 카테고리의 다른 글

04_Max Pooling  (0) 2022.04.18
03_Batch Nomalization 배치 정규화  (0) 2022.04.06
01_Optimizer 설명 및 여러 기법들  (0) 2022.03.31
Conda 가상환경 파일로 옮기기  (0) 2022.01.11
Pytorch Conv Output_Size 계산  (0) 2021.06.14

면접에서 대답을 잘못해서 다시 한번 공부를 하고자 작성하는 글입니다.

 

Optimizer 란?

 

딥러닝의 학습에서는 최대한 틀리지 않는 방향으로 학습해 나가야 합니다.

 

여기서 모델을 통해 나온 예측값이 정답과 비교해

 

얼마나 틀리는지(loss)를 알게 하는 함수가 loss function=손실함수입니다.

 

loss function의 최소값을 찾는 것이 Optimizer의 목표입니다.

2차 함수 그래프

간단한 예를 들면 위의 2차 함수의 최소값은 (0, 0)이고 현재의 위치는 임의의 위치에 있습니다.

 

임의의 위치에서 최소값의 위치를 찾아가는 방법이라고 보시면 되겠습니다.

 

여기서 최소값을 찾아가는 것을 최적화=Optimization 라고 하고

 

이를 수행하는 알고리즘이 최적화 알고리즘=Optimizer 입니다.

 

위의 알고리즘을 통해 구한 값을 이용해 가중치가 업데이트 되는 것입니다.

 

 

 


위의 그림과 같이 optimizer에는 여러가지 종류가 있습니다.

SGD부터 보겠습니다.

 

 

 


SGD

GD와 SGD는 같은 공식을 사용하지만

GD는 full-batch, SGD는 mini-batch를 이용하여 Gradient Descent를 적용합니다.

 

여기서 mini-batch를 사용하는 이유는

full-batch로 epoch마다 weight를 수정하지 않고 빠르게 mini-batch로 weight를 수정하면서 학습하기 위해서 입니다.

 

여기서 α는 learning rate라고 하고 loss function의 최저값을 찾아가는 step의 크기를 조절해주는 역할을 합니다.

https://bioinformaticsandme.tistory.com/130

위의 그림을 보면 learning rate의 중요성을 알 수 있습니다.

유튜브의 한경훈 교수님의 강의를 토대로 진행하였습니다.

 

이제 SGD를 직접 손으로 계산해 보겠습니다.

 

 

 

 


Momentum

Momentum은 경사하강법에 관성의 법칙을 적용한 것입니다.

관성의 법칙을 적용함으로써 local minimum에 빠지는 현상을 줄여줄 수 있습니다.

local minimum

 

위와 같이 local minimum에 빠지게 되면 더 좋은 학습을 진행할 수 없게 됩니다.

 

그러므로 위와 같이 관성을 이용하여 local minimum을 빠져나오는 원리 입니다.

Momentum

α : 관성계수

η : learning rate

 

손으로 계산

Momentum

 

 

 


NAG (Nesterov Accelrated Gradient)

NAG는 Momentum보다 공격적(?)으로 경사하강법을 진행합니다.

일단 공식을 보면

NAG (Nesterov Accelrated Gradient)

위와 같이 이루어져 있습니다.

공식을 보면 이전 속도만큼 더 간 후에 그 위치에서 Gradient를 계산해줍니다.

그래서 Momentum보다 더욱 공격적(?)이라고 할 수 있습니다.

 

손으로 계산

NAG (Nesterov Accelrated Gradient)

 

 

 


AdaGrad

지금까지는 일정한 learning rate를 사용해왔습니다.

여기서 AdaGrad는 learning rate를 바꿔가며 학습을 진행하는 아이디어에서 AdaGrad가 나왔습니다.

AdaGrad

시간이 지날수록 learning rate가 작아지는데

큰 변화를 겪은 변수의 learning rate는 작아지고

작은 변화를 겪은 변수의 learning rate는 커집니다.

큰 변화를 겪은 변수는 이미 최적에 가까워 졌고,

작은 변화를 겪은 변수는 최적에 아직 멀다고 생각하기 때문입니다.

 

손으로 계산

AdaGrad

 

 

 


RMSProb

위의 AdaGrad에서는 문제점이 있습니다.

학습이 오랫동안 진행될 경우 learning rate   \(\eta \frac{1}{\sqrt{h_{n}}}\) 가 너무 작아져 버려 weight가 0에 가까운 수로 수렴이 되게 되므로

학습이 더이상 진행이 되지 않습니다.

그래서 RMSprob이 나오게 되었습니다.

 

RMSProb

AdaGrad의 식과 다른 점은 γ(감마)가 추가 되었습니다.

γ의 값은 0과 1사이의 값이며, 커질수록 과거, 작을수록 현재에 더욱 치중됩니다.

 

손으로 계산

 

 

 


Adam

드디어 Adam까지 왔습니다.

Adam은 Momentum과 RMSProb 두가지 방법을 합친 것이라고 보시면 되겠습니다.

보정값

위와 같은 식으로 이루어져 있습니다.

 

손으로 계산

Adam

 

 

출처:

https://www.youtube.com/watch?v=5fwD1p9ymx8&list=PLBiQZMT3oSxXNGcmAwI7vzh2LzwcwJpxU&index=1&t=2481s

크롬 드라이브 다운로드

https://chromedriver.chromium.org/downloads

 

크롬 드라이브의 압축파일을 실행 파일과 동일한 위치에 두고 압축을 풀어준다.

그리고 코드 실행

 

from selenium import webdriver
from urllib.request import urlopen
from selenium.webdriver.common.keys import Keys
import time
import urllib.request
import os

search = "검색어"

if not os.path.isdir(search + "/"):
    os.makedirs(search + "/")

driver = webdriver.Chrome()
driver.get("https://www.google.co.kr/imghp?hl=ko&ogbl")

elem = driver.find_element_by_name("q")
elem.send_keys(search)
elem.send_keys(Keys.RETURN)

SCROLL_PAUSE_TIME = 1

last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
    time.sleep(SCROLL_PAUSE_TIME)
    new_height = driver.execute_script("return document.body.scrollHeight")

    if new_height == last_height:
        try:
            driver.find_element_by_css_selector(".mye4qd").click()
        except:
            break
    last_height = new_height

images = driver.find_elements_by_css_selector(".rg_i.Q4LuWd")
count = 1

for image in images:
    try:
        image.click()
        time.sleep(2)
        imgUrl = driver.find_element_by_xpath("//*[@id='Sva75c']/div/div/div[3]/div[2]/c-wiz/div/div[1]/div[1]/div[2]/div/a/img").get_attribute('src')
        urllib.request.urlretrieve(imgUrl, search + "/" + search + "_" + str(count) + ".jpg")
        print("Image saved: {}_{}.jpg".format(search, count))
        count += 1
    except:
        pass
        
print("Crawling End")
driver.close()

'Language > Python' 카테고리의 다른 글

json 파일 줄 맞춰 정리하기  (0) 2022.04.26
rec to image  (0) 2022.04.07
Python에서 Void Pointer 사용하기  (0) 2022.03.22
이미지 경로 리스트 만들기  (0) 2022.01.28
원하는 크기의 이미지 resize (padding 붙이기!)  (0) 2022.01.27

ctypes 이용

 

import numpy as np
from ctypes import *

 output = np.zeros(size, dtype=np.float32)
 output.ctypes.data_as(c_void_p)

이번 게시글은 GoogLeNet에 대해 알아보겠습니다.

GoogLeNet은 2014 ILSVRC Competition에서 우승을 했습니다.

하지만 우승을 한거에 비해서는 준우승을 한 VGG보다 널리 활용되지 못했습니다.

 

일단 GoogLeNet의 모델 구조 사진을 보겠습니다.

다른 모델들의 구조들과 달리 굉장히 복잡한게 눈에 보입니다.

이 모델을 보기 전에 여러 개념을 알고 가야하기 때문에 먼저 여러 개념부터 잡고 가겠습니다.

 

1 x 1 Convolution

GoogLeNet에서 사용된 1 x 1 Convolution입니다.

1 x 1 Convolution은 Feature Map의 갯수를 줄이는 목적으로 사용됩니다.

즉, Feature Map의 갯수가 줄어서 연산량을 낮추는 겁니다.

연산량을 낮추면 Layer를 더 깊게 쌓을 수 있습니다.

 

예를 들어보겠습니다.

※ Convolution 계산: https://ggongsowon.tistory.com/9

 

Example)

14 x 14 x 480 Feature Map이 있다고 가정을 해보겠습니다.

이것에 48장의 5 x 5의 Filter, padding = 2, stride = 1로 Convolution을 해주면

14 x 14 x 48 의 Feature Map이 생성됩니다.

그렇다면 이때 필요한 연산량은

※ (14 x 14 x 48) x (5 x 5 x 480) = 112,896,000 = 112.9M이 됩니다.

 

 

그렇다면 이제 1 x 1 Convolution을 사용해보겠습니다.

14 x 14 x 480 Feature Map이 있다고 가정을 해보겠습니다.

이것에 16장의 1 x 1의 Filter, padding = 0, stride = 1로 Convolution을 해주면

14 x 14 x 16 의 Feature Map이 생성됩니다.

그리고 48장의 5 x 5의 Filter, padding = 2, stride = 1로 Convolution을 해주면

14 x 14 x 48 의 Feature Map이 생성됩니다.

여기서도 똑같이 연산량을 계산해 보겠습니다.

⑴ (14 x 14 x 16) x (1 x 1 x 480) = 1,505,280 = 1.5M

⑵ (14 x 14 x 48) x (5 x 5 x 16) = 3,763,200 = 3.8M

※ ⑴ + ⑵ = 5.3M

 

두 값을 비교해보면 대략 21배 정도 차이가 납니다.

 

 

 

Inception Module

다음은 Inception Module 입니다.

GoogLeNet에는 총 9개의 Inception모듈이 사용되었습니다.

Inception 모듈은 다양한 특성 값들을 추출해내기 위한 과정이라고 볼 수 있겠습니다.

 

이제 하나를 확대해서 살펴보겠습니다.

GoogLeNet에서는 (b)를 사용하였고 1 x 1 Convolution이 추가된 layer입니다.

1 x 1 Convolution을 사용한 이유는 위에서 설명했다시피 연산량을 줄여주기 위해 사용되었습니다.

 

Inception 모듈에서는 1 x 1, 3 x 3, 5 x 5, 3 x 3 max pooling를 통해 다양한 특성값들을 추출해냅니다.

여기에서 max pooling에도 1 x 1 Convolution을 해주었는데

이는 Previous layer에서 Concat 연산시 채널값을 맞춰주기 위함입니다.

 

 

Global Average Pooling

이전 게시물에서 다뤘던 Model들은 FC 방식으로 마지막 부분의 Layer를 채워줬지만

GoogLeNet에서는 Global Average Pooling 방식을 사용하였습니다.

Global Average Pooling은 각 채널에서의 특성맵들을 각각 평균낸 것을 이어서 1차원 vector로 만들어 주었습니다.

이것도 연산량을 줄이기 위해 사용되었습니다.

 

 

 

Auxiliary Classifier

Train을 하다보면 Gradient Vanishing 현상이 발생할 수도 있습니다.

 

※ Gradient Vanishing: https://ggongsowon.tistory.com/42

 

13_Pytorch_Activation_Gradient Vanishing

저희가 저번시간에는 XOR 문제를 해결해 보았습니다. XOR를 사용하기 위해서 Perceptron 사이사이 Sigmoid를 사용해 주었습니다. 이런 함수들을 Activation Function이라고 부릅니다. 다음 Layer로 값이 넘어

ggongsowon.tistory.com

 

이를 위해 GoogLeNet에서는 중간중간에 softmax를 두어 중간에서도 Backpropagation이 발생하게 해주어

Gradient가 잘 전달되지 않는 문제를 해결하였습니다.

 

※ Auxiliary Classifier 주의사항

  • Backpropagation시, weight값에 큰 영향을 주는 것을 막기 위해 Auxiliary Classifier에 0.3을 곱함.
  • 맨 마지막 softmax는 따로 value를 곱해주지 않음.
  • Inference 과정에서는 Auxiliary Classifier를 모두 제거한 후 사용, weight값을 업데이트 해주지 않기 때문.

 

 

Code

 

 

 

 

 

 

※ 출처: 

☆ https://www.youtube.com/watch?v=uQc4Fs7yx5I&t=39s

https://github.com/pytorch/vision/blob/main/torchvision/models/googlenet.py

https://medium.com/coinmonks/paper-review-of-googlenet-inception-v1-winner-of-ilsvlc-2014-image-classification-c2b3565a64e7

https://www.vlfeat.org/matconvnet/models/imagenet-googlenet-dag.svg

https://bskyvision.com/539

https://deep-learning-study.tistory.com/523

'Deep Learning > Pytorch' 카테고리의 다른 글

32_DenseNet  (0) 2022.04.11
31_ResNext  (0) 2022.04.08
30_AlexNet  (0) 2022.03.01
27_Pytorch_Custom_Dataset  (0) 2021.12.02
23_Pytorch_EfficientNet  (0) 2021.11.24
#include <chrono>
using namespace chrono;

// 시간 start
uint64_t start_microsec = duration_cast<microseconds>(system_clock::now().time_since_epoch()).count();

// 시간 end
uint64_t end_microsec = duration_cast<microseconds>(system_clock::now().time_since_epoch()).count();

// 걸린 시간
uint64_t dur = end_microsec - start_microsec;

 

duration_cast<원하는 시간 단위>(system_clock::now().time_since_epoch()).count();

  • <nanoseconds>  // 나노 세컨드. 10억분의 1초
  • <microseconds> // 마이크로 세컨드. 100만분의 1초
  • <milliseconds>   // 밀리 세컨드. 1000분의 1초
  • <seconds>        // 초
  • <minutes>        // 분
  • <hours>           // 시

'Language > C++' 카테고리의 다른 글

시간 측정  (0) 2023.03.16
Mat 형식 Binary file로 저장 및 Binary file 불러오기  (0) 2022.02.22
opencv vector to Mat  (0) 2021.12.23
Image to Base64, Base64 to Image  (0) 2021.11.18
파일 랜덤 추출  (0) 2021.10.14

+ Recent posts