디시인사이드 갤러리

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

갤러리 본문 영역

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

몬발켜갤로그로 이동합니다. 2024.04.30 12:39:57
조회 73 추천 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 - -
공지 프로그래밍 갤러리 이용 안내 [88] 운영자 20.09.28 45505 65
2872015 내가 10초 에서 9초 사이에 뛰는 이유 넥도리아(175.196) 06:45 6 0
2872013 ai 로 인해 웹사이트는 이제 돈이 안될거야. ㅇㅇ(183.101) 06:34 7 0
2872009 ❤✨☀⭐⚡☘♥+나님 시작합니당♥+☘⚡⭐☀✨❤ ♥냥덩이의고독♥갤로그로 이동합니다. 05:59 9 0
2872007 간단한 웹사이트 만들라는데 질문좀 [1] 프갤러(1.238) 05:48 14 0
2872005 개발자 연봉 거품맞음 프갤러(183.101) 03:47 29 0
2872003 셰프는 개발자고 장사꾼은 사업가다 프갤러(183.101) 03:44 16 0
2872001 개빌자 취준 브이로그 보는데 asdqwezxc갤로그로 이동합니다. 02:47 40 0
2871999 요즘 개발자 거의 아예 안 뽑는다던데 [2] hrin(220.120) 02:40 55 0
2871998 헬스하고 술먹고 과로하니까 몸살 올 것 같다 [3] hrin(220.120) 02:38 27 0
2871996 자동차 개발자가 되어줘 [1] CANON갤로그로 이동합니다. 02:02 32 0
2871994 어느 면접관 썰.. [1] cvs.갤로그로 이동합니다. 01:12 67 0
2871992 리눅스 데스크탑이 왜 처망했는지 이제야 알겠네 ㅇㅇ(220.76) 01:05 38 0
2871990 Ada 프로그래밍: 1. 다중 통신 및 비동기 제어: select 문 루비갤로그로 이동합니다. 00:39 25 0
2871988 디시콘 2개 쑤니깐 개웃기지않냐? [3] 헬마스터갤로그로 이동합니다. 00:13 40 0
2871987 곤충중에 섹스하다 한녀에게 먹히는 한남들이 많구나 [3] 헬마스터갤로그로 이동합니다. 00:11 46 0
2871985 그래도 한국에서 태어나서 다행임 아스카영원히사랑해갤로그로 이동합니다. 00:03 46 0
2871984 ㅋㅋ 드디어 실제 구현에 Ada 코드 나올 차례이다 [3] 루비갤로그로 이동합니다. 00:02 35 0
2871983 Nimf 구현 및 설계: 2.1 아키텍처의 선택: 서버 모델의 설계 우위 루비갤로그로 이동합니다. 07.15 31 0
2871982 우테코 다들 공부하고 감? 프갤러(121.131) 07.15 23 0
2871981 Nimf 구현 및 설계: 2. 다국어 IME의 핵심 아키텍처 루비갤로그로 이동합니다. 07.15 21 0
2871980 현업에서 C++만 쓰다가 AI 때문에 파이썬 공부 중인데 [1] 프갤러(211.202) 07.15 47 0
2871979 Nimf 구현 및 설계: 1.5 콘솔 (console) 루비갤로그로 이동합니다. 07.15 20 0
2871977 Nimf 구현 및 설계: 1.4 웨이랜드 (Wayland) 루비갤로그로 이동합니다. 07.15 24 0
2871976 Nimf 설계 및 구현: 1.3 Qt IM 모듈 루비갤로그로 이동합니다. 07.15 15 0
2871974 Nimf 설계 및 구현: 1.2 GTK IM 모듈 루비갤로그로 이동합니다. 07.15 25 0
2871973 부트캠프 추천 부탁드립니다 프갤러(58.237) 07.15 79 1
2871971 내친구 피에로 보시게 카드캡터체리갤로그로 이동합니다. 07.15 29 0
2871969 컴과 1학년인데 앞으로 뭐 할지 모르겠음... [1] ㅇㅇ갤로그로 이동합니다. 07.15 72 0
2871967 프갤이 쓰레기장이니까 [1] 아스카영원히사랑해갤로그로 이동합니다. 07.15 49 0
2871965 신입쩌리 오늘 한 일. [2] cvs.갤로그로 이동합니다. 07.15 44 0
2871964 프로그래밍과정 개발자 취업되나요? [7] 프갤러(218.234) 07.15 75 0
2871962 챗티씨는 이재명씨발 민생지원금 동의 안하시더라 [6] 헬마스터갤로그로 이동합니다. 07.15 79 0
2871960 전국민 잘살기를하면 [4] 개멍청한유라갤로그로 이동합니다. 07.15 41 0
2871959 냥덩아 너 혼나야겠다 개멍청한유라갤로그로 이동합니다. 07.15 32 0
2871958 잘 가르치는 강사면 강사도 좋지 책이 무조건 좋은건 아님 [1] ㅆㅇㅆ(124.216) 07.15 51 0
2871957 개발자연봉 1400억?! [5] 개멍청한유라갤로그로 이동합니다. 07.15 70 0
2871956 후우.. 나님은 특별하지 않다는걸 깨달아 버렸당.. [2] ♥냥덩이의고독♥갤로그로 이동합니다. 07.15 45 0
2871955 윤석열이 망친 경제, 이재명 정부가 정상화 한다 [1] 야옹아저씨갤로그로 이동합니다. 07.15 41 0
2871954 책 vs 강의 뭐가 더 좋을까요? [9] 궁금해요(121.171) 07.15 59 0
2871953 대기업 중견기업 다니는 애들아 질문있다 [34] ㅇㅇ갤로그로 이동합니다. 07.15 85 0
2871952 데이터 보는 대시보드 툴 뭘 써도 만족이 안되네 [5] 뉴진파갤로그로 이동합니다. 07.15 31 0
2871951 나님 주무십니당⭐+ ♥냥덩이의고독♥갤로그로 이동합니다. 07.15 17 0
2871949 쪽바리 it가 병신인게 루비같은걸 좋다고 써재끼고 있음 [3] 프갤러(110.8) 07.15 52 0
2871948 중급, 고급, 특급개발자가 도대체 무슨 뜻임? [7] 프갤러(39.7) 07.15 65 0
2871947 문재앙 시대의 피해자 원종이에게 바치는 자장가⭐+ [1] ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 45 0
2871946 잘나간다는 기사에는 쌍욕뿐이고 [7] 헬마스터갤로그로 이동합니다. 07.15 50 0
2871945 나님 누엇어영⭐+ [2] ♥지나가던길냥덩♥갤로그로 이동합니다. 07.15 32 0
2871944 삼성 sw는 정년몇살까지냐 [6] 프갤러(175.214) 07.15 87 0
2871943 이 갤에 신입이 있는게 신기하다 [2] 박민준갤로그로 이동합니다. 07.15 59 0
뉴스 "무좀 양말인줄"…14만원짜리 품절대란 '제니 신발' 뭐길래 디시트렌드 07.14
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2