디시인사이드 갤러리

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

갤러리 본문 영역

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

몬발켜갤로그로 이동합니다. 2024.04.30 12:39:57
조회 47 추천 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 - -
2698180 Try these 16 Brilliant ESP32 projects!!! 발명도둑잡기갤로그로 이동합니다. 05.20 10 0
2698179 8 Brilliant ESP32 Smartwatch Projects fo 발명도둑잡기갤로그로 이동합니다. 05.20 12 0
2698178 내일 북한군 전쟁 가능성 있습니다. [4] 도리스아(119.195) 05.20 76 0
2698177 회사 소스코드에 ddd 적용하고 싶다 포항의봄갤로그로 이동합니다. 05.20 25 0
2698176 애널의 수면영상⭐+ ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 46 0
2698175 sts4에서 깃허브 임포트 해오면 싹 다 오류 뜨는데 이거 머임 프갤러(122.38) 05.20 15 0
2698173 학살 조르디야 잘 지내냐? 너 조리돌림하던 AI대학원생이야 [27] 프갤러(115.138) 05.20 370 15
2698172 생식기가 최고임 ㅇㅅㅇ 류류(121.160) 05.20 35 1
2698171 운덩해서 졸린건지.. 시간되서 졸린건지.. [2] ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 26 0
2698168 직구 금지 풀렸지만 알리 테무 이용 자제해야 [3] 헬마스터갤로그로 이동합니다. 05.20 45 0
2698167 성욕 때문에 미치겠음 ㅇㅅㅇ 류류(121.160) 05.20 44 1
2698165 프롬프트 엔지니어 자격증이나 따자 [1] 프갤러(183.101) 05.20 38 1
2698164 앱스토어 4시간 만에 승인 해줬네 ㅇㅅㅇㅋㅋㅋ AppHiki갤로그로 이동합니다. 05.20 31 0
2698163 아마존 3주 이용하고 나머지 35개월 유지비내고, [11] 도리스아(119.195) 05.20 37 0
2698162 아마존 ㅆㅂ새끼들 [3] 딱꾹(113.198) 05.20 35 1
2698161 4학년 1학기에 전자 복전해서, 5학년까지하고 임베디드 어떰 [9] ㅇㅇ갤로그로 이동합니다. 05.20 61 0
2698160 내다 왜 씨발두정거장이나 가야하냐고 [8] 멍청한유라ㅋ갤로그로 이동합니다. 05.20 44 0
2698159 아니 씨발 s3 라이브러리 하나 넣느라 aws 넣었는데 [2] 딱국(113.198) 05.20 43 1
2698158 12살 여자아이 잠지안에 내 정자를 넣고 ㅇㅅㅇ [1] 류류(121.160) 05.20 30 1
2698157 어린 여자아이 임신시키고 내 자식 만들고 ㅇㅅㅇ [2] 류류(121.160) 05.20 30 1
2698156 강조되고 반복되는 슬토킹은 냥덩이를 불안하게 해요! [3] ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 28 0
2698155 이번 규제 이거아님? [8] 멍청한유라ㅋ갤로그로 이동합니다. 05.20 58 0
2698154 UI기획은 정말 어려워요 [1] 헬마스터갤로그로 이동합니다. 05.20 32 1
2698153 요새 ddd 보고있는데 [2] 키비갤로그로 이동합니다. 05.20 34 0
2698152 그랴서 직구 막는다는 거야 철회한다는 거야 [2] 아스카영원히사랑해갤로그로 이동합니다. 05.20 30 0
2698151 멍유치타는 언제쯤 한남에게 승리할까 [4] 헬마스터갤로그로 이동합니다. 05.20 32 0
2698150 중국여행 갈건데 가방 어떤거 들고가야대 [11] 이시꾸갤로그로 이동합니다. 05.20 42 0
2698148 깃허브 프사가 이런 개발자 특징은 뭐임? [3] 딘퐁갤로그로 이동합니다. 05.20 90 0
2698147 아마존 이 씹 새끼들... [1] 딱국(113.198) 05.20 24 0
2698146 여자 개발자 신입 면접 특징 [4] ㅇㅇ(223.38) 05.20 126 2
2698145 존나퇴근 [4] 이시꾸갤로그로 이동합니다. 05.20 26 0
2698144 50대의 외로운 회식자리 멍청한유라ㅋ갤로그로 이동합니다. 05.20 30 0
2698143 애널 운덩했더니 벌써 10시넹.. [4] ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 29 0
2698142 앞으로 취업 시장 더 좋아질 가능성 없음?ㅠㅠ [1] 딘퐁갤로그로 이동합니다. 05.20 60 0
2698141 저만 모르고 있었음 근데 우선순위가 내일 여행이라서 방정리인데, [6] 도리스아(119.195) 05.20 26 0
2698140 여자들 ㅈㄴ 꼬신다음 성욕자극시키고싶다 [3] 딱꾹(113.198) 05.20 32 0
2698138 앱웹개발 아웃소싱맡길려고 하는데 해본적 있는사람? 프갤러(118.33) 05.20 29 0
2698137 의사새끼들 너무 폐쇄 선민주의 심하긴함 [1] ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 19 0
2698136 홍정모 왜 주마다 라이브 안함?????????? [3] ㅁㅁ(110.14) 05.20 32 0
2698134 서울 공기업 사무직 vs 개발자 [4] ㅇㅇ(14.38) 05.20 62 0
2698132 나님 막담핍니당❤+ ♥꽁꽁얼어버린냇물위에냥덩♥갤로그로 이동합니다. 05.20 17 0
2698131 한국 표현의 자유 억압 만화 출처가 어디죠? 발명도둑잡기갤로그로 이동합니다. 05.20 11 0
2698130 아씨발한남진짜 [3] 멍청한유라ㅋ갤로그로 이동합니다. 05.20 54 0
2698129 40 대 노땅 틀딱 취업했다. [1] 프갤러(59.16) 05.20 47 0
2698128 서버 껏다 키니까 갑자기 코드 잘 돌아가는 이유가 뭐냐? [6] ㅇㅇ갤로그로 이동합니다. 05.20 57 0
2698127 ios 앱 만들려고 아이폰 SE3 중고로 샀다. 캐프붕갤로그로 이동합니다. 05.20 35 0
2698126 유료 구독자 모으려면 안드 vs ios 중 어디가 좋을까 캐프붕갤로그로 이동합니다. 05.20 34 0
2698125 앱이 성공하려면 프갤러(118.235) 05.20 21 0
2698124 고작 알람시계 어플로 5000만 다운로드 뭐임? [6] 딘퐁갤로그로 이동합니다. 05.20 75 0
2698123 bootice BIOS 내일여행가는데, 나 새로운거 찾았는데, 도리스아(119.195) 05.20 12 0
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2