|
|
클리앙)testa1234
1. 저는 소설 독서를 좋아합니다. 그런데 책을 소유하는 것은 좋지만, 보관이나 이동은 좀 부담스럽습니다. 그래서 이 책을 사진을 찍어서 보관하면 좋겠다는 생각을 하게 되었습니다. 하드디스크 하나에 수백 권~수천 권의 책을 넣어서 다니면서 언제 어디서나 읽을 수 있게 되겠죠.
2. 처음에는 평판 스캐너를 사용해서 한 장 한 장 스캔을 하려고 했습니다. 그런데 평판 스캐너의 경우에는 1페이지를 스캔하는 데에 30초 이상의 시간이 걸렸습니다. 스캐너의 뚜껑을 열었다 닫았다 하는 과정도 매우 귀찮았습니다. 그래서 자동으로 할 수 있는 스캐너가 없나 검색해 봤는데요, 양면 스캐너라는 제품이 있었습니다. 가격은 최저 30만원대에서 100만원대까지 있었습니다. 자동급지 방식으로 스캔도 되고, 스캔 속도도 훨씬 더 빨랐습니다. 그래서 중고로 양면 스캐너를 사서 스캔을 해 보았습니다. 책 절단은 인쇄/프린터가 되는 점포에 가서 거대한 절단기로 한꺼번에 자르는 방법을 썼습니다. 양면 스캐너는 자동급지가 되었지만 중간중간에 걸리는 잼 현상이 발생했습니다. 또 흑백으로 스캔할 때는 동작이 빨랐지만, 컬러로 스캔할 때는 동작이 느렸습니다. 스캔된 사진의 품질도 200DPI는 동작이 빨랐지만, 300 DPI는 동작이 느렸습니다. 또 스캔된 사진에는 크고 작은 점들이 생겼습니다. 먼지 같은 것들이 이런 점들을 만드는 것 같습니다. 저는 양면 스캐너로 스캔하는 것을 포기하게 되었습니다. 다른 분들은 양면 스캐너를 잘 사용하시던데, 저는 못하겠더군요.
3. 스마트폰 앱 중에는 북스캔을 하는 앱들이 많이 있습니다. 유료 앱도 있고, 무료 앱도 있습니다. 가장 많이 애용되는 앱으로는 vflat이라는 앱이 있습니다. 저는 여러 앱을 사용해 보았는데, 장점이 여럿 있었지만, 몇 가지 마음에 안 드는 점이 있었습니다. 그래서 앱을 사용하는 것을 포기하였습니다. 그 뒤에도 몇 가지 시도가 더 있었습니다만, 생략하겠습니다.
4. 저는 스마트폰으로 책 사진을 찍고, 여기에서 책 부분만 잘라내어 파일로 저장하는 방식을 선택하게 되었습니다. 스마트폰 사진의 해상도는 1200만 화소 정도인데, 이렇게 찍으면 사진 파일의 크기는 3~4MB 정도가 됩니다. 예를 들어서 300 페이지 짜리 책을 찍으면 1GB 용량이 됩니다. 이 파일 용량은 좀 부담스럽습니다. 그래서 파일 크기를 줄이기 위해서는 책 부분만 잘라낼 필요가 생깁니다. 또 책이 비뚤게 찍혔을 때가 많은데, 그 때마다 태블릿을 왼쪽이나 오른쪽으로 기울게 하는 것은 매우 귀찮습니다. 그래서 책 부분의 수직과 수평이 딱 맞도록 잘라낼 필요가 생깁니다. 이렇게 300장의 사진 파일을 순차적으로 자동으로 잘라내어 저장하도록 프로그램을 만들어야 합니다.
5. 책 사진을 찍을 때는 각도는 2가지 방식이 있습니다.
첫째는 책을 아무 각도로나 찍는 방식입니다. 이렇게 찍으면 사진이 직사각형이 아닌 사다리꼴이나 마름모처럼 찍히게 됩니다. 북스캔 프로그램은 4개의 꼭지점을 찾아서 마주 보는 변의 길이가 같아지도록 변형을 해 줍니다.(수직과 수평은 자동으로 이뤄집니다.) 명함 같이 4개 변의 길이가 같은 경우에는 이 방법을 써도 됩니다. 북스캔 프로그램이 정확하게 변형을 해 주기 때문입니다. 하지만 책을 절단할 때 조금 비뚤게 잘렸다면, 그래서 4개 변의 길이가 다르게 찍히면, 북스캔 프로그램이 4개의 꼭지점을 찾아서 정확하게 변형을 해 줄 수가 없습니다. 왜곡된 모습으로 변형을 해 줍니다.
둘째는 책을 정확하게 수직으로 찍는 방법입니다. 이렇게 찍으면, 북스캔 프로그램은 회전 방법으로 책의 수직과 수평을 맞출 수 있게 됩니다. 변형이 아닌 회전 방법을 사용할 수 있게 됩니다. 이것이 제가 선택한 방식이기도 합니다.
6. 저는 나무로 된 공간 박스를 구매했습니다. 윗 부분에 구멍을 뚫었습니다. 이 구멍 위에 스마트폰을 놓고 아래의 책을 찍게 만듭니다. 사진에 그늘이 생기지 않도록 2개의 LED 조명도 달았습니다. 조명이 너무 밝아서 오히려 문제가 되었는데, 다음에 다시 만들게 되면 다이소에서 파는 5천원 짜리 Ring LED를 사용하려고 합니다. 책과 책이 아닌 부분이 확연하게 구별이 되도록 하기 위해서 바닥은 검은색 판지를 붙였습니다. 실제로 책을 찍을 때는 스마트폰이 흔들리지 않도록 테이프로 잘 붙여야 합니다. 이것도 개선의 여지가 있습니다. 스마트폰을 터치하려고 매번 손을 움직이는 게 귀찮을 것 같습니다. 그래서 블루투스 camera shutter를 구매했습니다. 다이소에 가면 2천원에 팔 겁니다. 발로 동작하는 블루투스 카메라 셔터가 나오면 좋겠는데, 아직 적당한 제품을 못 봤습니다. 책을 자르는 것은 업체에 맡길 수도 있고, 자신이 커터 칼로 직접 자를 수도 있습니다. 자를 때는 손을 조심해야 합니다. 빨간 코팅 장갑을 끼고 하면 좋을 것 같군요.
7. 여러분이 300페이지의 책을 찍어서 사진 파일이 300개가 생겼다고 가정하고 이야기를 하겠습니다. 이 사진 파일을 C:의 input 폴더에 저장하십시오. 북스캔 프로그램을 실행하면, 이 input 폴더에 들어 있는 사진 파일들을 가지고 작업을 하게 설정되어 있습니다. 사진 파일을 하나씩 불러서 수직과 수평에 맞도록 회전하고, 책 부분만 선택하여 C:의 output 폴더에 저장하도록 설정되어 있습니다. 이 작업 과정은 진행률 표시기로 표시됩니다. 예를 들어서 1/300 이런 식으로 진행률이 표시됩니다. 작업이 완료되면 프로그램은 자동으로 종료됩니다. 사용자 여러분이 하는 일은 프로그램을 실행하고, GUI에 보이는 <실행 버튼>을 클릭하는 것 뿐입니다. 컴퓨터 CPU의 성능에 따라서 1초에 1장 정도의 시간이 걸립니다. 이 프로그램을 만들 때 저는 그림 파일의 크기를 줄이는 resize 기능을 전혀 사용하지 않았습니다. 회전 각도를 찾아내도록 해서 회전만 하고, 책 부분을 자르게 만들었습니다. 그래서 원본 사진에 가장 가까운 화질이 될 거라고 생각합니다. jpg 포맷으로 저장하게 설정되어 있는데, 여러분은 png 포맷으로 저장하도록 수정할 수도 있겠죠.
8. 코드의 각 단계를 설명하겠습니다.
1. C:/input 폴더에서 jpg 파일들의 이름을 불러와서 변수에 저장합니다.
2. 변수에 저장된 이미지 파일을 하나씩 불러옵니다.
3. 이미지 파일을 그레이 스케일로 변환합니다.
4. 그레이 이미지 파일을 이진화합니다.
5. 컨투어contour를 찾습니다.
6. 가장 큰 컨투어를 찾습니다.
7. 새로운 이미지를 생성합니다. (검은색 배경)
8. 가장 큰 컨투어 중에서 면적이 가장 큰 폐곡선을 찾습니다.
9. 면적이 가장 큰 폐곡선 안에 있는 흰색 점들만 남깁니다. 이 점들을 새로운 이미지에 저장합니다.
10. 새로운 이미지에서 minAreaRect 함수를 사용하여 컨투어를 둘러싼 가장 작은 사각형을 찾습니다.
11. minAreaRect 함수가 반환한 4개의 점의 좌표는 실수 값으로 되어 있으므로, 이를 정수 값으로 변환합니다.
12. 4개 점의 좌표를 좌표값에 따라서 번호를 부여합니다. (사각형이므로 1, 2, 3, 4가 부여됩니다.)
13. 4개 점의 좌표를 보고, 왼쪽으로 기울어졌는지, 오른쪽으로 기울어졌는지를 판별합니다.
14. 기울기를 계산합니다. (기울기 양수/음수일 때 회전 방향이 달라집니다.)
15. 기울기에 따라서 회전할 방향과 각도angle를 계산합니다.
16. angle만큼 회전하고, 전체 이미지를 새로운 변수에 저장합니다.
17. 새로운 변수에 저장된 이미지를 그레이 스케일로 변환합니다.
18. 그레이 이미지 파일을 이진화합니다.
19. 컨투어를 찾습니다.
20. 가장 큰 컨투어를 찾습니다.
21. 새로운 이미지를 생성합니다. (검은색 배경)
22. 면적이 가장 큰 폐곡선 안에 있는 흰색 점들만 남깁니다. 이 점들을 새로운 이미지에 저장합니다.
23. 새로운 이미지에서 minAreaRect 함수를 사용하여 컨투어를 둘러싼 가장 작은 사각형을 찾습니다.
24. minAreaRect 함수가 반환한 4개의 점의 좌표는 실수 값으로 되어 있으므로, 이를 정수 값으로 변환합니다.
25. 4개의 점의 좌표를 이용하여 사각형 영역을 택합니다.
26. 사각형 영역의 데이터를 C:/output 폴더에 저장합니다. (같은 파일 이름으로 저장합니다.)
27. 실행 버튼
28. 전체 파일 수를 표시하는 레이블
29. 처리된 파일 수를 표시하는 레이블
30. 설명 버튼
주의 사항 : 이 코드에 사용된 openCV 라이브러리는 한글을 지원하지 않습니다. 그래서 사진 파일 이름에 한글이 들어 있으면 정상적으로 작동하지 않습니다.
=================
<북스캔 프로그램은 책 사진에서 어떻게 책 부분만 인식할 수 있는 걸까요?>
1. 파이썬과 openCV 라이브러리를 사용합니다.
2. SSD에 저장된 이미지 파일을 읽어서 변수 image에 저장합니다.
3. 이 이미지 파일은 RGB 방식으로 변수에 저장됩니다.
R은 red, G는 green, B는 blue 색입니다.
예를 들어서 4000 * 3000 해상도로 된 사진이라면 1200만 개의 점(화소)으로 이루어진 사진입니다.
각각의 화소는 red 256단계, green 256단계, blue 256단계로 이루어져 있습니다.
예를 들어서 화면에 빨간색으로 표시되는 화소를 괄호와 숫자로 표시하면 (256, 0. 0)이 됩니다.
화면에 녹색으로 표시되는 화소를 괄호와 숫자로 표시하면, (0, 256, 0)이 됩니다.
화면에 흰색으로 표시되는 화소를 괄호와 숫자로 표시하면, (256, 256, 256)이 됩니다.
화면에 검은색으로 표시되는 화소를 괄호와 숫자로 표시하면, (0, 0, 0)이 됩니다.
그러므로 사진의 한 화소는 가로 세로 좌표 정보와 RGB 정보로 이루어져 있다고 보면 될 것 같습니다.
4. RGB로 표시된 컬러 이미지를 그레이 스케일 gray scale로 변환합니다.
그레이 스케일은 우리가 흔히 보는 흑백 사진을 연상하시면 됩니다.
함수를 호출하여, 컬러 사진을 흑백 사진으로 변환하는 과정입니다.
가장 간단한 그레이 스킬 변환 방법은 RGB 숫자를 전부 더하여 그것을 3으로 나누면 됩니다.
예를 들어서 (80, 90, 170)이라는 화소는 전부 더하면 340이 되고, 이것을 3으로 나누면 113이 되겠네요.
이렇게 그레이 스케일로 변환하면, 색깔 정보는 전부 사라지고, 오로지 밝기 정보만 남게 됩니다.
변환된 데이터를 새로운 변수(gray 그레이)에 저장합니다.
5. 그레이를 이진화합니다.
그레이의 한 화소는 0~255 사이의 값을 갖게 됩니다.
우리는 0~255 사이의 한 숫자를 정해서, 이 수보다 더 밝은 점은 1로, 작거나 같은 점은 0으로 변환합니다.
예를 들어서, 130으로 정하면, 130보다 큰 210 화소는 1로 변하고, 110 화소는 0으로 변합니다.
이 기준이 되는 수를 쓰레쉬홀드 threshhold라고 부릅니다.
책 사진을 찍을 때 우리는 검은 색이나 어두운 색으로 된 바탕 위에서 찍어야 합니다.
그 이유는 바로 이 이진화가 정상적으로 처리되도록 하기 위해서입니다.
쓰레쉬홀드 값을 정할 때 사람이 일일이 숫자를 입력할 수도 있습니다만, 아주 귀찮습니다.
그래서 자동으로 적당한 값이 되도록 설정합니다.
이진화한 데이터를 새로운 변수(binary 바이너리)에 저장합니다.
바이너리를 그림으로 표시하면, 흰색과 검은색으로 된 그림이 될 것입니다.
이렇게 하여 사진 속의 책 부분을 흰색 점들로 바꿀 수 있습니다.
(책 속의 글자나 그림 중에서 검은색이나 어두운 색 부분은 0으로 이진화됩니다.)
6. 변수 binary에서 컨투어 contour를 찾습니다.(윤곽, 경계)
바이너리로 변환할 때 책 부분만 깔끔하게 1로 변환되면 좋겠지만,
실제로는 비슷한 색을 가진 것들도 같이 1로 변환됩니다.
예를 들어서 밝은 색의 먼지 같은 것들이 컬러 사진에는 잘 안 보이다가,
이진화하면 흰색으로 변환되기 때문입니다.
그래서 우리는 흰색으로 된 점들 중에서 책 부분을 찾아내야만 합니다.
openCV 라이브러리에는 이런 일을 하는 함수가 있는데, 이것을 컨투어 함수라고 합니다.
먼저 상하좌우로 이어진 모든 흰색 점들을 찾아내어 이 좌표를 각각의 변수에 저장합니다.
이어진 흰색 점들이 여러 개이므로, 이 변수들도 여러 개입니다.
상하좌우로 이어진 흰색 점들이 면적이 넓으면 좌표도 그만큼 많이 변수에 저장됩니다.
상하좌우로 이어진 흰색 점들이 면적이 적으면 좌표도 그만큼 적게 변수에 저장됩니다.
7. 우리가 찾고자 하는 책 부분은 흰색 점들의 면적이 가장 넓을 것입니다.
그래서 컨투어들 중에서 가장 큰 컨투어를 찾습니다.
8. 인간이 경계선(컨투어)을 찾는다면 흰색으로 이어진 모든 점들의 좌표를 변수에 저장하는 방식을 썼을 것입니다.
(경계선을 찾는 것이므로, 내부의 흰색 점들의 좌표는 저장할 필요가 없죠.)
그런데 openCV 컨투어 함수는 인간처럼 처리하지 않았습니다.
점프하는 방식으로 흰색 점들의 좌표를 저장합니다.
너무 멀리 점프하면 왜곡이 많이 생기니까, 대충 5점(?) 정도를 점프하는 방식인 것 같습니다.
이렇게 점프하면 저장되는 좌표의 수가 대폭 줄어들 것이고, 처리 속도도 그만큼 더 빨라질 것입니다.
컨투어 함수는 대체적으로 정상적으로 작동합니다만,
가끔은 이어지지 않은 근처의 흰색 점들이 변수에 포함되는 현상이 발생합니다.
(이 오류 때문에 회전 각도를 정상적으로 계산하지 못하는 경우가 발생합니다.
저는 이 오류를 발견하고 해결하는 데에 여러 번의 시행착오를 거쳐야 했습니다.)
그래서 다음 프로세스를 추가하였습니다.
9. 임시로 사용할 이미지 변수(new_image)를 생성합니다.
이 그림 파일은 전부 검은색이 되도록 RGB 값을 (0, 0, 0)으로 지정합니다.
그림 파일의 가로 세로 크기는 바이너리와 같은 크기가 되도록 설정합니다.
가장 큰 컨투어에서 면적이 가장 큰 폐곡선으로 된 부분을 찾습니다.
가장 큰 컨투어에서 이 폐곡선 안에 있는 흰색 점들을 남기고, 나머지 흰색 점들은 삭제합니다.
이 폐곡선 안에 있는 흰색 점들(경계선)을 new_image에 저장합니다. (실제로는 drawContours 함수로 경계선을 그립니다.)
저장할 때, 경계선의 내부를 흰색 점들로 채우도록 설정합니다.
이렇게 하면 정확하게 책 부분만 흰색으로 된 그림 파일이 만들어집니다.
10. 컨투어를 둘러싼 사각형 중에서 가장 작은 사각형을 찾습니다.
minAreaRect 함수가 이 기능을 합니다.
minAreaRect 함수는 4개의 좌표를 반환합니다. (사각형의 꼭지점 좌표입니다.)
minAreaRect 함수는 좌표를 실수 타입으로 저장합니다.
그래서 우리가 사용할 때는 실수 타입을 정수 타입으로 변환해 주어야만 합니다.
컴퓨터는 변수의 타입이 다르면 정상적으로 계산/처리를 못하니까요.
왜 minAreaRect 함수를 사용하느냐 하면, 책의 꼭지점의 좌표를 찾아내고, 기울기를 계산하기 위해서입니다.
사진을 찍으면 대체로 책은 수직을 기준으로 왼쪽으로 기울거나 오른쪽으로 기울어 있습니다.
책은 4개의 변으로 이루어져 있는데, 이 변들이 사각형과 만나는 점이 있을 것입니다.
그리고 이 점은 흰색으로 되어 있습니다.
사각형의 변들에서 흰색 점의 좌표를 찾아내면, 그것이 바로 책 부분의 꼭지점의 좌표입니다.
11. 책의 꼭지점의 좌표 4개를 구하면,
우리는 이 좌표들을 비교함으로써 책이 왼쪽으로 기울었는지 오른쪽으로 기울었는지 판별할 수가 있습니다.
openCV에서는 왼쪽 상단이 원점(0, 0)입니다.
첫째 단계로 4개 점들의 y좌표값을 비교하면 책의 상단 부분의 점들이 무엇인지 하단 부분의 점들이 무엇인지 확정할 수 있습니다.
둘째 단계로 4개 점들의 x좌표값을 비교하면 책의 왼쪽 부분의 점인지 오른쪽 부분의 점인지 확정할 수 있습니다.
책의 상단의 왼쪽 꼭지점을 점1, 오른쪽 꼭지점을 점2,
책의 하단의 오른쪽 꼭지점을 점3, 왼쪽 꼭지점을 점4라고 가정해 봅시다.
책이 수직과 수평이 맞다면, 점1의 y좌표와 점2의 y좌표는 같은 값을 가지겠죠.
만약 책이 왼쪽으로 기울었다면, 점1의 y좌표값이 점2의 y좌표값보다 더 클 겁니다.
점1과 점2의 y좌표값을 비교하여 왼쪽으로 기울었는지 오른쪽으로 기울었는지 확정할 수 있습니다.
그리고 왼쪽으로 기울었다면, 그림을 오른쪽으로 회전하여야 한다는 것을 알 수 있습니다.
12. 책의 점1과 점2의 좌표를 가지고 우리는 기울기를 계산할 수 있습니다.
책 부분을 수직 수평을 맞추려면, 점1을 기준으로 회전해도 되고, 점 2를 기준으로 회전해도 됩니다.
이 때 회전을 위해서 getRotationMatrix2D함수를 사용하는데, 회전할 기준점P의 좌표를 입력해야 합니다.
이 점P의 좌표는 데이터 타입을 변환해야 합니다.
getRotationMatrix2D 함수를 사용하여 M 값을 정하고,
warpAffine 함수로 원래의 이미지를 회전하여 변수 rotaede_image에 저장합니다.
자, 이 과정까지 처리하면, 원래의 이미지가 책 부분이 수직 수평이 되도록 회전이 되었을 겁니다.
저는 이 rotated_image 파일을 이미지 뷰어로 확인해 봤습니다.
일부 영역은 검은색으로 배경이 채워져 있었고, 일부 영역은 그림에서 없어졌습니다.
이것은 회전을 하였기 때문에 생긴 결과입니다.
13. rotated_image 변수를 가지고 4번부터 10번까지 과정을 실행합니다.
그러면 4개의 좌표가 반환되는데, 이것이 우리가 원하던 수직 수평이 맞는 책 영역입니다.
이 4개의 좌표는 실수 타입이므로 정수 타입으로 변환하고,
이 영역을 jpg 그림 파일로 저장합니다.
저장하는 위치는 C:/output 폴더이고, 저장할 이름은 이미지 파일의 이름과 같아야 합니다.
14. 모든 이미지 파일 이름을 불러들여서 변수에 저장하고,
한 파일씩 읽어서 4번~13번 작업을 반복적으로 실행하면 됩니다.
15. 이 모든 과정을 위해서 GUI를 만듭니다.
<실행> 버튼을 만들어서, 사용자가 클릭하면, 4번~13번이 반복적으로 실행됩니다.
작업 과정이 얼마나 진행되었는지 알기 위해서는 원본 파일의 갯수와 처리된 파일의 갯수가 표시되어야 합니다.
사용자에게 몇 가지 알려야 할 것이 있으므로 <설명> 버튼을 만듭니다.
16. 파이썬은 한글을 잘 처리할 수 있습니다만, openCV는 한글을 제대로 처리하지 못합니다.
그렇기 때문에 사진 파일의 이름에는 한글이 포함되면 정상적으로 파일을 처리하지 못합니다.
영어나 숫자나 특수문자로 된 이름만 사용해야 합니다.
첨부파일 :rotatecut_bookt.zip

