Tesseract-OCR 문자 인식 패키지를 사용해 이미지의 문자를 텍스트 문자로 변환 해주는 프로그램 test.py파일을 작성했습니다. 그리고, python test.py로 문제 없이 잘 실행 되는것도 확인했었습니다. 그런데 pyinstaller –onefile –icon=test.ico –clean –windowed test.py 로 Windows 실행 파일 test.exe를 만들어 실행했더니 다음과 같은 tesseract.image_to_string를 부르는 곳에서 에러가 발생했습니다. 이 글에서는 다음과 같은 오류에 접했을 때 해결 하는 방법을 소개합니다.
Traceback (most recent call last):
File "test.py", line 204, in testocr
File "pyocr\tesseract.py", line 364, in image_to_string
File "pyocr\tesseract.py", line 293, in run_tesseract
File "subprocess.py", line 829, in __init__
File "subprocess.py", line 1252, in _get_handles
OSError: [WinError 6] The handle is invalid
전제 조건
이 글은 다음 내용들이 적용 되어있는 환경에서 실행되었습니다.
- python 설치 프로그램 다운로드
- 최신 버전 python3.9 64bit 설치
- python 설치 결과 확인을 위해 실행해 보기
- pip 버전 업그레이드
- Python 추가 패키지 ‘pyautogui‘ 설치
- Python추가 패키지 ‘PIL‘ 설치
- Python 추가 패키지 ‘numpy‘ 설치
- Python 추가 패키지 ‘cv2‘ 설치
- Python의 py파일을 exe실행파일로 변화해주는Python 추가 패키지 ‘pyinstaller‘ 설치
pyocr\tesseract.py 문제의 코드
File “pyocr\tesseract.py”, line 293, in run_tesseract 의 에러 추적 정보를 가지고 다음과 같이 소스를 확인해 보았더니 stdin에 대해서는 인수를 설정하지 않고 있습니다.
293: proc = subprocess.Popen(command, cwd=cwd,
294: startupinfo=g_subprocess_startup_info,
295: creationflags=g_creation_flags,
296: stdout=subprocess.PIPE,
297: stderr=subprocess.STDOUT)
에러 장소 소스 확인
subprocess.Popen를 실행할 때 stdin에 대해서는 인수를 설정하지 않은 결과 에러 메시지 File “subprocess.py”, line 829, in __init__ 함수의 756:행에서 인수 stdin=None 의 기본 값은 바뀌지 않았으며 그리고 1251:행에서 if stdin is None 조건에 일치하여 에러 메시지 File “subprocess.py”, line 1252, in _get_handles 의 winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE)에서 에러가 발생했음을 알 수 있습니다.
console용으로 실행할 때는 _winapi.STD_INPUT_HANDLE 을 취득할 수 있지만 –noconsole 옵션으로 컴파일한 Windows용 exe실행 파일은 _winapi.STD_INPUT_HANDLE을 취득하지 못하는 것 같습니다.
만약 1260:행의 elif stdin == DEVNULL 로 조건 분기하도록 수정할 수 있다면 오류를 해결할 수 있을 것 같습니다.
756: def __init__(self, args, bufsize=-1, executable=None,
756: stdin=None, stdout=None, stderr=None,
756: preexec_fn=None, close_fds=True,
756: shell=False, cwd=None, env=None, universal_newlines=None,
756: startupinfo=None, creationflags=0,
756: restore_signals=True, start_new_session=False,
756: pass_fds=(), *, user=None, group=None, extra_groups=None,
756: encoding=None, errors=None, text=None, umask=-1):
.... 중간생략
827: (p2cread, p2cwrite,
828: c2pread, c2pwrite,
829: errread, errwrite) = self._get_handles(stdin, stdout, stderr)
.... 중간생략
1251: def _get_handles(self, stdin, stdout, stderr):
.... 중간생략
1251: if stdin is None:
1252: p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE)
.... 중간생략
1257: elif stdin == PIPE:
1258: p2cread, p2cwrite = _winapi.CreatePipe(None, 0)
1259: p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite)
1260: elif stdin == DEVNULL:
1261: p2cread = msvcrt.get_osfhandle(self._get_devnull())
1262: elif isinstance(stdin, int):
1263: p2cread = msvcrt.get_osfhandle(stdin)
1264: else:
1265: # Assuming file-like object
1266: p2cread = msvcrt.get_osfhandle(stdin.fileno())
pyocr\tesseract.py 소스 수정
다음과 같이 인수를 추가하여 stdin=subprocess.DEVNULL를 건네주도록 수정합니다. subprocess.py 소스에서 __init__함수의 stdin의 기본 설정 None이 subprocess.DEVNULL로 변경되어 1260:행 elif stdin == DEVNULL:로 조건 분기됩니다.
293: proc = subprocess.Popen(command, cwd=cwd,
294: startupinfo=g_subprocess_startup_info,
295: creationflags=g_creation_flags,
296: stdout=subprocess.PIPE,
297: stderr=subprocess.STDOUT,
298: stdin=subprocess.DEVNULL)
재컴파일 후 Windows 실행 파일 test.exe를 다시 실행 하면 오류가 발생하지 않는 것을 확인할 수 있습니다.
위의 내용으로 추측하건대 Tesseract-OCR의 작성자들은 pyinstaller를 이용한 –noconsole 옵션으로 Windows GUI exe파일을 작성해서 winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE)에서 문제가 발생했던 경우가 지금까지 보고 되지 않아서 수정을 못하지 않았나 싶습니다.