본문 바로가기
소프트웨어/파이썬

[파이썬] OpenCV로 GUI 만들기

by 만들오 2022. 1. 21.
728x90

donaricano-btn

OpenCV GUI

OpenCV는 이미지/영상처리의 대표격인 라이브러리 입니다.
이미지/영상 관련한 일은 뭐든지 할 수 있을것 같습니다.
하지만, GUI만큼은 지원하지 않고 있는데, 이 부분이 아쉬워 직접 만들어 봤습니다.

OpenCV를 이용한 GUI
OpenCV를 이용한 GUI


최근 리니지W 매크로 프로젝트를 진행하며, 가장 어려웠던 부분이 GUI였습니다.
전체 코드가 413 라인인데 핵심 코드는 100줄 정도이니, GUI가 75%를 차지하는군요...😂
이걸 이용해 라인수를 최대한 줄여 강좌를 시작하려고 합니다.


사용 방법

① 필수 라이브러리 설치

pip install opencv-python numpy pillow


② cvgui.py 다운로드
사용할 앱이 있는 폴더에 저장합니다.

cvgui.py
0.01MB
더보기
더보기
#cvgui.py

import os
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import tkinter as tk
from tkinter import messagebox, simpledialog, filedialog

root = tk.Tk()
root.withdraw()
#tkinter는 메시지 입/출력용도로 사용. 윈도우 사용 안함.

def showMessage(msg):
    messagebox.showinfo("Message", msg)


def askFloat():
    return simpledialog.askfloat(title="Set value", prompt="Type value(float)")


def askInt():
    return simpledialog.askinteger(title="Set value", prompt="Type value(int)")


def askStr():
    return simpledialog.askstring(title="Set value", prompt="Type value(str)")


def askFile():
    dir = os.getcwd()
    return filedialog.askopenfilename(initialdir=dir, title="Select .txt file")


def addImage(background, foreground, x, y):
    ret, mask = cv2.threshold(foreground[:,:,3], 1, 255, cv2.THRESH_BINARY)
    mask_inv = cv2.bitwise_not(mask)
    foreground = cv2.cvtColor(foreground, cv2.COLOR_BGRA2BGR)
    h, w = foreground.shape[:2]
    roi = background[y:y+h, x:x+w]
    maskedFg = cv2.bitwise_and(foreground, foreground, mask=mask)
    maskedBg = cv2.bitwise_and(roi, roi, mask=mask_inv)
    added = maskedFg + maskedBg
    background[y:y+h, x:x+w] = added
    return background


def makeButton(text, width, height, color, textSize=30,  border=True, textAlign="center"):
    img = Image.new("RGBA", (width, height), (0,0,0,0))
    draw = ImageDraw.Draw(img, "RGBA")
    outline = color if border else (0,0,0,0)
    draw.rounded_rectangle((0, 0, width-1, height-1), fill=(0,0,0,0), outline=outline, width=3, radius=width/10)
    font = ImageFont.truetype("fonts/gulim.ttc", size=textSize)
    textWidth, textHeight = draw.textsize(text, font=font)
    if textWidth > width:
        img = Image.new("RGBA", (textWidth, height), (0,0,0,0))
        draw = ImageDraw.Draw(img, "RGBA")
        outline = color if border else (0,0,0,0)
        draw.rounded_rectangle((0, 0, width-1, height-1), fill=(0,0,0,0), outline=outline, width=3, radius=width/10)
    if textAlign == "center":
        draw.text(((width-textWidth)/2,(height-textHeight)/2), text, fill=color, font=font)
    elif textAlign == "left":
        draw.text((0,(height-textHeight)/2), text, fill=color, font=font)
    elif textAlign == "right":
        draw.text((width-textWidth,(height-textHeight)/2), text, fill=color, font=font)
    del draw
    #img.show()
    #opencv형식으로 변환하여 리턴
    image = cv2.cvtColor(np.array(img), cv2.COLOR_RGBA2BGRA)
    #cv2.imshow("image", image)
    #cv2.waitKey(0)
    return image


