๐Ÿ˜ŠAI๋ฅผ ์ด์šฉํ•ด์„œ ์–ด๋‘์šด ์ด๋ฏธ์ง€๋ฅผ ๋ฐ๊ฒŒ ๋งŒ๋“ค์–ด๋ณด์ž

,

์œก์•„ ํœด์ง ์ค‘์ธ๋ฐ ์‹ฌ์‹ฌํ•˜๊ตฐ์š”..

AI๋ฅผ ์ด์šฉํ•ด์„œ ์–ด๋‘์šด ์ด๋ฏธ์ง€๋ฅผ ๋ฐ๊ฒŒ ๋งŒ๋“œ๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด๋ณด๊ณ  ์‹ถ์–ด์กŒ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜ ์ด๋ฏธ์ง€๋Š” ์ €์กฐ๋„ ๋ฐ์ดํ„ฐ์…‹ ์ด๋ฏธ์ง€์ธ๋ฐ ์žฌ๋ฏธ์žˆ์–ด ๋ณด์ด๋Š”๊ตฐ์š”.

์กฐ๊ธˆ์”ฉ ๋งŒ๋“ค์–ด๋ณด๋ฉด์„œ ๊ฒฐ๊ณผ๋“ค์„ ์—…๋ฐ์ดํŠธ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

9๊ฐœ์˜ ์ข‹์•„์š”

๊ฐ€์žฅ ๋งŒ๋งŒํ•ด ๋ณด์ด๋Š” ๋…ผ๋ฌธ์„ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.

https://arxiv.org/pdf/2103.00860

์š” ๋…€์„์„ ์ •๋…ํ•ด ๋ณด๋ฉด์„œ ์•„๋ž˜์™€ ๊ฐ™์€ ์ˆœ์„œ๋กœ ๊ตฌํ˜„์„ ํ•ด๋ณผ๊นŒ ํ•ฉ๋‹ˆ๋‹ค.

  1. pytorch๋ฅผ ์ด์šฉํ•ด์„œ ๋ชจ๋ธ์„ ๋งŒ๋“ฌ
  2. C++์—์„œ ๋Œ๋ฆด ์ˆ˜ ์žˆ๊ฒŒ inference ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ
  3. C#์œผ๋กœ ๊ทธ๊ฒƒ์„ ๋‹ค์‹œ ๋žฉํ•‘ (๋งˆ์ƒฌ๋ง๋ฐฉ๋ฒ•๊ณผ C++/CLI ๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ž˜ํ•‘์„ ์ง„ํ–‰ํ•ด์„œ ์žฅ๋‹จ์  ์ •๋ฆฌ)
  4. C# WPF์— ์„œ๋น„์Šค๋กœ ๋ฐ€์–ด ๋„ฃ์–ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์ž…๋ ฅ ์ด๋ฏธ์ง€์™€ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋ฅผ ๋น„๊ต

๊ทผ๋ฐ.. ๋ฒ„๋ฆ‡์ด ์ฐธ ๋ฌด์„œ์šด๊ฒŒ ๋ถ„๋ช… ํœด๊ฐ€ ์ค‘์ธ๋ฐ ๋ˆˆ์ด ๋กœ๋ด‡์ฒ˜๋Ÿผ ์ƒˆ๋ฒฝ์ด ๋˜๋ฉด ์—ด๋ฆฌ๋Š”๊ตฐ์š”..

12๊ฐœ์˜ ์ข‹์•„์š”

๋„ˆ๋Š” ๋„๋Œ€์ฒด ์–ด๋–ป๊ฒŒ ๋™์ž‘ ํ•˜๋Š” ๊ฑฐ๋‹ˆ?

Zero-DCE๋ชจ๋ธ์€ ์ฝ์–ด ๋ณด๋‹ˆ.. ์ž…๋ ฅ RGB ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉด ๊ทธ ์ž…๋ ฅ์œผ๋กœ ๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ํ†ต๊ณผํ•ด ์ถ”์ •ํ•œ ๊ณก์„  ์ปค๋ธŒ์šฉ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ์˜ ์ž…๋ ฅ์œผ๋กœ ๋„ฃ์—ˆ๋˜ x์™€ ํ•จ๊ป˜ ๊ณฑํ•˜๊ฑฐ๋‚˜ ๋”ํ•ด์„œ ์ตœ์ ์˜ ๋ฐ๊ธฐ ๊ณก์„ ์„ ๊ฐ€์ง„ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“œ๋Š”๊ฒƒ์ด ๋ชฉ์ ์ž…๋‹ˆ๋‹ค.

๋…ผ๋ฌธ ์ €์ž๋Š” 8๋ฒˆ ๋ฐ˜๋ณตํ•ด์„œ ๊ณฑํ•ด์„œ ์ตœ์ ์˜ ๋ฐ๊ธฐ๋ฅผ ๊ฐ€์ง„ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค์–ด๋ƒˆ์Šต๋‹ˆ๋‹ค.

