디시인사이드 갤러리

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

갤러리 본문 영역

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

몬발켜갤로그로 이동합니다. 2024.04.30 12:39:57
조회 78 추천 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/09/22 - -
AD 가전디지털, 액세서리 SALE 운영자 25/09/12 - -
2890593 내가 원하는게 뭘까 생각해보면 돈이 아닌거같음... ㅇㅇ(223.38) 09.20 22 0
2890591 밤새서 제정신이 아니다 [1] 발명도둑잡기(118.216) 09.20 47 0
2890590 그나마 내가 경쟁력있는게 한투 자동매매 프로그램에 AI붙인거임 ㅆㅇㅆ(124.216) 09.20 57 0
2890589 특검, 尹 ‘무인기 의혹’ 소환통보…정청래 “외환죄 더 무거워” 발명도둑잡기(118.216) 09.20 38 0
2890588 자동매매 프로그램 홍보하려면 금융 자문업 해야하는데 나는 그게 없음 ㅆㅇㅆ(124.216) 09.20 44 0
2890587 한투 자동매매 프로그램팔이.. 그게 나 ㅆㅇㅆ정체성 [10] ㅆㅇㅆ찡갤로그로 이동합니다. 09.20 102 0
2890586 근데 세상 좋아졌다. 옛날에는 api 다 외워야했는데 [5] ㅆㅇㅆ찡갤로그로 이동합니다. 09.20 74 0
2890585 우리 서로 사랑해요 [8] 개멍청한유라갤로그로 이동합니다. 09.20 63 0
2890584 고아새끼 [4] 개멍청한유라갤로그로 이동합니다. 09.20 61 0
2890583 현역 코드 보는데 현역도 데이터 증분 안쓰고 전체를 받더라 [6] ㅆㅇㅆ(124.216) 09.20 62 0
2890581 냥덩이 미국 못 가네 [1] 발명도둑잡기(118.216) 09.20 36 0
2890580 오늘이 남은 인생중 가장 젊은나이라 믿고 공부에 도전해보고 싶습니다... ㅇㅇ(223.38) 09.20 61 0
2890579 이제 똑똑이들 미국취업 못한댄다 [5] 헬마스터갤로그로 이동합니다. 09.20 165 0
2890574 나님 왤케 특별하실깡? [8] ♥냥덩이♥갤로그로 이동합니다. 09.20 63 0
2890572 오늘 현역 프론트에게 엑스칼리드로우강의함 [7] ㅆㅇㅆ찡갤로그로 이동합니다. 09.20 85 0
2890564 와 씨발 VB6 회사명 기본값 EXE에 하드코딩 해놨노 ㅋㅋㅋㅋㅋㅋㅋ ㅇㅇ(125.134) 09.20 48 0
2890561 편의점에서 64-- 원 양배추 1800원 1/4 파는데 넥도리아(220.74) 09.20 33 0
2890560 앱히키 웹히키 씨발년아 나대지마라 프갤러(211.246) 09.20 39 0
2890558 혹시 gstreamer 써본 사람? [2] 프갤러(27.163) 09.20 54 0
2890556 [대한민국] 나무위키에 대해서 프갤러(121.172) 09.20 33 0
2890554 깨달았다. 렉카도 페라리 개조해서 레카하면 가오 안상함 프갤러(211.234) 09.20 34 0
2890553 길가는데 병신 렉카 포터 개조해서 렉카차 만들었네 ㅋㅋㅋㅋ 프갤러(211.234) 09.20 40 0
2890552 파이썬 쉽고 마스터 하는 방법. [1] 프갤러(59.16) 09.20 51 0
2890551 게임핵 근절방법 알았다. [2] 프갤러(211.234) 09.20 58 0
2890550 님들아 파이썬 배우려면 뭐부터해야대나여 [8] ㄴㅇㄹ(112.163) 09.20 59 0
2890549 팔레스타인 = 테러리스트 [1] ♥냥덩이♥갤로그로 이동합니다. 09.20 52 0
2890548 부동산후분양은 금융자본주의 미발달 때문 [1] ♥냥덩이♥갤로그로 이동합니다. 09.20 48 0
2890547 Pd가 만들면 nl이 차지함 [2] ♥냥덩이♥갤로그로 이동합니다. 09.20 53 0
2890546 요즘 게임들 머리좋네 [5] 루도그담당(58.239) 09.20 86 0
2890544 밀우야 안된다..한투 자동매매만은 하지마라 ㅆㅇㅆ찡갤로그로 이동합니다. 09.20 70 0
2890543 바짓가랑이가 빵구났어요. [6] 볼드오이(121.151) 09.20 60 0
2890542 메세지 방식이 파싱이 없어서 더 빨라서 [4] ㅆㅇㅆ찡갤로그로 이동합니다. 09.20 69 0
2890541 씨발 저능아들 전부다 닥치고 내 말대로 해! [5] 프갤러(110.8) 09.20 73 1
2890540 한국투자증권 api 양식이 전문 방식이네 ㅋㅋ [13] 밀우갤로그로 이동합니다. 09.20 97 0
2890539 화교의 속마음 [1] 프갤러(211.210) 09.20 64 0
2890538 히키 씨발년아 글 보고 있는 거 안다 [4] 프갤러(211.246) 09.20 50 0
2890533 [대한민국] 자유민주주의 - 개념 재설명 프갤러(121.172) 09.20 26 0
2890531 극좌무능 이재명 가짜정부 수준 ㅁㅌㅊ? ♥냥덩이♥갤로그로 이동합니다. 09.20 46 0
2890530 어른이 되어서 수능문제보면 개쉬워보인다는게 이런 느낌인가 [3] 공기역학갤로그로 이동합니다. 09.20 73 0
2890529 여러분 [1] 공기역학갤로그로 이동합니다. 09.20 61 0
2890528 씨발 히키년 글 다 지웠네 ㅋㅋㅋㅋㅋ ㅇㅇ(222.108) 09.20 49 3
2890527 게시판 쇼핑몰말고 신박한거없누 뒷통수한방(1.213) 09.20 31 0
2890526 121.139 점마 211.235 아이피 다중이로 [1] ㅆㅇㅆ찡갤로그로 이동합니다. 09.20 73 0
2890524 어제 Ada 예외 처리 원고 작성하느냐 4시간 [2] 나르시갤로그로 이동합니다. 09.20 41 0
2890523 못배워쳐먹은 어른을 [3] 프갤러(49.165) 09.20 92 0
2890522 ㅆㅇㅆ 멘탈 나갈때 행동패턴이 뻔함 ㅋㅋㅋㅋ [2] ㅇㅇ(211.234) 09.20 82 5
2890521 이번에 외주받은게 친구일이라 밤 얻어먹는중 [3] ㅆㅇㅆ찡갤로그로 이동합니다. 09.20 57 0
2890520 조나단 느그홍 나중에 나라 경기 호황이면 그때 장관하자 [1] 뒷통수한방(1.213) 09.20 27 0
2890519 프롬프트허브? 프롬프트마켓? 만들어볼건데 꼬치의달인갤로그로 이동합니다. 09.20 46 0
2890518 ㅈㅇ세번함 [2] 꼬치의달인갤로그로 이동합니다. 09.20 62 0
뉴스 AM8IC(엠빅), 루(ROUX) 창작 안무 댄스 영상 공개…차세대 퍼포먼스 괴물 탄생 예고 디시트렌드 10:00
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2