#PIL로 버튼 이미지 만드는 버전.
#한글 입력 가능
class GUI():
    def __init__(self, text, x, y, w, h, color=(255,255,255,100), colorOn=(45,230,90,100), textSize=30, border=True, toggle=False, textAlign="center", readOnly=False):
        self.text, self.textSize = text, textSize
        self.x, self.y = x, y
        self.w, self.h = w, h
        self.lastValue = ""
        self.toggle = toggle
        self.readOnly = readOnly
        self.color, self.colorOn = color, colorOn
        self.border = border
        self.textAlign = textAlign
        self.state = False
        self.make()


    def make(self):        
        self.btn = makeButton(self.text, self.w, self.h, self.color, self.textSize, self.border, self.textAlign)
        self.btnHover = makeButton(self.text, self.w, self.h, self.colorOn, self.textSize, self.border, self.textAlign)
        self.btnClick = makeButton(self.text, self.w, self.h, self.colorOn, self.textSize, self.border, self.textAlign)


    def changeText(self, text):
        self.text = text
        self.btn = makeButton(self.text, self.w, self.h, self.color, self.textSize, self.border, self.textAlign)
        self.btnHover = makeButton(self.text, self.w, self.h, self.colorOn, self.textSize, self.border, self.textAlign)
        self.btnClick = makeButton(self.text, self.w, self.h, self.colorOn, self.textSize, self.border, self.textAlign)


    def add(self, frame, mouse):
        self.hover = self.x <= mouse["x"] <= (self.x + self.w) and self.y <= mouse["y"] <= (self.y + self.h)
        if self.readOnly:
            addImage(frame, self.btn, self.x, self.y)
            return
        if not self.toggle:
            if self.hover and mouse["down"]:
                value = "DOWN"
                addImage(frame, self.btnClick, self.x, self.y+2)
            elif self.hover or self.state:
                value = "HOVER"
                addImage(frame, self.btnHover, self.x, self.y)
            else:
                value = "NORMAL"
                addImage(frame, self.btn, self.x, self.y)
        else:
            value = "DOWN" if self.hover and mouse["down"] else "NORMAL"
            if self.state:
                addImage(frame, self.btnHover, self.x, self.y)
            else:
                addImage(frame, self.btn, self.x, self.y)

        if self.lastValue != "DOWN" and value == "DOWN":
            self.lastValue = value
            if self.toggle:
                self.state = not self.state
            return "CLICK"
        else:
            self.lastValue = value
            return value


③ 사용 예제

gui.py
0.00MB
더보기
더보기
# gui.py

import cv2
import numpy as np
import cvgui

# 마우스 이벤트를 위한 딕셔너리.
# 마우스 콜백함수에 넣어서 항상 업데이트되도록 한다.
mouseValue = {"x": 0, "y": 0, "down": False}    
def mouseCallback(event, x, y, flags, param):
    mouseValue["x"], mouseValue["y"] = x, y
    mouseValue["down"] = True if flags == cv2.EVENT_FLAG_LBUTTON else False


# 사용할 창의 이름을 MANDLOH로 정하고, 마우스 콜백을 지정한다.
win_title = "MANDLOH"
cv2.namedWindow(win_title)
cv2.setMouseCallback(win_title, mouseCallback)


# (height, width, channel) 300x300x3 크기의 빈 이미지를 생성해 배경으로 사용한다.
frame = np.zeros((150, 300, 3), np.uint8)

# 사용 예. 메인루프 진입 전 생성해 준다.
title = cvgui.GUI(text="파이썬 초보를 위한 OpenCV GUI.", x=5, y=5, w=100, h=40, textSize=15 ,border=False, textAlign="left", readOnly=True)
info = cvgui.GUI(text="q키나 창의 x버튼을 눌러 종료할 수 있습니다.", x=5, y=25, w=100, h=40, textSize=15 ,border=False, textAlign="left", readOnly=True)
btn = cvgui.GUI(text="버튼", x=10, y=60, w=100, h=40)
toggle = cvgui.GUI(text="토글", x=120, y=60, w=100, h=40, toggle=True)
text = cvgui.GUI(text="a 키를 눌러 텍스트를 바꿀 수 있습니다.", x=10, y=100, w=100, h=40, textSize=15 ,border=False, textAlign="left", readOnly=True)
maker = cvgui.GUI(text="만든이 : 만들오", x=190, y=130, w=100, h=20, textSize=12, textAlign="right" ,border=False, readOnly=True)


# 메인루프
while True:
    # 배경 색상을 칠한다. OpenCV는 BGR(Blue,Green, Red) 순서.
    frame[:] = (90, 90, 90)
    
    # 생성한 GUI들을 frame에 추가한다.
    # .add(frame, mouseValue) 후 리턴값들
        # NORMAL : 선택되지 않은 상태
        # HOVER : 마우스가 위에 있는 상태
        # CLICK : 클릭한 경우(1회)
        # DOWN : 마우스로 누르고 있는 상태
    title.add(frame, mouseValue)
    info.add(frame, mouseValue)

    btn_value = btn.add(frame, mouseValue)
    if btn_value != "NORMAL" and btn_value != "HOVER":
        print(btn_value)
    if toggle.add(frame, mouseValue) == "CLICK": print("HELLO")
    text.add(frame, mouseValue)
    if maker.add(frame, mouseValue) == "DOWN":
        print("만들오입니다.")

    cv2.imshow(win_title, frame)
    key = cv2.waitKey(33)
    
    #q키 또는 X버튼을 누르면 종료
    if key == ord("q") or cv2.getWindowProperty(win_title, cv2.WND_PROP_VISIBLE) < 1:
        break
    elif key == ord("a"):
        msg = cvgui.askStr()
        text.changeText(msg)
        

cv2.destroyAllWindows()


사용 관련 궁금하신 부분은 댓글을 남겨주세요.

감사합니다.
[끝].

댓글