์•„.. ๊ทธ๋ฆฌ๊ณ  ์Œ.. ๋…ผ๋ฌธ์ €์ž์˜ ์›๋ฌธ repository๋„ ์ฐธ๊ณ ํ–ˆ๋Š”๋ฐ ์ € ์ปค๋ธŒ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ์ž…๋ ฅ์ด๋ฏธ์ง€๊ฐ€ 500x500์ด๋ผ๋ฉด ๊ทธ ๊ฐฏ์ˆ˜๋งŒํผ ๋”ฐ๋กœ ์ถ”์ •ํ•ฉ๋‹ˆ๋‹ค. ์ดํ•ด๊ฐ€ ์•ˆ๋˜์„œ ํ•œ์ฐธ์„ ๋ดค๋Š”๋ฐโ€ฆ ํ”ฝ์…€๋งˆ๋‹ค ํŒŒ๋ผ๋ฏธํ„ฐ weight๊ฐ€ ๋…๋ฆฝ์ด๋‹ˆ ์–ด๋‘์šด ๊ณณ์€ ๋ฐ๊ฒŒ ๋ฐ์€ ๊ณณ์€ ๋” ์ž‘์€ ๊ฐ€์ค‘์น˜๋ฅผ ์ค˜์„œ ๋ฒˆ์ง€์ง€ ์•Š๊ฒŒ ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ๋А๋‚Œ์ ์ธ ๋А๋‚Œ์ด ๋“œ๋Š”๊ตฐ์š”. ๋งŒ์•ฝ ์ € ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํ•˜๋‚˜์˜€๋‹ค๋ฉด ๋‹น์—ฐํ•˜๊ฒ ์ง€๋งŒ ๋ฐ์€๊ณณ๊ณผ ์–ด๋‘์šด ๊ณณ์„ ๋”ฐ๋กœ ์ปจํŠธ๋กค ํ•  ์ˆ˜ ์—†๊ฒ ์ง€์š” ใ… 

์œ„์— ์„ค๋ช…ํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ ๋ชจ๋“  ํ”ฝ์…€์ด ๋‹ค๋ฅธ ๊ฐ€์ค‘์น˜๋ฅผ ๊ฐ€์ง€๊ธฐ ๋•Œ๋ฌธ์— ๋”ฅ๋Ÿฌ๋‹์ด ์กฐ์ •ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’๋“ค์„ R,G,B ์ฑ„๋„๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์‹œ๊ฐํ™”ํ•˜์—ฌ heatmap์œผ๋กœ ํ‘œํ˜„ํ–ˆ์„ ๋•Œ ์–ด๋‘์šด ๊ณณ์€ ๋งŽ์€ ๊ฐ€์ค‘์น˜, ๋ฐ์€ ๊ณณ์€ ์ ์€ ๊ฐ€์ค‘์น˜๋ฅผ ๊ณฑํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ์˜ ๊ตฌ์กฐ์  ๊ด€์ ์œผ๋กœ ๊ด€์ฐฐํ•ด๋ณด์ž

์ด๊ฑด ๋ญ.. ์ „ํ˜€ ๋ณต์žกํ•˜์ง€ ์•Š์€ ์—ฌ๋Ÿฌ ์ปจ๋ณผ๋ฃจ์…˜์„ ์ ์ธตํ•˜์—ฌ ๋งŒ๋“  ๋‹จ์ˆœํ•œ ๋ชจ๋ธ์ด๊ตฐ์š”.

๋‹ค๋งŒ ์•„๋ฌด๋ฆฌ ๋‹จ์ˆœํ•˜๋”๋ผ๋„ stride๋‚˜ max pooling์„ ์ด์šฉํ•œ ํ”ผ์ณ๋งต ์ถ•์†Œ๊ฐ€ ์—†์–ด์„œ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํ„ฐ์ ธ๋ฒ„๋ฆด ๊ฒƒ ๊ฐ™์€๋ฐ.. ์ฝ์–ด๋ณด๋‹ˆ ์ € ์ปจ๋ณผ๋ฃจ์…˜์€ ๋‹จ์ˆœ ์ปจ๋ณผ๋ฃจ์…˜์ด ์•„๋‹ˆ๋ผ google์ด mobilenet์ด๋ž‘ xception๋ชจ๋ธ์—์„œ ์‚ฌ์šฉํ•œ separable convolution์„ ์ด์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

separable convolution์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ ์œผ๋ฉด ๋„ˆ๋ฌด ๊ธธ์–ด์งˆ ๊ฒƒ ๊ฐ™์•„์„œ ์•„๋ž˜ ๋งํฌ๋กœ ๋Œ€์ฒดํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ธ”๋กœ๊ทธ ์šด์˜ํ•˜์‹œ๋Š” ๋ถ„๋“ค์ด ๊ธ€์„ ์ •๋ง ์ž˜ ์“ฐ์‹œ๋Š”๊ตฐ์š” โ€ฆ.

