디시인사이드 갤러리

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

갤러리 본문 영역

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

몬발켜갤로그로 이동합니다. 2024.04.30 12:39:57
조회 76 추천 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/21 - -
2874549 2/1 ㅌㅊ 시도 해봐야징 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 28 0
2874547 흠.. 2.3ㅌㅊ.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 19 0
2874545 성북길빛도서관에서 한 시간 쉬다 간다 발명도둑잡기(39.7) 07.25 27 0
2874542 알리익스프레스 정지된거 너무 불편하다 [2] 발명도둑잡기(211.234) 07.25 39 0
2874541 와 나님 갑자기 폭똥 터져서 큰일날뻔;; ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 28 0
2874540 ... 프갤러(118.37) 07.25 35 0
2874539 스프링 드라마 발명도둑잡기(211.234) 07.25 37 0
2874538 양산형 도배기 이거써라 ㅋㅋㅋ 프갤러(118.37) 07.25 31 0
2874536 "프로그래밍은 엉덩이로 한다" 발명도둑잡기(211.234) 07.25 30 0
2874535 도배기나역류기가 막혔다는 병신은 엠창새끼임? [3] 프갤러(118.37) 07.25 60 0
2874534 나는조현병이야 나는내향적이야 손발이시립디다갤로그로 이동합니다. 07.25 29 0
2874533 윤석열 계엄 손해배상 인정…‘1만명 위자료 소송’ 이어진다 발명도둑잡기(211.234) 07.25 37 0
2874532 공수처 검찰청 경찰청 국제수사 과학수사 포랜식수사 기무사 국정원 존재이유 뒷통수한방(1.213) 07.25 19 0
2874531 외교참사 국제왕따 2찢명 회생방안 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 23 0
2874530 106.101 심리공작원 글 발명도둑잡기(118.235) 07.25 24 1
2874528 박보영 엘르 화보 발명도둑잡기(118.235) 07.25 43 0
2874526 이바닥이 이렇게까지 폐쇄적인줄 몰랐음 프갤러(118.235) 07.25 40 0
2874525 성냥사세양..성냥사세양.. 성냥말구 다른것두 팔아양.. ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 28 0
2874524 뉴프로 개새끼야!!!!!!!!!!!!!!!!!!!! [1] 아스카영원히사랑해갤로그로 이동합니다. 07.25 55 0
2874523 흑녀 생머리 존꼴 ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 47 0
2874522 개젗센 연도별 200충개돼지노예 신입 요구사항 뒷통수한방(1.213) 07.25 31 0
2874521 영화 <터미네이터> 시리즈가 뻥인 이유 [1] 발명도둑잡기(182.222) 07.25 33 0
2874520 프로그래밍 같은 지식에만 심취하면 [11] 아스카영원히사랑해갤로그로 이동합니다. 07.25 110 0
2874519 개발자는 영어잘하면 해외취업 쉬워? [1] 프갤러(118.235) 07.25 40 0
2874518 요즘 왜 다 런닝화같은 비새는 신발뿐? 헬마스터갤로그로 이동합니다. 07.25 33 0
2874517 핵전쟁 나면 우선 it산업은 개박살이고 뭘 준비해할까? ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 25 0
2874516 첫 출근 뭐해야하노 [1] 세상아덤벼라갤로그로 이동합니다. 07.25 38 0
2874515 원종이가 꿈꾸던 스카이넷 가동 도입중.. [2] ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 48 0
2874514 ai 가 인류를 위협한다는 시나리오 봐라 프갤러(183.101) 07.25 27 0
2874513 핵전쟁 3차세계대전 임박(미-러 핵무기제한 협정 내년 2월 종료) ♥팬티스타킹냥덩♥갤로그로 이동합니다. 07.25 54 0
2874512 종교가 없어져도 종교 대체물은 나오기 마련임 [3] 아스카영원히사랑해갤로그로 이동합니다. 07.25 76 0
2874511 어떤 프로그래밍 방법도, 프로그래밍 모델도 반드시 새는 부분이 존재함 ㅆㅇㅆ(124.216) 07.25 35 0
2874509 내가 좋아하는 프로그래밍 명언 중에 이런 말이 있다. [1] ㅆㅇㅆ(124.216) 07.25 62 0
2874508 종교 = 사람이 어떻게 살아야하는가 부드러운곰탱이갤로그로 이동합니다. 07.25 36 1
2874507 코딩을 좋아하긴 했지 [4] 프갤러(106.101) 07.25 91 2
2874506 븅신들아 프갤러(211.36) 07.25 39 0
2874505 스위치 라이트 샀는데 이어서 진행하기로 했는데 세이즈데이터 없다. 넥도리아(220.74) 07.25 24 0
2874504 종교라는 모델도 리팩토링이 필요하다고 보임. ㅆㅇㅆ(124.216) 07.25 30 2
2874503 프론트 백엔드 요약 ㅇㅇ(118.235) 07.25 42 0
2874502 종교를 가진다는 것은 복잡성에서 눈을 돌린다는 것. [1] ㅆㅇㅆ(124.216) 07.25 37 0
2874501 난 솔직하게 정신병 있음 프갤러(106.101) 07.25 61 0
2874500 공수처 검찰청 경찰청 국제수사 과학수사 포랜식수사 기무사 국정원 존재이유 뒷통수한방(1.213) 07.25 17 0
2874499 대체 왜 종교라는 정신병에서 헤어나올질 못할까 [8] ㅆㅇㅆ찡갤로그로 이동합니다. 07.25 68 0
2874498 유니티 메카님 솔직히 좆구림 [3] 루도그담당(58.239) 07.25 52 0
2874496 코딩 공부할려고 책 미리 알아둠 프갤러(106.102) 07.25 40 0
2874494 유니티로 모션 싱크 같은거 맞춰보는데 [1] 루도그담당(58.239) 07.25 29 0
2874493 난 채수빈이랑 결혼 안 한다 발명도둑잡기(118.235) 07.25 53 0
2874492 일반적으로 프론트가 클린코드랑 아키텍팅이 더 쉬움 ㅆㅇㅆ찡갤로그로 이동합니다. 07.25 46 0
2874489 자 오늘 일 끝이다 [3] 아스카영원히사랑해갤로그로 이동합니다. 07.25 39 0
2874488 프로그래머의 가장 큰 미덕은 끈기라고 생각함 [3] ㅆㅇㅆ찡갤로그로 이동합니다. 07.25 63 0
뉴스 십센치, 7년 11개월 만에 정규 5집으로 돌아온다…선공개 싱글 ‘춤’으로 컴백 예열 디시트렌드 07.25
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2