첫댓글 댓글 중---
블루프라그마
잘 읽었습니다.
핸드폰으로 찍어 책의 모서리가 말렸다거나,
책을 펼친 후 중심부위 바로 위쪽에서 찍어,
촬영본 중심부 종이가 튀어 오르다 보니 생기는
왜곡(사진의 중심 세로부위는 불룩 튀어나오면서 활자가 크고,
양 끝단으로 갈수록 활자가 퍼스펙티브 해지면서 작아지도록 찍힌 이미지들.
이 두 가지 문제가 있는 스캔본들은 어떻게 똑바로 펴는걸까요?
그러니까 B5 사이즈를 펼치면 B4가 되는데,
스마트폰으로 촬영하면 종이 모서리가 들리면서 말리기도 하고,
양장책 가운데 부분은 렌즈와 가까워 지면서 왜곡이 되죠.
설명을 잘하셔서 이런 문제에 대해,
코드가 아닌 설명만으로도 들어보고 싶네요.
testa1234
@블루프라그마님 vflat 같은 앱들은 수직에서 찍지 않고 아무 각도에서나 찍어도 북스캔이 됩니다. 제가 만든 코드는 회전+잘라내기를 하지만, vflat 같은 앱들은 perspective 어쩌고 하는 변환법을 사용합니다. 유투브에 가면 이 변환 방법을 사용하는 동영상들이 있습니다. 저는 이 방법에 단점이 있다고 생각합니다. 사람이 칼이나 수동절단기로 책을 자를 때는 정확하게 직사각형이 되지 못하는 경우가 많거든요. 또 책의 일부가 떨어져 나간 경우에도 문제가 생깁니다. 그래서 이 방법 대신 수직 촬영+회전+잘라내기를 선택한 것입니다.
일부 프로그램은 책의 좌우 2페이지를 촬영하고, 굽어진 부위는 펴 주는 기능도 탑재하고 있습니다. 저는 이걸 어떻게 구현해냈는지 모릅니다.
저는 프로그래머도 아니고, 사용할 줄 아는 컴퓨터 언어도 하나도 없습니다. chatGPT에게 만들어 달라고 해서 만든 코드이고, 코드를 실행해 봐서 에러가 발생하면 또 수정하라고 요구하고, .... 반복으로 만든 코드입니다. ^ ^