๊ทธ ์™ธ์— ํŠน๋ณ„ํ•œ ๊ธฐ์ˆ ์ ์ธ ๋ถ€๋ถ„์€ ์—†๊ณ  ์—ฐ์‚ฐ๋Ÿ‰์„ ์ค„์ด๊ธฐ ์œ„ํ•ด์„œ ์ปค๋ธŒ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”์ • ์‹œ์— ์‚ฌ์šฉ์ž๊ฐ€ ์ง€์ •ํ•œ scale๊ฐ’ ๋งŒํผ์„ ์ค„์ด๋Š” ์ž‘์—…์„ ํ•˜๋Š”๊ตฐ์š”.

์ฝ”๋“œ๋ฅผ ๋œฏ์–ด๋ณด๋‹ˆ.. ๊ฐ€๋กœ ์„ธ๋กœ ๊ธธ์ด๋ฅผ 4๋ฐฐ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค. ๋ฉด์ ์œผ๋กœ ๋”ฐ์ง€๋ฉด 16๋ถ„์˜ 1๋กœ ์ค„์–ด ๋“ค์—ˆ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๊ฒ ๊ตฐ์š”.

์ฝ”๋”ฉํ•˜๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ฐ”๋ผ๋ณด๋Š” ๋™์ž‘ ์›๋ฆฌ

์ €๋Š” ๋”ฅ๋Ÿฌ๋‹์— ๊ด€์‹ฌ์ด ์žˆ๋Š” ์‚ฌ๋žŒ์ด์ง€ ๋Œ€ํ•™์›์„ ๋‹ค๋‹ˆ๊ฑฐ๋‚˜ ์—ฐ๊ตฌ์›์€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜ํ•™์„ ์ •๋ง ๋ชปํ•ฉ๋‹ˆ๋‹ค -_-

์˜คํžˆ๋ ค C# ์ฝ”๋”ฉ์ด ์ข‹์€๋ฐ ๊ทธ๋Ÿผ ์ € ๋ชจ๋ธ์„ ๋„๋Œ€์ฒด ์–ด๋–ป๊ฒŒ ์ดํ•ดํ•ด์•ผ ๋  ๊ฐ€ ์ƒ๊ฐํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

๋”ฑ ๋งž๋Š” ์ •ํ™•ํ•œ ์„ค๋ช…์€ ์•„๋‹ˆ์ง€๋งŒ ์•„๋ž˜ ๊ทธ๋ฆผ์ฒ˜๋Ÿผ ์ง๊ด€์ ์ธ ๋А๋‚Œ์ด ๊ทธ๋ ค์ง€๋Š”๊ตฐ์š”..

Slog ํƒœํด๊ณผ ์˜๊ฒฌ๊ณต์œ โ€ฆ ๋Œ“๊ธ€์€ ์–ธ์ œ๋‚˜ ํ™˜์˜์ž…๋‹ˆ๋‹ค.

7๊ฐœ์˜ ์ข‹์•„์š”

๊ตฌํ˜„ํ•œ ์ €์กฐ๋„ ๊ฐœ์„  ๋ชจ๋ธ Python์ฝ”๋“œ

๋…ผ๋ฌธ์— ๋‚˜์˜จ๋Œ€๋กœ ๊ตฌํ˜„ํ•˜์˜€๊ณ  ํ•™์Šต์„ ๊ฑธ์–ด๋†“์•˜์Šต๋‹ˆ๋‹ค.
๊ฒฐ๊ณผ๊ฐ€ ์ž˜ ๋‚˜์™”์œผ๋ฉด ์ข‹๊ฒ ๊ตฐ์š”..
์ด์ „ ํฌ์ŠคํŠธ์—์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด ์ด ๋ชจ๋ธ์„ ํ•™์Šต์‹œ์ผœ์„œ ๋ฐฐํฌ๋Š” ONNX๋กœ ์ง„ํ–‰์„ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๊ทธ ONNX๋ชจ๋ธ์„ C++ ONNX๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋กœ๋“œํ•ด์„œ ๋Œ์•„๊ฐ€๋„๋ก ๋งŒ๋“ค ๊ฒƒ์ด๊ณ .
๊ทธ ์ดํ›„์— C# ๋ž˜ํ•‘ ๊ณผ์ •์„ ๊ฑฐ์ณ UI์— ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
๋ฌผ๋ก  ๋ˆ„๊ฒŸ์— ๋ฐฐํฌ๋˜์–ด์žˆ๋Š” ์œ ๋ช…ํ•œ ONNX C#๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์„œ ์ถ”๋ก ํ•˜๋Š”๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์•„ ๊ทธ๋ฆฌ๊ณ  ์ œ๊ฐ€ ์‚ฌ์šฉํ•œ ์ €์กฐ๋„ ๊ฐœ์„  ๋ฐ์ดํ„ฐ์…‹์€ ์•„๋ž˜ ๋งํฌ์—์„œ ๋ฐ›์œผ์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Separable Convolution Block

