합리적 에이전트 개념(사람 = 항상 최적의 의사결정 하려 노력하는 주체) → 컴퓨터에 적용하면 “지능 에이전트”
+) 환경과 상호작용 - 인식한 결과에 따라 로봇 move / 자동차 브레이크 등 조작해야함
액추에이터(actuator) : 환경에 영향 미치는 물리적 장치
비전 프로그램 → 비전 에이전트 확장 : 그래픽 사용자 인터페이스(GUI: Graphic User Interface) 추가해야함
# 모듈 불러오기
from PyQt5.QtWidgets import *
import sys
import winsound # 삑 소리 내는데 사용
## PyQt로 GUI 제작하는 일 지원하는 클래스 선언
# 삑 소리 내주는 클래스
class BeepSound(QMainWindow): # QMainWindow 클래스(윈도우 생성 및 관리하는 함수 제공하는 클래스) 상속 받음
# BeepSound 클래스로 객체 생성 시 자동 실행되는 생성자 함수 정의
def __init__(self) :
super().__init__()
# 윈도우 이름과 위치 지정
self.setWindowTitle('삑 소리 내기') # 윈도우의 제목 표시줄에 적힐 제목 설정
self.setGeometry(200,200,500,100) # (윈도우의 초기 위치 x좌표, y좌표, 윈도우의 너비, 높이)
# 4개 위젯 : 버튼 & 레이블 생성
shortBeepButton=QPushButton('짧게 삑',self)
longBeepButton=QPushButton('길게 삑',self)
quitButton=QPushButton('나가기',self)
self.label=QLabel('환영합니다!',self) # 멤버 변수; shortBeepButton, longBeepButton 함수에서 접근 위해서
# 4개 위젯(버튼,레이블) 위치와 크기 지정
shortBeepButton.setGeometry(10,10,100,30)
longBeepButton.setGeometry(110,10,100,30)
quitButton.setGeometry(210,10,100,30)
self.label.setGeometry(10,40,500,70)
# 사용자가 버튼 클릭했을 때 수행할 콜백 함수 지정
shortBeepButton.clicked.connect(self.shortBeepFunction) # shortBeepButton 클릭시 shortBeepFunction 함수 실행
longBeepButton.clicked.connect(self.longBeepFunction)
quitButton.clicked.connect(self.quitFunction)
def shortBeepFunction(self):
self.label.setText('주파수 1000으로 0.5초 동안 삑 소리를 냅니다.') # 레이블 위젯에 지정한 텍스트 씀
winsound.Beep(1000,500) # 소리 발생
def longBeepFunction(self):
self.label.setText('주파수 1000으로 3초 동안 삑 소리를 냅니다.')
winsound.Beep(1000,3000)
def quitFunction(self):
self.close()
app=QApplication(sys.argv) # PyQt 실행에 필요한 객체 생성
win=BeepSound() # 생성자 함수 실행 -> 윈도우 생성, 위젯 4개, 콜백함수 등록
win.show() # win에 해당하는 윈도우를 화면에 표시
app.exec_() # 무한 루프 돌아 프로그램 끝나는 것 방지 (없으면 win 객체인 윈도우 화면 띄우는 순간 프로그램 종료됨)
💡 프로그램 패턴
__init__
설정 : 윈도우 제목&위치&크기 설정, 위젯(버튼,레이블 등) 생성, 버튼 클릭 시 수행할 콜백함수 지정ex.6-2: OpenCV에 PyQt의 GUI 붙이기; 비디오에서 프레임 잡아 저장하기
from PyQt5.QtWidgets import *
import sys
import cv2 as cv
class Video(QMainWindow):
def __init__(self) :
super().__init__()
self.setWindowTitle('비디오에서 프레임 수집') # 윈도우 이름과 위치 지정
self.setGeometry(200,200,500,100)
videoButton=QPushButton('비디오 켜기',self) # 버튼 생성
captureButton=QPushButton('프레임 잡기',self)
saveButton=QPushButton('프레임 저장',self)
quitButton=QPushButton('나가기',self)
videoButton.setGeometry(10,10,100,30) # 버튼 위치와 크기 지정
captureButton.setGeometry(110,10,100,30)
saveButton.setGeometry(210,10,100,30)
quitButton.setGeometry(310,10,100,30)
videoButton.clicked.connect(self.videoFunction) # 콜백 함수 지정
captureButton.clicked.connect(self.captureFunction)
saveButton.clicked.connect(self.saveFunction)
quitButton.clicked.connect(self.quitFunction)
# 웹 캠으로부터 비디오 입력 받아 윈도우에 디스플레이
def videoFunction(self):
self.cap=cv.VideoCapture(0,cv.CAP_DSHOW) # 카메라와 연결 시도
if not self.cap.isOpened(): self.close() # 연결 안 되면 오류 메시지 출력 후 프로그램 종료
while True: # 카메라 연결 성공하면
ret,self.frame=self.cap.read() # 비디오에서 프레임 획득 -> frame 변수에 저장
if not ret: break
cv.imshow('video display',self.frame) # 'video display'라는 윈도우에 표시
cv.waitKey(1)
def captureFunction(self):
self.capturedFrame=self.frame # 비디오 프레임을 저장한 frame(멤버변수여서 사용 가능)을 capturedFrame 변수에 저장
cv.imshow('Captured Frame',self.capturedFrame) # 캡쳐된 프레임을 윈도우에 디스플레이
def saveFunction(self): # 파일 저장
fname=QFileDialog.getSaveFileName(self,'파일 저장','./') # 사용자가 입력한 파일 이름 반환
cv.imwrite(fname[0],self.capturedFrame) # capturedFrame에 저장해뒀던 프레임을 사용자가 지정한 파일명으로 저장
def quitFunction(self):
self.cap.release() # 카메라와 연결을 끊음 (멤버변수 cap 사용)
cv.destroyAllWindows() # OpenCV가 연 모든 윈도우 닫음
self.close() # 프로그램 종료시킴
app=QApplication(sys.argv)
win=Video()
win.show()
app.exec_()
🌿 ex 6-3. GrabCut을 이용해 관심 물체 오리기
: 사용자와 상호작용하면서 GrabCut 반복 적용 → 사용자가 만족할 때까지 물체 영역 오려내는 일을 지원하는 비전 에이전트
import cv2 as cv
import numpy as np
import sys
from PyQt5.QtWidgets import *
# Orim 클래스 선언
class Orim(QMainWindow):
# 생성자 함수
def __init__(self) :
super().__init__()
self.setWindowTitle('오림')
self.setGeometry(200,200,700,200)
# 버튼 7개 생성
fileButton=QPushButton('파일',self)
paintButton=QPushButton('페인팅',self)
cutButton=QPushButton('오림',self)
incButton=QPushButton('+',self)
decButton=QPushButton('-',self)
saveButton=QPushButton('저장',self)
quitButton=QPushButton('나가기',self)
# 버튼 위치, 크기 지정
fileButton.setGeometry(10,10,100,30)
paintButton.setGeometry(110,10,100,30)
cutButton.setGeometry(210,10,100,30)
incButton.setGeometry(310,10,50,30)
decButton.setGeometry(360,10,50,30)
saveButton.setGeometry(410,10,100,30)
quitButton.setGeometry(510,10,100,30)
# 콜백함수 등록
fileButton.clicked.connect(self.fileOpenFunction)
paintButton.clicked.connect(self.paintFunction)
cutButton.clicked.connect(self.cutFunction)
incButton.clicked.connect(self.incFunction)
decButton.clicked.connect(self.decFunction)
saveButton.clicked.connect(self.saveFunction)
quitButton.clicked.connect(self.quitFunction)
self.BrushSiz=5 # 페인팅 붓의 크기
self.LColor,self.RColor=(255,0,0),(0,0,255) # 파란색 - 물체, 빨간색 - 배경
def fileOpenFunction(self):
fname=QFileDialog.getOpenFileName(self,'Open file','./') # 폴더 브라우징하여 파일을 읽어옴
self.img=cv.imread(fname[0]) # imread 함수로 영상 파일 읽어 img 객체에 저장
if self.img is None: sys.exit('파일을 찾을 수 없습니다.')
self.img_show=np.copy(self.img) # img 복사 -> 사용자가 색칠한 정보 표시용 영상
cv.imshow('Painting',self.img_show)
self.mask=np.zeros((self.img.shape[0],self.img.shape[1]),np.uint8) # 사용자가 색칠한 정보 저장할 mask 객체 생성
self.mask[:,:]=cv.GC_PR_BGD # 모든 화소를 배경일 것 같음으로 초기화
def paintFunction(self):
cv.setMouseCallback('Painting',self.painting) # painting 함수를 'Painting' 윈도우의 콜백함수로 등록
# => Painting이라는 윈도우에서 마우스 조작 일어나면 painting 함수 호출
def painting(self,event,x,y,flags,param):
if event==cv.EVENT_LBUTTONDOWN:
cv.circle(self.img_show,(x,y),self.BrushSiz,self.LColor,-1) # 왼쪽 버튼을 클릭하면 파란색
cv.circle(self.mask,(x,y),self.BrushSiz,cv.GC_FGD,-1)
elif event==cv.EVENT_RBUTTONDOWN:
cv.circle(self.img_show,(x,y),self.BrushSiz,self.RColor,-1) # 오른쪽 버튼을 클릭하면 빨간색
cv.circle(self.mask,(x,y),self.BrushSiz,cv.GC_BGD,-1)
elif event==cv.EVENT_MOUSEMOVE and flags==cv.EVENT_FLAG_LBUTTON:
cv.circle(self.img_show,(x,y),self.BrushSiz,self.LColor,-1) # 왼쪽 버튼을 클릭하고 이동하면 파란색
cv.circle(self.mask,(x,y),self.BrushSiz,cv.GC_FGD,-1)
elif event==cv.EVENT_MOUSEMOVE and flags==cv.EVENT_FLAG_RBUTTON:
cv.circle(self.img_show,(x,y),self.BrushSiz,self.RColor,-1) # 오른쪽 버튼을 클릭하고 이동하면 빨간색
cv.circle(self.mask,(x,y),self.BrushSiz,cv.GC_BGD,-1)
cv.imshow('Painting',self.img_show)
def cutFunction(self):
background=np.zeros((1,65),np.float64)
foreground=np.zeros((1,65),np.float64)
cv.grabCut(self.img,self.mask,None,background,foreground,5,cv.GC_INIT_WITH_MASK)
mask2=np.where((self.mask==2)|(self.mask==0),0,1).astype('uint8')
self.grabImg=self.img*mask2[:,:,np.newaxis]
cv.imshow('Scissoring',self.grabImg)
def incFunction(self):
self.BrushSiz=min(20,self.BrushSiz+1) # 붓 크기 +1, 붓 크기 20 넘지 않도록 함
def decFunction(self):
self.BrushSiz=max(1,self.BrushSiz-1) # 붓 크기 -1, 붓 크기가 1보다 작아지지 않게 함
def saveFunction(self):
fname=QFileDialog.getSaveFileName(self,'파일 저장','./')
cv.imwrite(fname[0],self.grabImg)
def quitFunction(self): # OpenCV가 연 모든 윈도우 닫고 프로그램 종료시킴
cv.destroyAllWindows()
self.close()
# main
app=QApplication(sys.argv)
win=Orim()
win.show()
app.exec_()
🌿 실행 결과
<기능>
import cv2 as cv
import numpy as np
from PyQt5.QtWidgets import *
import sys
import winsound
class TrafficWeak(QMainWindow):
def __init__(self):
super().__init__()
# 윈도우 제목,위치,크기 설정
self.setWindowTitle('교통약자 보호')
self.setGeometry(200,200,700,200)
# 버튼 4개, 레이블 1개 생성
signButton=QPushButton('표지판 등록',self)
roadButton=QPushButton('도로 영상 불러옴',self)
recognitionButton=QPushButton('인식',self)
quitButton=QPushButton('나가기',self)
self.label=QLabel('환영합니다!',self)
# 버튼 클릭 시 수행할 콜백 함수 지정
signButton.setGeometry(10,10,100,30)
roadButton.setGeometry(110,10,100,30)
recognitionButton.setGeometry(210,10,100,30)
quitButton.setGeometry(510,10,100,30)
self.label.setGeometry(10,40,600,170)
signButton.clicked.connect(self.signFunction)
roadButton.clicked.connect(self.roadFunction)
recognitionButton.clicked.connect(self.recognitionFunction)
quitButton.clicked.connect(self.quitFunction)
self.signFiles=[['child.png','어린이'],['elder.png','노인'],['disabled.png','장애인']] # 표지판 모델 영상 이름 설정
self.signImgs=[] # 표지판 모델 영상 저장할 signImgs 객체 생성
# 콜백함수 선언
def signFunction(self):
self.label.clear() # 레이블 지움
self.label.setText('교통약자 번호판을 등록합니다.') # 메시지 출력
for fname,_ in self.signFiles: # signFiles 리스트에 들어있는 요소 각각에 대해 파일 이름을 fname에 담음
self.signImgs.append(cv.imread(fname)) # 이름이 fname인 영상 파일 읽어 signImgs에 추가
cv.imshow(fname,self.signImgs[-1]) # signImgs의 맨 뒤에 있는(= 방금 추가한 영상)을 fname이라는 제목 표시줄 가진 윈도우에 디스플레이
def roadFunction(self):
if self.signImgs==[]: # signImgs가 비었을 때(= 사용자가 표지판 등록 안 하고 '도로 영상 불러옴' 버튼 클릭했을 때)
self.label.setText('먼저 번호판을 등록하세요.') # 메시지 표시
else: # 표지판 제대로 등록해둔 경우
fname=QFileDialog.getOpenFileName(self,'파일 읽기','./') # 사용자가 폴더 브라우징하면서 도로 영상 선택할 수 있게 함
self.roadImg=cv.imread(fname[0]) # 영상 파일 읽어 roadImg 객체에 저장
if self.roadImg is None: sys.exit('파일을 찾을 수 없습니다.')
cv.imshow('Road scene',self.roadImg) # 읽은 영상을 윈도우에 디스플레이
def recognitionFunction(self):
if self.roadImg is None: # 사용자가 도로 영상 입력 안 하고 '인식' 버튼 클릭했을 때
self.label.setText('먼저 도로 영상을 입력하세요.')
else: # 도로 영상 제대로 입력해둔 경우
sift=cv.SIFT_create() # SIFT 검출하는 데 사용할 객체 생성
## signImgs에 담겨있는 3장의 표지판 모델 영상에서 특징점 & 기술자 차례로 추출하여 KD에 저장
KD=[] # 여러 표지판 영상의 키포인트와 기술자 저장
for img in self.signImgs:
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
KD.append(sift.detectAndCompute(gray,None))
grayRoad=cv.cvtColor(self.roadImg,cv.COLOR_BGR2GRAY) # 명암으로 변환
road_kp,road_des=sift.detectAndCompute(grayRoad,None) # 키포인트와 기술자 추출
## 여러 표지판 영상의 good match를 저장 => GD : 표지판 영상 3장 각각에 대해 좋은 매칭 리스트 가지게 됨
matcher=cv.DescriptorMatcher_create(cv.DescriptorMatcher_FLANNBASED) # FLANN 기반 매칭해주는 matcher 객체 생성
GM=[] # 3장의 모델 영상이랑 매칭한 결과 저장할 GM 객체 생성
for sign_kp,sign_des in KD: # 표지판 모델 영상에서 추출해둔 특징점 & 기술자를 KD에서 하나씩 꺼내서 sign_kp, sign_des에 저장하고 반복문 실행
knn_match=matcher.knnMatch(sign_des,road_des,2) # sign_des & road_des 매칭하여 특징점마다 최근접 이웃 2개 찾음
T=0.7
# 좋은 매칭 골라 good_match에 저장
good_match=[]
for nearest1,nearest2 in knn_match:
if (nearest1.distance/nearest2.distance)<T:
good_match.append(nearest1)
GM.append(good_match) # good_match를 GD에 추가
best=GM.index(max(GM,key=len)) # 표지판 영상 3장의 매칭 리스트 중 max 찾음 => 매칭 쌍 개수가 최대인 번호판 찾기
if len(GM[best])<4: # 최선의 번호판이 매칭 쌍 4개 미만이면 인식 실패로 간주
self.label.setText('표지판이 없습니다.')
else: # 인식에 성공한 경우(호모그래피 찾아 영상에 표시)
sign_kp=KD[best][0] # 표지판 영상의 특징점
good_match=GM[best] # 매칭 쌍 정보 저장
points1=np.float32([sign_kp[gm.queryIdx].pt for gm in good_match])
points2=np.float32([road_kp[gm.trainIdx].pt for gm in good_match])
H,_=cv.findHomography(points1,points2,cv.RANSAC)
h1,w1=self.signImgs[best].shape[0],self.signImgs[best].shape[1] # 번호판 영상의 크기
h2,w2=self.roadImg.shape[0],self.roadImg.shape[1] # 도로 영상의 크기
box1=np.float32([[0,0],[0,h1-1],[w1-1,h1-1],[w1-1,0]]).reshape(4,1,2)
box2=cv.perspectiveTransform(box1,H)
self.roadImg=cv.polylines(self.roadImg,[np.int32(box2)],True,(0,255,0),4)
img_match=np.empty((max(h1,h2),w1+w2,3),dtype=np.uint8)
cv.drawMatches(self.signImgs[best],sign_kp,self.roadImg,road_kp,good_match,img_match,flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
cv.imshow('Matches and Homography',img_match)
self.label.setText(self.signFiles[best][1]+' 보호구역입니다. 30km로 서행하세요.') # best가 0:어린이, 1:노인, 2:장애인
winsound.Beep(3000,500) # 비프음 발생
def quitFunction(self): # '나가기' 버튼 클릭 시 실행
cv.destroyAllWindows()
self.close()
app=QApplication(sys.argv)
win=TrafficWeak()
win.show()
app.exec_()
🌿 프로그램의 한계점
🌿 기능
⇒ 결과 : 수집한 영상들 봉합한 파노라마 영상 보여줌
from PyQt5.QtWidgets import *
import cv2 as cv
import numpy as np
import winsound
import sys
# Panorama 클래스 선언
class Panorama(QMainWindow) :
def __init__(self) :
super().__init__()
self.setWindowTitle('파노라마 영상')
self.setGeometry(200,200,700,200)
# 버튼 5개, 레이블 1개 생성
collectButton=QPushButton('영상 수집',self)
self.showButton=QPushButton('영상 보기',self)
self.stitchButton=QPushButton('봉합',self)
self.saveButton=QPushButton('저장',self)
quitButton=QPushButton('나가기',self)
self.label=QLabel('환영합니다!',self)
collectButton.setGeometry(10,25,100,30)
self.showButton.setGeometry(110,25,100,30)
self.stitchButton.setGeometry(210,25,100,30)
self.saveButton.setGeometry(310,25,100,30)
quitButton.setGeometry(450,25,100,30)
self.label.setGeometry(10,70,600,170)
# 나머지 3개 버튼 비활성화
self.showButton.setEnabled(False)
self.stitchButton.setEnabled(False)
self.saveButton.setEnabled(False)
# 버튼 클릭 시 수행할 콜백함수 등록
collectButton.clicked.connect(self.collectFunction)
self.showButton.clicked.connect(self.showFunction)
self.stitchButton.clicked.connect(self.stitchFunction)
self.saveButton.clicked.connect(self.saveFunction)
quitButton.clicked.connect(self.quitFunction)
# <영상 수집> 버튼 클릭 시 실행
def collectFunction(self):
# <영상 보기>,<봉합>,<저장> 버튼 비활성화 : 원래 프로그램 시작될 때 비활성화되지만, 사용자가 다시 시도하는 경우 대비
self.showButton.setEnabled(False)
self.stitchButton.setEnabled(False)
self.saveButton.setEnabled(False)
self.label.setText('c를 여러 번 눌러 수집하고 끝나면 q를 눌러 비디오를 끕니다.')
self.cap=cv.VideoCapture(0,cv.CAP_DSHOW)
if not self.cap.isOpened(): sys.exit('카메라 연결 실패')
self.imgs=[]
while True:
ret,frame=self.cap.read()
if not ret: break
cv.imshow('video display', frame)
key=cv.waitKey(1)
if key==ord('c'): # c 누르면
self.imgs.append(frame) # 영상 저장
elif key==ord('q'): # q 누르면
self.cap.release() # 비디오 연결 끊고
cv.destroyWindow('video display') # 연결된 윈도우 닫음
break
if len(self.imgs)>=2: # 수집한 영상이 2장 이상이면 나머지 3개 버튼 활성화
self.showButton.setEnabled(True)
self.stitchButton.setEnabled(True)
self.saveButton.setEnabled(True)
# <영상 보기> 버튼 클릭 시 실행
def showFunction(self):
self.label.setText('수집된 영상은 '+str(len(self.imgs))+'장 입니다.') # 수집된 영상 수 레이블에 표시
stack=cv.resize(self.imgs[0],dsize=(0,0),fx=0.25,fy=0.25) # 수집된 영상 0.25배 축소
for i in range(1,len(self.imgs)): # 수집한 영상들 가로로 이어붙임
stack=np.hstack((stack,cv.resize(self.imgs[i],dsize=(0,0),fx=0.25,fy=0.25)))
cv.imshow('Image collection',stack) # 이어붙인 영상 디스플레이
# <봉합> 버튼 클릭 시 실행
def stitchFunction(self):
stitcher=cv.Stitcher_create() # 영상 봉합에 쓸 stitcher 객체 생성
status,self.img_stitched=stitcher.stitch(self.imgs) # stitch 함수로 봉합 시도, stitch(수집한 영상 객체)
if status==cv.STITCHER_OK: # 봉합 성공
cv.imshow('Image stitched panorama',self.img_stitched) # 새 윈도우에 파노라마 영상 디스플레이
else: # 봉합 실패
winsound.Beep(3000,500) # 비프음 출력
self.label.setText('파노라마 제작에 실패했습니다. 다시 시도하세요.')
# <저장> 버튼 클릭 시 실행
def saveFunction(self):
fname=QFileDialog.getSaveFileName(self,'파일 저장','./')
cv.imwrite(fname[0],self.img_stitched)
# <나가기> 버튼 클릭 시 실행
def quitFunction(self):
self.cap.release()
cv.destroyAllWindows()
self.close()
app=QApplication(sys.argv)
win=Panorama()
win.show()
app.exec_()
🌿 실행 결과
음….??..ㅠㅠ
💡 함수 매개변수 확인
➡️ 양방향 필터 식
2개의 가우시안 함수
gs_(i) : i=0 일 때 가장 큼, i가 0에서 멀어질수록 작아짐
g_r(f(x)-f(x+i)
⇒ 물체 경계에서 서로 다른 물체에 속한 화소는 서로에게 영향력 낮아짐 → 에지 잘 보존
stylization & pencilSketch 매개변수
sigma_s
: g_s의 표준편차, 클수록 영상 흐릿하게 만드는 효과 upsigma_r
: g_r의 표준편차, 작을수록 에지 보존 효과 up🌿 사용자 인터페이스
import cv2 as cv
import numpy as np
from PyQt5.QtWidgets import *
import sys
class SpecialEffect(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('사진 특수 효과')
self.setGeometry(200,200,800,200)
# 버튼 7개,레이블 1개,콤보박스 1개 생성
pictureButton=QPushButton('사진 읽기',self)
embossButton=QPushButton('엠보싱',self)
cartoonButton=QPushButton('카툰',self)
sketchButton=QPushButton('연필 스케치',self)
oilButton=QPushButton('유화',self)
saveButton=QPushButton('저장하기',self)
self.pickCombo=QComboBox(self)
self.pickCombo.addItems(['엠보싱','카툰','연필 스케치(명암)','연필 스케치(컬러)','유화'])
quitButton=QPushButton('나가기',self)
self.label=QLabel('환영합니다!',self)
# 버튼, 레이블, 콤보박스 위젯의 위치와 크기를 지정
pictureButton.setGeometry(10,10,100,30)
embossButton.setGeometry(110,10,100,30)
cartoonButton.setGeometry(210,10,100,30)
sketchButton.setGeometry(310,10,100,30)
oilButton.setGeometry(410,10,100,30)
saveButton.setGeometry(510,10,100,30)
self.pickCombo.setGeometry(510,40,110,30)
quitButton.setGeometry(620,10,100,30)
self.label.setGeometry(10,40,500,170)
# 버튼을 클릭했을 때 수행할 콜백 함수 등록
pictureButton.clicked.connect(self.pictureOpenFunction)
embossButton.clicked.connect(self.embossFunction)
cartoonButton.clicked.connect(self.cartoonFunction)
sketchButton.clicked.connect(self.sketchFunction)
oilButton.clicked.connect(self.oilFunction)
saveButton.clicked.connect(self.saveFunction)
quitButton.clicked.connect(self.quitFunction)
# <사진 읽기> 버튼 클릭 시 실행
def pictureOpenFunction(self):
# 폴더에서 사진 파일을 브라우징하여 선택하고 읽어오기
fname=QFileDialog.getOpenFileName(self,'사진 읽기','./')
self.img=cv.imread(fname[0])
if self.img is None: sys.exit('파일을 찾을 수 없습니다.')
cv.imshow('Painting',self.img)
# <엠보싱> 버튼 클릭 시 실행
def embossFunction(self):
femboss=np.array([[-1.0, 0.0, 0.0],[0.0, 0.0, 0.0],[0.0, 0.0, 1.0]]) # 엠보싱 필터 정의
# 명암 영상으로 변환하 고 컨볼루션을 적용
gray=cv.cvtColor(self.img,cv.COLOR_BGR2GRAY)
gray16=np.int16(gray)
self.emboss=np.uint8(np.clip(cv.filter2D(gray16,-1,femboss)+128,0,255))
cv.imshow('Emboss',self.emboss)
# <카툰> 버튼 클릭 시 실행
def cartoonFunction(self):
self.cartoon=cv.stylization(self.img,sigma_s=60,sigma_r=0.45) # 매개변수는 기본값으로 설정
cv.imshow('Cartoon',self.cartoon)
# <연필 스케치> 버튼 클릭 시 실행
def sketchFunction(self):
self.sketch_gray,self.sketch_color=cv.pencilSketch(self.img,sigma_s=60,sigma_r=0.07,shade_factor=0.02)
cv.imshow('Pencil sketch(gray)',self.sketch_gray)
cv.imshow('Pencil sketch(color)',self.sketch_color)
# <유화> 버튼 클릭 시 실행
def oilFunction(self):
self.oil=cv.xphoto.oilPainting(self.img,10,1,cv.COLOR_BGR2Lab)
cv.imshow('Oil painting',self.oil)
def saveFunction(self):
fname=QFileDialog.getSaveFileName(self,'파일 저장','./')
i=self.pickCombo.currentIndex()
if i==0: cv.imwrite(fname[0],self.emboss)
elif i==1: cv.imwrite(fname[0],self.cartoon)
elif i==2: cv.imwrite(fname[0],self.sketch_gray)
elif i==3: cv.imwrite(fname[0],self.sketch_color)
elif i==4: cv.imwrite(fname[0],self.oil)
def quitFunction(self):
cv.destroyAllWindows()
self.close()
app=QApplication(sys.argv)
win=SpecialEffect()
win.show()
app.exec_()
import cv2 as cv
import numpy as np
from PyQt5.QtWidgets import *
import sys
class VideoSpecialEffect(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('비디오 특수 효과')
self.setGeometry(200,200,400,100)
videoButton=QPushButton('비디오 시작',self)
self.pickCombo=QComboBox(self)
self.pickCombo.addItems(['엠보싱','카툰','연필 스케치(명암)','연필 스케치(컬러)','유화'])
quitButton=QPushButton('나가기',self)
videoButton.setGeometry(10,10,140,30)
self.pickCombo.setGeometry(150,10,110,30)
quitButton.setGeometry(280,10,100,30)
videoButton.clicked.connect(self.videoSpecialEffectFunction)
quitButton.clicked.connect(self.quitFunction)
def videoSpecialEffectFunction(self):
self.cap=cv.VideoCapture(0,cv.CAP_DSHOW)
if not self.cap.isOpened(): sys.exit('카메라 연결 실패')
while True:
ret,frame=self.cap.read()
if not ret: break
pick_effect=self.pickCombo.currentIndex()
if pick_effect==0:
femboss=np.array([[-1.0, 0.0, 0.0],[0.0, 0.0, 0.0],[0.0, 0.0, 1.0]])
gray=cv.cvtColor(frame,cv.COLOR_BGR2GRAY)
gray16=np.int16(gray)
special_img=np.uint8(np.clip(cv.filter2D(gray16,-1,femboss)+128,0,255))
elif pick_effect==1:
special_img=cv.stylization(frame,sigma_s=60,sigma_r=0.45)
elif pick_effect==2:
special_img,_=cv.pencilSketch(frame,sigma_s=60,sigma_r=0.07,shade_factor=0.02)
elif pick_effect==3:
_,special_img=cv.pencilSketch(frame,sigma_s=60,sigma_r=0.07,shade_factor=0.02)
elif pick_effect==4:
special_img=cv.xphoto.oilPainting(frame,10,1,cv.COLOR_BGR2Lab)
cv.imshow('Special effect',special_img)
cv.waitKey(1)
def quitFunction(self):
self.cap.release()
cv.destroyAllWindows()
self.close()
app=QApplication(sys.argv)
win=VideoSpecialEffect()
win.show()
app.exec_()