디시인사이드 갤러리

갤러리 이슈박스, 최근방문 갤러리

갤러리 본문 영역

북스캔 파이썬 코드 퍼블릭 도메인 (2)

몬발켜갤로그로 이동합니다. 2024.04.30 12:39:57
조회 51 추천 1 댓글 0

<PyQT5를 이용해서 GUI를 추가함... 허접한 GUI이긴 하지만..>


import sys
import os
import cv2
import numpy as np
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QLabel, QMessageBox
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtGui import QFont
import time



class ImageProcessingThread(QThread):
    update_signal = pyqtSignal(int, int)
    finished_signal = pyqtSignal()  # 작업 완료 시그널

    def run(self):
        try:
            folder_path = "C:/input"
            file_list = os.listdir(folder_path)
            image_extensions = [".jpg", ".jpeg", ".png"]
            image_files = [file for file in file_list if any(file.lower().endswith(ext) for ext in image_extensions)]

            total_files = len(image_files)
            processed_files = 0

            # 각 파일을 하나씩 불러온다
            for image_name in image_files:

                # 이미지 파일을 불러온다
                image = cv2.imread("C:\\input\\{}".format(image_name))

                # 그레이 스케일 및 이진화
                gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
                _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU )

                # 컨투어 찾기
                contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

                # 가장 큰 컨투어 찾기
                largest_contour = max(contours, key=cv2.contourArea)

                # 가장 큰 컨투어에 불필요한 점/점들이 포함된 경우가 종종 발생하기 때문에 제거할 필요가 있다
                # 새로운 이미지 생성 (검은색 배경)
                new_image = np.zeros_like(binary)

                # 가장 큰 컨투어 중에서 면적이 가장 큰 폐곡선 찾기
                largest_closed_contour = max([c for c in contours if cv2.contourArea(c) > 0], key=cv2.contourArea)

                # 면적이 가장 큰 폐곡선 안에 있는 흰색 점들만 남기고 나머지는 삭제하여 새 이미지에 저장
                cv2.drawContours(new_image, [largest_closed_contour], -1, (255), thickness=cv2.FILLED)

                # 컨투어를 둘러싼 가장 작은 사각형 찾기
                min_rect = cv2.minAreaRect(largest_closed_contour)

                # minAreaRect에서 반환한 4개 점은 실수값을 갖으므로, 사용하려면 정수로 변환한다
                box_points = cv2.boxPoints(min_rect)
                box_points = np.intp(box_points)

                # 4개 점을 좌표값에 따라서 번호를 부여한다
                # 원래의 의도는 좌상, 우상, 좌하, 우하 순서로 P1, P2, P3, P4를 부여하려고 하였다
                # 그러나 프로그래머의 일반적인 코드 규칙에 따라서 점의 번호를 부여하기로 생각을 바꿨다.
                # The order will be P1 (top-left), P2 (top-right), P3 (bottom-left), P4 (bottom-right)
                box_points = sorted(box_points, key=lambda x: (x[1], x[0]))
                if box_points[0][0] > box_points[1][0]:
                    box_points[0], box_points[1] = box_points[1], box_points[0]
                if box_points[2][0] > box_points[3][0]:
                    box_points[2], box_points[3] = box_points[3], box_points[2]

                # 프로그래머의 일반적인 코드 부여 방법에 따라서 점의 번호를 정했다
                P1 = box_points[0]
                P2 = box_points[1]
                P3 = box_points[2]
                P4 = box_points[3]

                if P1[0] < P2[0]: # 기울기가 양수일 때(왼쪽으로 기울었다:시계방향으로 회전해야 한다)
                    x1, y1 = P1[0], P1[1]
                    x2, y2 = P2[0], P2[1]

                    # 기울기 slope를 계산한다.
                    delta_x = x2 - x1
                    delta_y = y2 - y1
                    slope = delta_y / delta_x if delta_x != 0 else None

                    # 기울기에 따라서 회전할 각과 방향을 계산한다
                    # Calculate the angle in radians and then convert to degrees
                    # The angle must be negative for a clockwise rotation
                    angle_of_rotation = -np.degrees(np.arctan(slope))

                    # 시계방향으로 angle만큼 회전하고, 변수에 저장한다
                    # center에 들어갈 점 P의 좌표는 데이터형을 변환해야 한다
                    center = (int(P1[0]), int(P1[1]))
                    angle = -angle_of_rotation
                    M = cv2.getRotationMatrix2D(center, angle, 1)
                    rotated_image = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

                    # 회전한 이미지를 저장하지 않고 변수를 바로 사용하여 다음 단계의 작업을 진행한다
                    #그레이 스케일 및 이진화
                    gray1 = cv2.cvtColor(rotated_image, cv2.COLOR_BGR2GRAY)
                    _, binary1 = cv2.threshold(gray1, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU )

                   
                    # 회전한 이미지의 컨투어 찾기
                    contours1, _ = cv2.findContours(binary1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

                    # 회전한 이미지의 가장 큰 컨투어 찾기
                    largest_contour1 = max(contours1, key=cv2.contourArea)

                    # 새로운 이미지 생성 (검은색 배경)
                    new_image1 = np.zeros_like(binary1)

                    # 가장 큰 컨투어에 불필요한 점/점들이 포함된 경우가 종종 발생하기 때문에 제거할 필요가 있다
                    # 회전한 이미지의 가장 큰 컨투어 중에서 면적이 가장 큰 폐곡선 찾기
                    largest_closed_contour1 = max([c for c in contours1 if cv2.contourArea(c) > 0], key=cv2.contourArea)

                    # 면적이 가장 큰 폐곡선 안에 있는 흰색 점들만 남기고 나머지는 삭제하여 새 이미지에 저장
                    cv2.drawContours(new_image1, [largest_closed_contour1], -1, (255), thickness=cv2.FILLED)

                    # 회전한 이미지의 컨투어를 포함하는 가장 작은 사각형을 찾는다
                    min_rect1 = cv2.minAreaRect(largest_closed_contour1)

                    # minAreaRect에서 반환한 4개 점은 실수값을 갖으므로, 사용하려면 정수로 변환한다
                    # Convert it to box points (four points)
                    box_points1 = cv2.boxPoints(min_rect1)
                    box_points1 = np.intp(box_points1)

                    # # 4개 점을 좌표값에 따라서 번호를 부여한다
                    # 원래의 의도는 좌상, 우상, 좌하, 우하 순서로 P1, P2, P3, P4를 부여하려고 하였다
                    # 그러나 프로그래머의 일반적인 코드 규칙에 따라서 점의 번호를 부여하기로 생각을 바꿨다.
                    # The order will be P1 (top-left), P2 (top-right), P3 (bottom-left), P4 (bottom-right)
                    box_points1 = sorted(box_points1, key=lambda x: (x[1], x[0]))
                    if box_points1[0][0] > box_points1[1][0]:
                        box_points1[0], box_points1[1] = box_points1[1], box_points1[0]
                    if box_points1[2][0] > box_points1[3][0]:
                        box_points1[2], box_points1[3] = box_points1[3], box_points1[2]

                    # 프로그래머의 일반적인 코드 부여 방법에 따라서 점의 번호를 정했다
                    # 점의 번호를 앞의 점과 다르게 해서 혼동을 피했다    
                    P11 = box_points1[0]
                    P22 = box_points1[1]
                    P33 = box_points1[2]
                    P44 = box_points1[3]
                   
                    # 4개 점의 좌표를 이용하여 사각형 영역을 택하여 새 그림 파일로 저장한다
                    k = rotated_image[P11[1]:P33[1], P11[0]:P22[0]]

                    cv2.imwrite("C:\\output\\{}".format(image_name), k)

                else: # 기울기가 양수가 아닐 때(오른쪽으로 기울었다:반시계방향으로 회전해야 한다)

                    x1, y1 = P1[0], P1[1]
                    x2, y2 = P2[0], P2[1]

                    # 기울기를 계산한다
                    delta_x = x2 - x1
                    delta_y = y2 - y1
                    slope = delta_y / delta_x if delta_x != 0 else None

                    # 기울기에 따라서 회전할 각과 방향을 계산한다
                    # If the slope is zero (horizontal line), we do not need to rotate.
                    # If the slope is negative or undefined (vertical line), we rotate counterclockwise.
                    # The angle must be positive for a counterclockwise rotation
                    angle_of_rotation = np.degrees(np.arctan(-slope)) if slope is not None else 90

                    # 반시계방향으로 angle만큼 회전하여 변수에 저장한다
                    # center에 들어갈 점 P의 좌표는 데이터형을 변환해야 한다
                    center = (int(P1[0]), int(P1[1]))
                    angle = angle_of_rotation
                    M = cv2.getRotationMatrix2D(center, angle, 1)
                    rotated_image = cv2.warpAffine(image, M, (image.shape[1], image.shape[0]))

                    # 회전한 이미지를 저장하지 않고 변수를 바로 사용하여 다음 단계의 작업을 진행한다  
                    #그레이 스케일 및 이진화
                    gray1 = cv2.cvtColor(rotated_image, cv2.COLOR_BGR2GRAY)
                    _, binary1 = cv2.threshold(gray1, 127, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU )

                    # 회전한 이미지의 컨투어 찾기
                    contours1, _ = cv2.findContours(binary1, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

                    # 회전한 이미지의 가장 큰 컨투어 찾기
                    largest_contour1 = max(contours1, key=cv2.contourArea)

                    # 새로운 이미지 생성 (검은색 배경)
                    new_image1 = np.zeros_like(binary1)

                    # 회전한 이미지의 가장 큰 컨투어 중에서 면적이 가장 큰 폐곡선 찾기
                    largest_closed_contour1 = max([c for c in contours1 if cv2.contourArea(c) > 0], key=cv2.contourArea)

                    # 면적이 가장 큰 폐곡선 안에 있는 흰색 점들만 남기고 나머지는 삭제하여 새 이미지에 저장
                    cv2.drawContours(new_image1, [largest_closed_contour1], -1, (255), thickness=cv2.FILLED)

                    # 회전한 이미지의 컨투어를 포함하는 가장 작은 사각형을 찾는다
                    min_rect1 = cv2.minAreaRect(largest_closed_contour1)

                    # minAreaRect에서 반환한 4개 점은 실수값을 갖으므로, 사용하려면 정수로 변환한다
                    # Convert it to box points (four points)
                    box_points1 = cv2.boxPoints(min_rect1)
                    box_points1 = np.intp(box_points1)

                    # 4개 점을 좌표값에 따라서 번호를 부여한다
                    # 원래의 의도는 좌상, 우상, 좌하, 우하 순서로 P1, P2, P3, P4를 부여하려고 하였다
                    # 그러나 프로그래머의 일반적인 코드 규칙에 따라서 점의 번호를 부여하기로 생각을 바꿨다.
                    # The order will be P1 (top-left), P2 (top-right), P3 (bottom-left), P4 (bottom-right)
                    box_points1 = sorted(box_points1, key=lambda x: (x[1], x[0]))
                    if box_points1[0][0] > box_points1[1][0]:
                        box_points1[0], box_points1[1] = box_points1[1], box_points1[0]
                    if box_points1[2][0] > box_points1[3][0]:
                        box_points1[2], box_points1[3] = box_points1[3], box_points1[2]

                    # 프로그래머의 일반적인 코드 부여 방법에 따라서 점의 번호를 정했다
                    # 점의 번호를 앞의 점과 다르게 해서 혼동을 피했다  
                    P11 = box_points1[0]
                    P22 = box_points1[1]
                    P33 = box_points1[2]
                    P44 = box_points1[3]
                   
                    # 4개 점의 좌표를 이용하여 사각형 영역을 택하여 새 그림 파일로 저장한다
                    k = rotated_image[P11[1]:P33[1], P11[0]:P22[0]]

                    cv2.imwrite("C:\\output\\{}".format(image_name), k)
           


                processed_files += 1
                self.update_signal.emit(total_files, processed_files)

                time.sleep(0.1)  # 짧은 지연 추가


        # 모든 작업 완료 후 신호 발생 전에 짧은 지연을 추가
            self.finished_signal.emit()
            time.sleep(0.2)
        except Exception as e:
           print("Error during image processing:", e)
           self.finished_signal.emit()


class ImageProcessorGUI(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()

    def initUI(self):
        self.setWindowTitle('Dadong')
        self.setFixedSize(500, 300)  # 가로 500, 세로 300 크기로 고정

        self.layout = QVBoxLayout()

        # QFont 객체 생성
        font = QFont()
        font.setPointSize(font.pointSize() * 2)  # 현재 폰트 크기의 2배로 설정

     
        # 실행 버튼
        self.startButton = QPushButton('실행')
        self.startButton.setFont(font)  # 폰트 적용
        self.startButton.clicked.connect(self.startProcessing)
        self.layout.addWidget(self.startButton)
       
        # 전체 파일 수를 표시하는 레이블
        self.totalFilesLabel = QLabel('input total files: 0')
        self.totalFilesLabel.setFont(font)  # 폰트 적용
        self.layout.addWidget(self.totalFilesLabel)

        # 처리된 파일 수를 표시하는 레이블
        self.processedFilesLabel = QLabel('output processed files: 0')
        self.processedFilesLabel.setFont(font)  # 폰트 적용
        self.layout.addWidget(self.processedFilesLabel)

        # '설명' 버튼 추가
        self.descriptionButton = QPushButton('설명')
        self.descriptionButton.setFont(font)  # 폰트 적용
        self.descriptionButton.clicked.connect(self.showDescription)
        self.layout.addWidget(self.descriptionButton)

        self.setLayout(self.layout)

        self.thread = ImageProcessingThread()
        self.thread.update_signal.connect(self.updateStatus)
        self.thread.finished_signal.connect(self.closeProgram)  # 작업 완료 시그널 연결

    def showDescription(self):
        description = (
            "'C:의 input 폴더에 사진 파일을 저장하세요 \n\n"
            "파일 이름에 한글이 있으면 처리가 중단됩니다 \n\n"
            "C:의 output 폴더에 책 파일들이 저장됩니다 \n\n"
            "처리가 완료되면 프로그램이 자동 종료됩니다 \n\n"
           
        )
        QMessageBox.information(self, "설명", description)        

    def startProcessing(self):
        self.thread.start()

    def closeProgram(self):
        if self.thread.isRunning():  # 스레드가 실행 중인지 확인
            self.thread.quit()  # 스레드에 종료 요청
            self.thread.wait(1000)  # 스레드가 완전히 종료될 때까지 기다림
        self.close()  # 이후 GUI 종료

    # 상태 업데이트 메서드
    def updateStatus(self, total, processed):
        self.totalFilesLabel.setText(f'input total files: {total}')
        self.processedFilesLabel.setText(f'output processed files: {processed}')    



def main():
    app = QApplication(sys.argv)
    ex = ImageProcessorGUI()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()


추천 비추천

1

고정닉 1

0

댓글 영역

전체 댓글 0
등록순정렬 기준선택
본문 보기

하단 갤러리 리스트 영역

왼쪽 컨텐츠 영역

갤러리 리스트 영역

갤러리 리스트
번호 제목 글쓴이 작성일 조회 추천
설문 어떤 상황이 닥쳐도 지갑 절대 안 열 것 같은 스타는? 운영자 24/05/20 - -
2698228 미래의 어느 역사학자들의 대화라는데 (요약편) 프갤러(211.241) 05.21 34 0
2698227 미래의 어느 역사학자들의 대화라는데 프갤러(211.241) 05.21 38 0
2698225 류류 저새끼 프갤에서 몰아내자 [2] 초코냥갤로그로 이동합니다. 05.21 71 0
2698220 나 좋아하는 여자 프사보고 ㄸ친거 범죄야? [9] 딱꾹(113.198) 05.21 64 0
2698215 네가 그래도 양심이 있는데 일반인 보고 그거 안한다. 도리스아(119.195) 05.20 23 0
2698214 코파일럿 외않씀? 프갤러(118.235) 05.20 25 0
2698213 서울대생 음란물 걸렸되요. [1] 도리스아(119.195) 05.20 69 1
2698212 딱국과 최원종 관계 제보 받는다. rockon01@naver.com 조커@두팩트수보(175.117) 05.20 36 5
2698211 오늘 헬쓰장에서 트레드밀 뛰면서 영화 <마일22>를 봤다 발명도둑잡기갤로그로 이동합니다. 05.20 16 0
2698210 전공 비전공 차이크냐? [1] 프갤러(118.235) 05.20 62 0
2698209 따꾸기 땜 [2] 조커@두팩트수보(175.117) 05.20 40 0
2698208 컴공갈바에 전자공갔으면 초봉1.5배는 더 받았을듯 [6] 프갤러(112.187) 05.20 89 0
2698207 갤기장은 결국 나님을 버륀 거야 ㅇㅅㅇ [2] 나트륨찡갤로그로 이동합니다. 05.20 29 0
2698206 친구 업는 나틂륨은 ㅇㅅㅇ [2] 나트륨찡갤로그로 이동합니다. 05.20 35 1
2698205 개발자vs9급 [1] 프갤러(118.235) 05.20 49 0
2698204 음식 잘옷먹은듯.. 체한거 같다.. [1] ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 35 0
2698203 술먹고 토하는 사람 없냐??오늘 술몇잔 쳐먹고 안취하겠다고 쪼다마냥 토함 [2] ㅇㅇ(223.38) 05.20 60 0
2698202 신입 SI개발자가 동안이라서 좋은 점 [3] ㅇㅇ갤로그로 이동합니다. 05.20 134 1
2698201 성욕해소에는 [6] 멍청한유라ㅋ갤로그로 이동합니다. 05.20 75 1
2698200 아래 성욕 때문에 고민한다는 남자 발명도둑잡기갤로그로 이동합니다. 05.20 27 0
2698199 mysql db 이미지로 인스턴스 띄우는거 어려움? [2] 프갤러(175.213) 05.20 103 0
2698197 9to4,10to5에 주4일 직장이 최고임 ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 39 0
2698196 따꾸기 발정난거만 용서해주면 안됑? ㅠㅠㅠ [2] 딱국(113.198) 05.20 31 0
2698195 운덩 강도를 낮추고 빈도를 늘려야겠음 [6] ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 45 0
2698194 진척 딱군 따랑햇 ㅠㅠ 딱국이랑 동맹맺쟝 [1] 딱국(113.198) 05.20 22 0
2698193 이제 자자 [2] 멍청한유라ㅋ갤로그로 이동합니다. 05.20 40 0
2698192 직구 관련 내용 정리해준다 cvs.갤로그로 이동합니다. 05.20 26 1
2698191 취업한지 6개월된 신입인데 이거 ㅈ된거냐?? [3] 프갤러(59.18) 05.20 177 2
2698190 민주당 갤러리 vs 국힘 갤러리 연령대 비교..JPG 발명도둑잡기갤로그로 이동합니다. 05.20 45 0
2698188 DIY Cardboard FM Synth (8 DX7s!) | MiniD 발명도둑잡기갤로그로 이동합니다. 05.20 13 0
2698187 아니 이거 토스가 아니라 국비라고? [1] 딘퐁갤로그로 이동합니다. 05.20 132 0
2698185 말세입니다. 이거 엄청 좊습니다. 도리스아(119.195) 05.20 29 0
2698184 부캠 추천좀 NHN 아카데미 vs 유플러스 유레카 [4] 프갤러(125.241) 05.20 99 0
2698182 7to4기업 어때? [3] 멍유바라기(112.169) 05.20 43 0
2698181 #PICOmputer 28 - latest universal PICO c 발명도둑잡기갤로그로 이동합니다. 05.20 11 0
2698180 Try these 16 Brilliant ESP32 projects!!! 발명도둑잡기갤로그로 이동합니다. 05.20 13 0
2698179 8 Brilliant ESP32 Smartwatch Projects fo 발명도둑잡기갤로그로 이동합니다. 05.20 17 0
2698178 내일 북한군 전쟁 가능성 있습니다. [4] 도리스아(119.195) 05.20 88 0
2698176 애널의 수면영상⭐+ ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 70 0
2698175 sts4에서 깃허브 임포트 해오면 싹 다 오류 뜨는데 이거 머임 프갤러(122.38) 05.20 18 0
2698173 학살 조르디야 잘 지내냐? 너 조리돌림하던 AI대학원생이야 [29] 프갤러(115.138) 05.20 590 18
2698171 운덩해서 졸린건지.. 시간되서 졸린건지.. [2] ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 31 0
2698168 직구 금지 풀렸지만 알리 테무 이용 자제해야 [3] 헬마스터갤로그로 이동합니다. 05.20 50 0
2698165 프롬프트 엔지니어 자격증이나 따자 [1] 프갤러(183.101) 05.20 40 1
2698164 앱스토어 4시간 만에 승인 해줬네 ㅇㅅㅇㅋㅋㅋ AppHiki갤로그로 이동합니다. 05.20 34 0
2698163 아마존 3주 이용하고 나머지 35개월 유지비내고, [11] 도리스아(119.195) 05.20 42 0
2698162 아마존 ㅆㅂ새끼들 [3] 딱꾹(113.198) 05.20 105 6
2698161 4학년 1학기에 전자 복전해서, 5학년까지하고 임베디드 어떰 [9] ㅇㅇ갤로그로 이동합니다. 05.20 79 0
2698160 내다 왜 씨발두정거장이나 가야하냐고 [8] 멍청한유라ㅋ갤로그로 이동합니다. 05.20 48 0
2698159 아니 씨발 s3 라이브러리 하나 넣느라 aws 넣었는데 [2] 딱국(113.198) 05.20 111 5
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2