import torch
class DepthwiseSeparableConv(torch.nn.Module):
    def __init__(self, in_ch, out_ch):
        super(DepthwiseSeparableConv, self).__init__()
        self.depth_conv = torch.nn.Conv2d(in_ch, in_ch, kernel_size=3, padding=1, groups=in_ch)
        self.point_conv = torch.nn.Conv2d(in_ch, out_ch, kernel_size=1)

    def forward(self, x):
        return self.point_conv(self.depth_conv(x))

ZeroDCE Model

import torch
import torch.nn as nn
import torch.nn.functional as F



from model.block import DepthwiseSeparableConv


class ZeroDCEPP(nn.Module):
    def __init__(self, scale_factor=4, num_features=32):
        super(ZeroDCEPP, self).__init__()

        self.relu = nn.ReLU(inplace=True)
        self.scale_factor = scale_factor
        self.upsample = nn.UpsamplingBilinear2d(scale_factor=self.scale_factor)

        self.e_conv1 = DepthwiseSeparableConv(3, num_features) 
        self.e_conv2 = DepthwiseSeparableConv(num_features, num_features) 
        self.e_conv3 = DepthwiseSeparableConv(num_features, num_features) 
        self.e_conv4 = DepthwiseSeparableConv(num_features, num_features) 
        self.e_conv5 = DepthwiseSeparableConv(num_features * 2, num_features) # x3 + x4
        self.e_conv6 = DepthwiseSeparableConv(num_features * 2, num_features) # x2 + x5
        self.e_conv7 = DepthwiseSeparableConv(num_features * 2, 3)            # x1 + x6

    def apply_curve(self, x, x_r):
        for _ in range(8):
            x = x + x_r * (torch.pow(x, 2) - x)
        return x

    def forward(self, x):
        # ๋‹ค์šด ์Šค์ผ€์ผ
        if self.scale_factor == 1:
            x_down = x
        else:
            x_down = F.interpolate(x, scale_factor=1/self.scale_factor, mode='bilinear', align_corners=False)


        # ํŠน์ง• ์ถ”์ถœ ๋ ˆ์ด์–ด ์‹œ์ž‘ 
        layer1 = self.relu(self.e_conv1(x_down))
        layer2 = self.relu(self.e_conv2(layer1))
        layer3 = self.relu(self.e_conv3(layer2))
        layer4 = self.relu(self.e_conv4(layer3))
        
        # Feature Concat
        layer5 = self.relu(self.e_conv5(torch.cat([layer3, layer4], 1)))
        layer6 = self.relu(self.e_conv6(torch.cat([layer2, layer5], 1)))
        layer7 = self.e_conv7(torch.cat([layer1, layer6], 1))
        

        x_r = torch.tanh(layer7)

        # ์›๋ณธ ์‚ฌ์ด์ฆˆ๋กœ ๋‹ค์‹œ upsampling
        if self.scale_factor != 1:
            x_r = self.upsample(x_r)
        
        # ์›๋ณธ ์ด๋ฏธ์ง€์— ๋ผ์ดํŠธ๋งต์„ ๊ณฑํ•จ.
        enhance_image = self.apply_curve(x, x_r)
        
        return enhance_image, x_r

ONNX Model ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ ๋ชจ๋ธ ์ฝ”๋“œ

import torch


class OnnxModel(torch.nn.Module):
    def __init__(self, backbone:torch.nn.Module):
        super(OnnxModel, self).__init__()

        self.backbone = backbone


    def forward(self, x):
    
        ## ํ•™์Šตํ•œ ๋ชจ๋ธ์„ ๋‚ด๋ณด๋‚ด๊ธฐ ํ• ๋•Œ curve parameter๋Š” inference์‹œ ํ•„์š”๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์—. 
        ## OnnxModel์šฉ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ curve๋ฅผ ์ œ์™ธํ•œ ๊ฒฐ๊ณผ๋งŒ ๋ฆฌํ„ดํ•˜๋„๋ก ์ˆ˜์ • 
        
        x, curve = self.backbone(x)

        return x

Training Code

import torch
import torch.onnx
import numpy as np
import cv2
import os

from model.zerodcepp import ZeroDCEPP
from loss.zerodce_loss import ZeroDCETotalLoss
from dataset.lle_dataset import get_lle_loader
from model.onnx_model import OnnxModel

# GPU ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ๋””๋ฐ”์ด์Šค: {device}")

# Hyper Parameter
epochs = 50
learning_rate = 0.0001
weight_decay = 0.0001
batch_size = 8
scale_factor = 4
num_features = 32
image_width = 1024
image_height = 1024
image_channel = 3

dataset_path = "C://github//dataset//lol_dataset//our485//all"
weight_path = "C://github//Dot4Seminar//working//python//results//weights.pth"
onnx_model_path = "C://github//Dot4Seminar//working//python//results//model.onnx"


