디시인사이드 갤러리

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

갤러리 본문 영역

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

몬발켜갤로그로 이동합니다. 2024.04.30 12:39:57
조회 74 추천 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
본문 보기

하단 갤러리 리스트 영역

왼쪽 컨텐츠 영역

갤러리 리스트 영역

갤러리 리스트
번호 제목 글쓴이 작성일 조회 추천
설문 이번주 설문은 탈모 걱정 없어 보이는 머리숱 금수저 스타는? 운영자 25/07/14 - -
AD 휴대폰 액세서리 SALE 운영자 25/07/15 - -
2871847 금연 3주 넘었는데, 어제부터 진짜 하루종일 라이터만 만지작 거리는중 ㅇㅇ(223.38) 07.15 29 0
2871846 파킹통장보다 CMA가 나은 이유 발명도둑잡기갤로그로 이동합니다. 07.15 30 0
2871845 exe 빌드가 안되서 납품 내일로 미룸 ㅆㅇㅆ(124.216) 07.15 30 0
2871844 프갤에서 레거시 싸개라는 건 독립 개발자 관점에서 [4] ㅆㅇㅆ(124.216) 07.15 164 4
2871843 요즘도 국비 -> 뻥튀기 si 코스 살아남아있냐? [1] 프갤러(118.235) 07.15 73 0
2871842 근데 신입이 레거시 될수밖에 없지 않냐 신입이 최신 문법 따라가긴 좀.. [4] ㅆㅇㅆ(124.216) 07.15 128 0
2871841 루비 그 사람 아님? [1] 프갤러(1.229) 07.15 56 2
2871840 [정조준194] 윤석열은 미국에 토사구팽당했다 발명도둑잡기갤로그로 이동합니다. 07.15 26 0
2871839 오늘 일 존1나 열심히 하는 중 [2] 아스카영원히사랑해갤로그로 이동합니다. 07.15 49 0
2871838 핸드폰 흠... [6] 개멍청한유라갤로그로 이동합니다. 07.15 31 0
2871837 레거시 정의 확립해줌 [6] 개멍청한유라갤로그로 이동합니다. 07.15 69 0
2871836 바쁘다 [8] 개멍청한유라갤로그로 이동합니다. 07.15 51 0
2871835 아까 유튜브 보는데 [2] 루도그담당(118.235) 07.15 34 0
2871834 내가 욕하는것들은 오히려 더 잘나가구나 재벌 넥사크로 자바 [1] 뒷통수한방(1.213) 07.15 39 0
2871832 나님 점심 마니 머거서 잠이 온다냥.. 졸리.. 졸리.. ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 24 0
2871831 냥덩이가 절대 과식하지 않는 진짜 이유⭐+ [6] ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 39 0
2871829 나님 방금 폰 바꿨다 [17] 루도그담당(211.184) 07.15 117 0
2871828 Nimf 설계 및 구현: 1. 리눅스, BSD에서의 입력 방식 개요 [3] 루비갤로그로 이동합니다. 07.15 36 0
2871826 한국 개발회사들의 웃기는 점은 시장에 신호를 보내는건 최신스택인데 [2] ㅆㅇㅆ(124.216) 07.15 59 0
2871825 채용지식보면 DDD,CQRS,이벤트 Sourcing 당연히 하는거같은데 [2] ㅆㅇㅆ(124.216) 07.15 51 0
2871824 저커버그 머스크 게이츠 좇센인이였으면 노예맞잖누 뒷통수한방(1.213) 07.15 18 1
2871822 네이티브 [3] 배구공(119.202) 07.15 36 0
2871821 신입채용 진짜의미 [2] ㅇㅇ(106.102) 07.15 67 1
2871819 C/C++ 특징. [1] 프갤러(59.16) 07.15 64 0
2871818 냐? ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 28 0
2871817 실베 4년제라는데 자바만 배운다는거 개소리 아님?? [5] ㅇㅇ(106.101) 07.15 62 0
2871816 미국 뇌신경과학자가 말하는 졸릴때 잠에서 깨는 방법 발명도둑잡기갤로그로 이동합니다. 07.15 23 0
2871815 주식 초보 수익 ㅇㅇ(211.246) 07.15 43 0
2871813 진짜 신입 모집 이래놓고 점점 필요역량 길어지는게 웃김 [4] ㅇㅇ(221.146) 07.15 98 0
2871811 챗티씨 왜케 빡대갈통이시냐? [4] 헬마스터갤로그로 이동합니다. 07.15 82 1
2871810 한국 ai 는 유료 API 가져다 쓰는 쪽으로 [4] 무한탐구(218.234) 07.15 83 0
2871809 인생은 아름답구 냥덩은 발전한당⭐+ By 나님 ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 29 0
2871808 국비출신 자바웹개발 배웠는데 현업가서 경험해보는게 꿈이다. [6] 프갤러(180.231) 07.15 173 0
2871807 디시앱에서 왜 글 가끔 안 써짐? [1] 아스카영원히사랑해갤로그로 이동합니다. 07.15 35 0
2871806 자랑 하나 해도 되냐 [4] 아스카영원히사랑해갤로그로 이동합니다. 07.15 81 0
2871803 자야지~ ㅇㅅㅇ 헤르 미온느갤로그로 이동합니다. 07.15 34 0
2871802 임베디드개발자하는일 -> 로봇어플리케이션, 공장자동화 [3] 네오커헠(1.237) 07.15 138 0
2871801 결국 2018년쯤 프갤러(로우레벨러)가맞았던거지 네오커헠(211.235) 07.15 98 0
2871800 QA 팀은 뭐하는 곳이야? [2] 프갤러(14.47) 07.15 78 0
2871799 포프님 예측들이 다 맞네 [9] 피치(183.101) 07.15 132 0
2871798 보더콜리가 border 콜리였노 [4] 헬마스터갤로그로 이동합니다. 07.15 58 0
2871797 짱깨 ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 23 0
2871796 우끽끼끼끼기기끼 우키 [1] 통암기클론원숭이(211.235) 07.15 52 0
2871795 ❤✨☀⭐⚡☘♥+나님 시작합니당♥+☘⚡⭐☀✨❤ ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 20 0
2871794 총체적문제 무능논란좌파 2찢명 쫄았넹 ㅇㅅㅇ ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 21 0
2871792 조급함이 일을 망치는법이당 By 나님 ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 34 0
2871791 [속보] 좌파 2찢명 갑질논란 인사대참사 거짓변명 강선우 텔레그램 확보! [1] ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 49 0
2871790 세부분석하니 1.5 클래스 올렸다는게 맞는듯? ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 28 0
2871789 반국가세력 모순 덩어리 아닌가 생각해본다 [2] 넥도리아(220.74) 07.15 36 0
2871788 다시 계산하니 2달 이상 앞당김 ㄷㅅㄷ ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 22 0
뉴스 '화려한 날들' 김희정, 살림의 신 ‘김다정’ 역으로 명품 라인업 합류! 따뜻한 존재감 예고! 디시트렌드 14:00
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2