디시인사이드 갤러리

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

갤러리 본문 영역

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

몬발켜갤로그로 이동합니다. 2024.04.30 12:39:57
조회 52 추천 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 - -
2698553 클리너 아이디이거 뭐로하면 됨? 프갤러(121.163) 05.21 22 0
2698552 나는 게시판만 만들꺼얌. 40대프린이(106.240) 05.21 45 0
2698551 에반게리온은 현대의 고전이 맞다 ㅇㅇ [9] 아스카영원히사랑해갤로그로 이동합니다. 05.21 64 0
2698549 개발 -> 전산직 정착함 [5] 프갤러(115.21) 05.21 115 0
2698548 나님 시작합니당❤+ ♥순수퓨어냥덩♥갤로그로 이동합니다. 05.21 21 0
2698547 컴공공부 어케하는건지좀 알려주이소 ㅠ [1] 프갤러(221.165) 05.21 45 0
2698546 나는내향적이다 ㅇㅇ(223.39) 05.21 21 0
2698545 이로써 모든 게임 계정들을 탈퇴하였다 ㅇㅅㅇㅋㅋ [4] ㅇㅇ(123.213) 05.21 44 0
2698544 밑에 비전공국비 글쓴 갤러인데 질문점 받아줘 [4] 프갤러(121.183) 05.21 96 0
2698543 에반게리오 노잼임 [6] 딱지(210.183) 05.21 44 0
2698542 디시 클리너 다운 받고 프갤러(121.163) 05.21 16 0
2698540 에바는 인문고전임 ㅇㅅㅇ [4] 프갤러(175.208) 05.21 37 0
2698539 c, c++ 그냥 사용못하게끔 막아버리면 좋겠노 ㅇㅇ(39.120) 05.21 36 0
2698538 c, c++ 미국에서 막아버릴수도 있다는데 ㅋㅋ [3] ㅇㅇ(39.120) 05.21 91 0
2698536 내 상황에서 뭘 해야될까 [4] 프갤러(218.155) 05.21 65 0
2698535 근데그럼에반개리온파란머리고른얘들은뭐임? [6] 보법E노무현갤로그로 이동합니다. 05.21 43 0
2698534 개발자 때려칠건데 뭐하는게좋을까 [4] 프갤러(211.114) 05.21 103 0
2698533 나는 편한 것만 찾아야겠다- 프갤러(121.172) 05.21 31 1
2698532 모니터를 담궈 버리는 기술력 프갤러(121.172) 05.21 36 1
2698530 할짓없어서 돼지저금통 까고 500원짜리만 골라냈어요 ㅇㅅㅇㅋㅋ [4] ㅇㅇ(123.213) 05.21 37 0
2698529 '개훌륭' 긴급 결방... 강형욱 '갑질' 논란에 입장 밝혀야 한다 발명도둑잡기갤로그로 이동합니다. 05.21 22 0
2698528 좌파대가리박살내고우파대가리터치고나면이제그다음은? 보법E노무현갤로그로 이동합니다. 05.21 25 0
2698527 진로를 고민이세요? RxStore를 이용하세요 [2] 프갤러(121.172) 05.21 38 1
2698526 GGOLIS ♥순수퓨어냥덩♥갤로그로 이동합니다. 05.21 27 0
2698525 질문 5개만 대답하면 스타벅스 드릴게요 프갤러(221.148) 05.21 34 0
2698524 걍이재명이되는게맞음단체로대가리깨부숴야함 보법E노무현갤로그로 이동합니다. 05.21 28 0
2698523 야 원래 랜덤스트링 인덱스 느리다 아니야? [2] 프갤러(1.230) 05.21 35 0
2698522 냥덩이의 소중한 친구가 되어 주세양❤+ [1] ♥순수퓨어냥덩♥갤로그로 이동합니다. 05.21 33 0
2698521 에바 한국 공식 홈피 스탭하면서 글 존나 쌌는데 [1] 딱지(210.183) 05.21 30 0
2698520 갠적으로 한동훈이 다시한번 나왔으면 하지만 헬마스터갤로그로 이동합니다. 05.21 29 0
2698519 요즘 ㄹㅇ 밥먹으면 바로졸림 [1] 헬마스터갤로그로 이동합니다. 05.21 33 0
2698517 어릴땐 몰랐는데 일본만화 젖주무르는신 항상 있더라 [7] 헬마스터갤로그로 이동합니다. 05.21 74 0
2698516 개발 형들 궁금한거,, 삼성노트 펜 기능 같은거는 어떤 개발종류임? [4] 프갤러(183.104) 05.21 86 0
2698515 피곤.. ♥순수퓨어냥덩♥갤로그로 이동합니다. 05.21 24 0
2698514 솔직히초딩때ㅍㅂㄱ시켜주던만화:다다다,그남그여,란마,환상게임 [4] 보법E노무현갤로그로 이동합니다. 05.21 41 0
2698513 그남자그여자의 사정 아냐? 그것도 잼남 [7] 딱지(210.183) 05.21 46 0
2698512 유튜브 노프리미엄 1일차 생각보다 견딜만함 [3] 헬마스터갤로그로 이동합니다. 05.21 45 0
2698511 동생 방임 [1] 도리스아처(223.39) 05.21 38 0
2698510 에반게리온은 프갤 개드립과 동일한 특징이 있음 [4] ㅇㅇ갤로그로 이동합니다. 05.21 51 0
2698508 하렘물 하면 딸기 100퍼지 먼 러브인러브 [5] 딱지(210.183) 05.21 36 0
2698507 솔직히 러브인러브가 에반게리온보다 재밋음 [3] 헬마스터갤로그로 이동합니다. 05.21 41 0
2698506 오타의 원인은 오버피팅인듯 [2] 헬마스터갤로그로 이동합니다. 05.21 28 0
2698505 에반게리온 리테이크 본 사람 있냐? [2] 프갤러(121.172) 05.21 26 1
2698504 아머야파란머리가주인공엄마클론이었어?근친만화임? [19] 보법E노무현갤로그로 이동합니다. 05.21 54 0
2698503 어릴땐 안그랬는데 이상한 오타를 침 [2] 헬마스터갤로그로 이동합니다. 05.21 23 0
2698502 비전공 국비듣는데 지금 잘하고있는건지 궁금하다 [51] 프갤러(121.183) 05.21 265 0
2698501 중소 si는 거의 그냥 붙여준다며 ... [3] 프갤러(112.150) 05.21 114 0
2698500 나는내향적이야 ㅇㅇ(223.39) 05.21 16 0
2698499 나 딱지 중2병 시절 한국 에바 공식 사이트 스탭이었다 [4] 딱지(210.183) 05.21 44 0
2698498 에반게리온은 왜 이쪽 업계 최고의 작품인가? [6] 아스카영원히사랑해갤로그로 이동합니다. 05.21 54 0
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2