dummy_input = torch.randn(size=(1, image_channel, image_height, image_width)).to(device)

model = ZeroDCEPP(scale_factor=scale_factor, num_features=num_features)
model = model.to(device)
if os.path.exists(weight_path):
    state_dict = torch.load(weight_path, map_location=device)
    model.load_state_dict(state_dict)


dataloader = get_lle_loader(dataset_path, batch_size, resize_shape=(image_height, image_width))
total_batches = len(dataloader)


loss = ZeroDCETotalLoss().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay)

temp_loss = 1000000
for epoch in range(epochs):
    avg_loss = 0

    model.train()
    for i, x_image in enumerate(dataloader):

        gpu_x_image = x_image.to(device)
        enhanced_img, curve_params = model(gpu_x_image)
        current_loss = loss(gpu_x_image, enhanced_img, curve_params)

        optimizer.zero_grad()
        current_loss.backward()
        optimizer.step()

        avg_loss += current_loss.item() / total_batches

    tensor_input_check = gpu_x_image[0]
    tensor_output_check = enhanced_img[0]
    # 1. CPU ์ด๋™ ๋ฐ Numpy ๋ณ€ํ™˜
    input = tensor_input_check.detach().cpu().numpy()
    input = np.transpose(input, (1, 2, 0))
    input = (input * 255).astype(np.uint8)
    input = cv2.cvtColor(input, cv2.COLOR_RGB2BGR)

    # 1. CPU ์ด๋™ ๋ฐ Numpy ๋ณ€ํ™˜
    output = tensor_output_check.detach().cpu().numpy()
    output = np.transpose(output, (1, 2, 0))
    output = (output * 255).astype(np.uint8)
    output = cv2.cvtColor(output, cv2.COLOR_RGB2BGR)


    # 5. ์‹œ๊ฐํ™”
    cv2.imshow("original", input)
    cv2.imshow("output", output)
    cv2.waitKey(1) # 1ms ๋Œ€๊ธฐ (ํ•™์Šต ๋ฃจํ”„ ๋ฉˆ์ถค ๋ฐฉ์ง€)

    if temp_loss > avg_loss:
        temp_loss = avg_loss
        
        model.eval()
    
        onnx_model = OnnxModel(backbone=model)

        torch.save(model.state_dict(), weight_path)
        torch.onnx.export(
            onnx_model,                      # ์‹คํ–‰ํ•  ๋ชจ๋ธ
            dummy_input,                # ๋ชจ๋ธ ์ž…๋ ฅ ์˜ˆ์‹œ
            onnx_model_path,            # ์ €์žฅ ํŒŒ์ผ๋ช…
            export_params=True,         # ๋ชจ๋ธ ํŒŒ์ผ ์•ˆ์— ํ•™์Šต๋œ ํŒŒ๋ผ๋ฏธํ„ฐ ์ €์žฅ
            opset_version=11,           # Bilinear ์—ฐ์‚ฐ์„ ์•ˆ์ •์ ์œผ๋กœ ์ง€์›ํ•˜๋Š” ๋ฒ„์ „
            do_constant_folding=True,   # ์ƒ์ˆ˜ ํด๋”ฉ ์ตœ์ ํ™” (์†๋„ ํ–ฅ์ƒ)
            input_names=['input'],      # ์ž…๋ ฅ ๋…ธ๋“œ ์ด๋ฆ„ (C++์—์„œ ํ˜ธ์ถœ ์‹œ ์‚ฌ์šฉ)
            output_names=['output'],    # ์ถœ๋ ฅ ๋…ธ๋“œ ์ด๋ฆ„
        )
        
    print('current avg loss = ', avg_loss)

7๊ฐœ์˜ ์ข‹์•„์š”

์†”์งํžˆ ์•Œ์•„๋“ฃ์ง€ ๋ชปํ•˜์ง€๋งŒ ๋Œ€๋‹จํ•˜์‹œ๋„ค์š” ์ œ๊ฐ€ ๋ชฉํ‘œ๊ฐ€ AI ๋Š” ๋ชจ๋ฅด์ง€๋งŒ @gellston ๋‹˜๊ฐ™์ด
์ผ์ฐจ๋กœ ํ•™์Šต์„ ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๋Š”๊ฒƒ๋ฐ ๋Œ€๋‹จํ•˜์„ธ์š”

2๊ฐœ์˜ ์ข‹์•„์š”

์Œโ€ฆ ๋”ฅ๋Ÿฌ๋‹ ๋„ˆ๋ฌด ์–ด๋ ค์šด๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

1๊ฐœ์˜ ์ข‹์•„์š”

ํ•™์Šตํ•œ ๋ชจ๋ธ์„ ์ถ”๋ก 

์ง€๋‚œ ํฌ์ŠคํŠธ์—์„œ ํ•™์Šตํ•œ ๋ชจ๋ธ์„ ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ถ”๋ก ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
์ถ”๋ก ์‹œ์—๋Š” ONNX๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  pytorch ๋ชจ๋ธ์˜ ์ €์žฅ๋œ weights๋ฅผ ๋กœ๋“œํ•˜์—ฌ ์ถ”๋ก  ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
์ฝ”๋“œ ๋™์ž‘์€ ์ €์กฐ๋„ ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ๋Š” ์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ถˆ๋Ÿฌ์™€์„œ ์ˆœํšŒํ•˜๋ฉฐ ์ถ”๋ก ์„ ์ง„ํ–‰ํ•˜๋ฉฐ opencv๋ฅผ ์ด์šฉํ•ด์„œ ์‹œ๊ฐํ™” ๋ฐ ์ •์ƒ ๋™์ž‘ํ•˜๋Š”์ง€ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜์—ฌ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

๋ชจ๋ธ ์ถ”๋ก  ์ฝ”๋“œ

import torch
import numpy as np
import cv2
import os


from model.zerodcepp import ZeroDCEPP


# GPU ์‚ฌ์šฉ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ํ™•์ธ
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ ๋””๋ฐ”์ด์Šค: {device}")



# Hyper Parameter
scale_factor = 4
num_features = 32
image_width = 1024
image_height = 1024
image_channel = 3

dataset_path = "C://github//dataset//lol_dataset//our485//low"
weight_path = "C://github//Dot4Seminar//working//python//results//weights.pth"
output_dir = "C://github//Dot4Seminar//working//python//results//output_images"


dummy_input = torch.randn(size=(1, image_channel, image_height, image_width)).to(device)

model = ZeroDCEPP(scale_factor=scale_factor, num_features=num_features)
model = model.to(device)
if os.path.exists(weight_path):
    state_dict = torch.load(weight_path, map_location=device)
    model.load_state_dict(state_dict)

model.eval()

os.makedirs(output_dir, exist_ok=True)

file_list = os.listdir(dataset_path)
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp')


for file_name in file_list:
    if not file_name.lower().endswith(image_extensions):
        continue

    full_path = os.path.join(dataset_path, file_name)
    input = cv2.imread(full_path)
    
    if input is None:
        continue

    h, w, _ = input.shape

    input_img = cv2.cvtColor(input, cv2.COLOR_BGR2RGB)
    input_img = cv2.resize(input_img, (image_width, image_height))
    input_img = input_img.astype(np.float32) / 255.0
    input_img = np.transpose(input_img, (2, 0, 1))
    
    input_tensor = torch.from_numpy(input_img).unsqueeze(0).to(device)

    with torch.no_grad():
        enhanced_tensor, _ = model(input_tensor)


    output = enhanced_tensor.squeeze(0).cpu().numpy()
    output = np.transpose(output, (1, 2, 0))
    output = (output * 255.0).clip(0, 255).astype(np.uint8)
    
    output = cv2.cvtColor(output, cv2.COLOR_RGB2BGR)
    output = cv2.resize(output, (w, h))


    save_path = os.path.join(output_dir, file_name)
    cv2.imwrite(save_path, output)


    cv2.imshow('input', input)
    cv2.imshow('output', output)
    

    cv2.waitKey(1)

์‹œ์—ฐ ๋™์˜์ƒ

์ €์กฐ๋„๊ฐœ์„ ๋ชจ๋ธ ํ…Œ์ŠคํŠธ
click image to open video

๊ฒฐ๊ณผ ์ด๋ฏธ์ง€

:blob: #1 ์›๋ณธ

:blob: #1 ๊ฒฐ๊ณผ

:blob: #2 ์›๋ณธ

:blob: #2 ๊ฒฐ๊ณผ

:blob: #3 ์›๋ณธ

:blob: #3 ๊ฒฐ๊ณผ

์ผ๋ถ€ ์ €์กฐ๋„๊ฐ€ ์•„๋‹Œ ๋ฐ์€ ์ด๋ฏธ์ง€์—์„œ๋Š” ์™œ ์˜ค๋™์ž‘์„ ํ• ๊ฐ€?

์™œ๋ƒ๋ฉด ZeroDCE์˜ ํ•™์Šต ๋กœ์Šค๋Š” ์–ด๋‘์šด ์ด๋ฏธ์ง€๋ฅผ ๋ฐ๊ฒŒ ๋งŒ๋“ค๋„๋ก weights ๋“ค์„ ์กฐ์ •ํ•˜์ง€๋งŒ (curve parameters) ๋ฐ˜๋Œ€๋กœ ๊ณผํ•˜๊ฒŒ ๋ฐ์€ ํ”ฝ์…€ ๋ถ€๋ถ„์„ ์กฐ์ •ํ•˜์—ฌ ๋‚ฎ์ถ”๋Š” ์„ค๊ณ„๋Š” ๋“ค์–ด๊ฐ€ ์žˆ์ง€ ์•Š์•„์„œ ๊ทธ๋Ÿฐ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
๋งŒ์•ฝ์— ์ด ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด ํ‰๊ท  ๋ฐ๊ธฐ๊ฐ€ ์–ด๋А ๊ฐ’ ์ด์ƒ์ผ๋•Œ์—๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๋„๋ก (๊ตณ์ด ์ €์กฐ๋„ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๊ธฐ๋–„๋ฌธ์—?) ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•œ๋‹ค๊ฑฐ๋‚˜ ์•„๋‹ˆ๋ฉด ํ•™์Šต์ค‘์— ์„ค์ •ํ•˜๋Š” ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์กฐ์ •ํ•˜์—ฌ ํƒ€๊ฒŸ ๋ฐ๊ธฐ๋ฅผ ๋„ˆ๋ฌด ๋ฐ์ง€ ์•Š๋„๋ก ์กฐ์ •ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ฒ ๋„ค์š” (ํ•ด๋ดค๋Š”๋ฐ ์ž˜์•ˆ๋ฉ๋‹ˆ๋‹ค..)

5๊ฐœ์˜ ์ข‹์•„์š”

์šฐ์˜คโ€ฆ.. CCTV ์Šคํฌ๋ฆฐ์ƒท ์ด ์–ด๋‘์šธ๋•Œ ์‚ฌ์šฉ ํ•˜๋ฉดโ€ฆ ๋Œ€๋ฐ• ๋‚˜๋Š”๊ฑฐ ์•„๋‹˜๊นŒ ?

์Œ ๋Œ€๊ธฐ์—…๋งŒ ์“ฐ๋ ค๋‚˜

๋Šฅ๋ ฅ์ž ํšฝ์•„๋„คโ€ฆ .. ๋…ผ๋ฌธ์„ ๊ฐœ๋ฐœ๋กœโ€ฆ

1๊ฐœ์˜ ์ข‹์•„์š”

์ƒ๊ฐ๋ณด๋‹ค ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•ด์„œ onnx ๋ณ€ํ™˜๋งŒ ๋˜๋ฉด ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒ ๋„ค์š”

ํ‰๊ท  ๋ฐ๊ธฐ๊ฐ€ ์ผ์ • ์ด์ƒ์ผ ๋•Œ,

์ด๋ฏธ์ง€ ๋ฐ˜์ „ โ†’ ๋กœ์ง ์ ์šฉ โ†’ ๊ฒฐ๊ณผ๋ฌผ ๋ฐ˜์ „

์œผ๋กœ ํ•œ ๋ฒˆ ํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์œผ๋ฆฌ๋ผ ์ƒ๊ฐ๋ฉ๋‹ˆ๋‹ค.

2๊ฐœ์˜ ์ข‹์•„์š”

์š”์ฆ˜ CCTV์— ๋”ฅ๋Ÿฌ๋‹ ์“ด๋‹ค๊ณ  ๋“ค์—ˆ๋Š”๋ฐ ๋” ์ข‹์€๊ฒŒ ๋“ค์–ด๊ฐ€ ์žˆ์ง€ ์•Š์„๊ฐ€์š”?ใ…  ์ž˜๋ชจ๋ฅด๊ฒ ๊ตฐ์š” ๊ฑฐ๊ธฐ ์—…๊ณ„๋Š” ใ… ใ… 

๋„ค ๋งž์Šต๋‹ˆ๋‹ค ๋…ผ๋ฌธ ์ €์ž๋ถ„์ด ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์„ค๊ณ„๋ฅผ ํ•˜์…จ๋”๋ผ๊ตฌ์š”
์˜ค๋ž˜๋œ ๋…ผ๋ฌธ์ด์ง€๋งŒ ์ € ์ €๋„ ๋…ผ๋ฌธ์—์„œ ํ•ญ์ƒ reference๋กœ ๋‚˜์˜ค๋Š” ๋…ผ๋ฌธ๊ฐ™๋”๊ตฐ์š”.
onnx๋ชจ๋ธ์€ ๋ˆ„๊ฒŸ์œผ๋กœ ๊ฐ์‹ธ์„œ ๋งŒ๋“ค๋•Œ ๊ฐ™์ด readme๋ฅผ ์ž‘์„ฑํ•ด์„œ ๊ฐ™์ด ๊ณต์œ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค (๊ณต๋ถ€์šฉ)

์•„ ์ข‹์€ ์•„์ด๋””์–ด ๊ฐ™์Šต๋‹ˆ๋‹ค.
C++ ๋ž˜ํ•‘์„ ํ• ๊ฑด๋ฐ ๊ทธ๋•Œ worst์ผ€์ด์Šค ์ด๋ฏธ์ง€๋“ค์„ ๋ชจ์•„์„œ ๋˜๋Š”์ง€ ์‹คํ—˜ํ•ด๋ด์•ผ๊ฒ ๊ตฐ์š”.


๋Œ€์ถฉ ์ด๋Ÿฐ ๋А๋‚Œ์ด๊ตฐ์š”?
๋‹ค๋งŒ ๋”ฅ๋Ÿฌ๋‹์ด ์ € ๋ฐ˜์ „๋œ ์ด๋ฏธ์ง€๋ฅผ ์ž…๋ ฅ์œผ๋กœ ๋ฐ›์„๋•Œ ์–ด๋–ป๊ฒŒ ํ•ด์„ํ• ์ง€ (์˜ค๋™์ž‘) ํ•ด๋ด์•ผ ๋  ๊ฒƒ ๊ฐ™๋„ค์š”
์•„๋งˆ ์ž˜ ๋˜๋ฆฌ๋ผ ๋ณด๋Š”๋ฐโ€ฆ ๋งŒ์•ฝ์— ๋ฐ˜์ „์ด ์•ˆ๋œ๋‹ค๋ฉด ํ•™์Šตํ•  ๋•Œ augmentation์œผ๋กœ ํ”ฝ์…€ ๊ฐ’ ๋ฐ˜์ „์„ ๋„ฃ๊ณ  ํ•™์Šตํ•œ ๋ชจ๋ธ์„ ๊ฐ€์ง€๊ณ  ์•„์ด๋””์–ด๋ฅผ ์ ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์˜๊ฒฌ ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

1๊ฐœ์˜ ์ข‹์•„์š”

์•„๊นŒ ์˜๊ฒฌ ์ฃผ์‹  ๊ฑฐ ๋Œ“๊ธ€๋กœ ๋‹ต๋ณ€ ๋“œ๋ฆฌ๋‹ค๊ฐ€ ๊ทธ๋Ÿฌ๋‹ˆ๊นŒ ์ € ๊ทธ๋ฆผ์„ ๊ทธ๋ฆฌ๋‹ค๊ฐ€?
๋ฌธ๋“ ๋“œ๋Š” ์ƒ๊ฐ์ด ์ด๋ฏธ ๋กœ์Šค ํ•จ์ˆ˜์—์„œ๋Š” ์ธ์ ‘ ํ”ฝ์…€์˜ ๋ฐ๊ธฐ์ฐจ๊ฐ€(๊ตญ์†Œ์ ์ธ ๋ฐ๊ธฐ์ฐจ) ๊ณผํ•˜์ง€ ์•Š๋„๋ก ์ œ์•ฝ์„ ์ฃผ๊ณ ์žˆ๋Š”๋ฐ์š”.
๊ฑฐ๊ธฐ์— ๋”ํ•ด์„œ ํ‰๊ท  ๋ฐ๊ธฐ๊ฐ€ ๋‚ด๊ฐ€ ์„ค์ •ํ•œ ๋ฐ๊ธฐ๋กœ ๋”ฐ๋ผ๊ฐ€๋„๋ก ์„ค๊ณ„๊ฐ€ ๋˜์–ด์žˆ๋Š”๋ฐ.
์‹คํ—˜์—์„œ ์ž…๋ ฅ์œผ๋กœ ๋„ฃ์€ ์ •์ƒ์ ์ธ ์ด๋ฏธ์ง€๋Š” ์‚ฌ๋žŒ์˜ ๋ˆˆ์œผ๋กœ ๋ณด์•˜์„๋•Œ ์ „์ฒด์ ์ธ ๊ธ€๋กœ๋ฒŒํ•œ ๋ฐ๊ธฐ๊ฐ€
์˜ฌ๋ผ๊ฐ€ ์žˆ๋Š” ์ƒํƒœ์ธ๋ฐ ๋ชจ๋ธ์˜ ํ”ผ์ณ๋ฅผ ๋ฝ‘๋Š” ์ปจ๋ณผ๋ฃจ์…˜ ๋ ˆ์ด์–ด๋“ค์˜ ์ปค๋„์‚ฌ์ด์ฆˆ๋Š” 3x3์œผ๋กœ ์ž‘๊ธฐ ๋•Œ๋ฌธ์— ์ „์ฒด ์”ฌ ํ•ด์„ ๋Šฅ๋ ฅ์ด ๋–จ์–ด์ง€๋Š” ๊ฒƒ๋„ ์–ด๋А๋ถ€๋ถ„ ์ž‘์šฉํ–ˆ์„ ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“œ๋Š”๊ตฐ์š” ใ… 
๋ชจ๋ธ์ด ๋„ˆ๋ฌด ์ข๊ฒŒ ๋ณด๋Š”๊ฒŒ ์•„๋‹Œ๊ฐ€ ์ƒ๊ฐ์ด ์Œ..
๊ทธ๋ž˜์„œ ๋ง์”€ํ•˜์‹  ๋ฐ˜์ „ ์‹คํ—˜์— ๋”ํ•ด์„œ ํ”ผ์ณ๋ ˆ์ด์–ด์— ์ปค๋„ ์‚ฌ์ด์ฆˆ๋ฅผ ๋Š˜๋ ค์„œ ํ…Œ์ŠคํŠธํ•ด๋ณด๋Š” ๊ฒƒ๋„ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!