diff --git a/book.jpg b/book.jpg new file mode 100644 index 0000000000000000000000000000000000000000..37ccd87dc68071799782ebe0d42435f5e4dbba37 Binary files /dev/null and b/book.jpg differ diff --git a/book2.jpg b/book2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6140f846c0e87fa2850a738ae7b736dd028f1b26 Binary files /dev/null and b/book2.jpg differ diff --git a/images/book.jpg b/images/book.jpg new file mode 100644 index 0000000000000000000000000000000000000000..37ccd87dc68071799782ebe0d42435f5e4dbba37 Binary files /dev/null and b/images/book.jpg differ diff --git a/images/book2.jpg b/images/book2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6140f846c0e87fa2850a738ae7b736dd028f1b26 Binary files /dev/null and b/images/book2.jpg differ diff --git a/images/receipt.jpg b/images/receipt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6a1fea89295717a2dc66f273876b1f6e79f98053 Binary files /dev/null and b/images/receipt.jpg differ diff --git a/images/receipt2.jpg b/images/receipt2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03929d831c03842d6375aa051dd91b1362379008 Binary files /dev/null and b/images/receipt2.jpg differ diff --git a/modified/modified.jpg b/modified/modified.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6380610691871f1e8cac343bc0c0ce2e38c198bf Binary files /dev/null and b/modified/modified.jpg differ diff --git a/openCV_scanner.ipynb b/openCV_scanner.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ad83d382691c5e317746743f63454d40bc7ec7fb --- /dev/null +++ b/openCV_scanner.ipynb @@ -0,0 +1,617 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 78, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import cv2\n", + "import glob\n", + "from matplotlib import pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [], + "source": [ + "def findMaxArea(contours): \n", + " max_area = -1\n", + " max_index = -1\n", + " for i,contour in enumerate(contours):\n", + " area = cv2.contourArea(contour)\n", + " x,y,w,h = cv2.boundingRect(contour)\n", + " if (w*h)*0.4 > area:\n", + " continue\n", + " if w > h:\n", + " continue\n", + " if area > max_area:\n", + " max_area = area\n", + " max_index = i\n", + " if max_area < 10000:\n", + " max_index = -1\n", + " return max_index" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [], + "source": [ + "def transform(img_input, points):\n", + " # 다음 순서롤 갖도록 꼭지점을 정렬한다.\n", + " # top left, top right, bottom right, bottom left\n", + " points = sort_points(points)\n", + " topLeft, topRight, bottomRight, bottomLeft = points\n", + " print(topLeft, topRight, bottomRight, bottomLeft)\n", + " print(topLeft[0] + topLeft[1], topRight[0]+topRight[1],\n", + " bottomRight[0]+bottomRight[1], bottomLeft[0]+bottomLeft[1])\n", + " \n", + " # 변환 후 책의 너비와 높이를 결정한다.\n", + " topWidth = distance(bottomLeft, bottomRight)\n", + " bottomWidth = distance(topLeft, topRight)\n", + " maxWidth = max(int(topWidth), int(bottomWidth))\n", + " \n", + " leftHeight = distance(topLeft, bottomLeft)\n", + " rightHeight = distance(topRight, bottomRight)\n", + " maxHeight = max(int(leftHeight), int(rightHeight))\n", + " \n", + " # 정면에서 바라본 책의 좌표를 결정한다.\n", + " dst = np.array([[0, 0],[maxWidth - 1, 0],\n", + " [maxWidth - 1, maxHeight - 1],[0, maxHeight - 1]], dtype = \"float32\")\n", + " \n", + " H = cv2.getPerspectiveTransform(points, dst)\n", + " img_warped = cv2.warpPerspective(img_input, H, (maxWidth, maxHeight))\n", + " \n", + " return img_warped\n" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [], + "source": [ + "def sort_points(points):\n", + "\n", + " points = points.astype(np.float32)\n", + "\n", + " new_points = np.zeros((4, 2), dtype = \"float32\")\n", + " \n", + "\n", + " s = points.sum(axis = 1)\n", + " min_index = np.argmin(s)\n", + " new_points[0] = points[min_index]\n", + " points = np.delete(points, min_index, axis = 0)\n", + "\n", + " s = points.sum(axis = 1)\n", + " max_index = np.argmax(s)\n", + " new_points[2] = points[max_index]\n", + " points = np.delete(points, max_index, axis = 0)\n", + "\n", + " v0 = points[0] - new_points[0]\n", + " v1 = points[1] - new_points[0]\n", + "\n", + " angle = angle_between(v0, v1)\n", + "\n", + " if angle < 0:\n", + " new_points[1] = points[1]\n", + " new_points[3] = points[0]\n", + " else:\n", + " new_points[1] = points[0]\n", + " new_points[3] = points[1]\n", + " \n", + " return new_points" + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [], + "source": [ + "def angle_between(A, B):\n", + "\n", + " x1 = A[0]\n", + " y1 = A[1]\n", + " x2 = B[0]\n", + " y2 = B[1]\n", + "\n", + " dot = x1*x2 + y1*y2 \n", + " det = x1*y2 - y1*x2 \n", + " angle = np.arctan2(det, dot) * 180/np.pi \n", + "\n", + " return angle" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [], + "source": [ + "# 마우스를 이용하여 책의 꼭지점을 조정한다.\n", + "def mouse_callback(event,x,y,flags,param):\n", + " \n", + " global mouse_is_pressing,points\n", + "\n", + " if step != 1:\n", + " return\n", + "\n", + " if event == cv2.EVENT_MOUSEMOVE: \n", + " if mouse_is_pressing == True: ## 마우스가 움직이고, 눌린상태일때\n", + "\n", + " for i,point in enumerate(points):\n", + " if distance((x,y), point) < 15: ##마우스의 좌표와, 포인트(꼭지점) 의 거리가 가깝다면\n", + " points[i][0] = x ##꼭지점을 이동한다\n", + " points[i][1] = y\n", + " break \n", + " \n", + " elif event == cv2.EVENT_LBUTTONDOWN: ##마우스버튼이 눌린상태에서, 꼭짓점과의 거리가 가깝다면\n", + " ##press 를 Ture로 바꾼다.\n", + " for point in points:\n", + " if distance((x,y), point) < 10:\n", + " mouse_is_pressing = True\n", + " break\n", + "\n", + " elif event == cv2.EVENT_LBUTTONUP: ## 마우스가 안눌려있으면, press 를 false로 바꿈\n", + "\n", + " mouse_is_pressing = False" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [], + "source": [ + "def distance(point1, point2):\n", + " \n", + " x1,y1 = point1\n", + " x2,y2 = point2\n", + " \n", + " return int(np.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)))" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\ncv2.imshow('images',image)\\ncv2.waitKey(0)\\n\"" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = cv2.imread('./images/receipt.jpg') \n", + "\n", + "# rgb 이미지 보기\n", + "\"\"\"\n", + "cv2.imshow('images',image)\n", + "cv2.waitKey(0)\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\npath = 'C:/Users/User/Desktop/opencv/images' # 폴더 경로\\nos.chdir(path) # 해당 폴더로 이동\\n\\nfiles = os.listdir(path) # 해당 폴더에 있는 파일 이름을 리스트 형태로 받음\\npng_img = []\\njpg_img = []\\nfor file in files:\\n if '.png' in file: \\n f = cv2.imread(file)\\n png_img.append(f)\\n if '.jpg' in file: \\n f = cv2.imread(file)\\n jpg_img.append(f)\\n\"" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "######################################################################\n", + "#이미지파일 한번에 여러개 받는 방법??\n", + "#\n", + "#\n", + "\"\"\"\n", + "path = 'C:/Users/User/Desktop/opencv/images' # 폴더 경로\n", + "os.chdir(path) # 해당 폴더로 이동\n", + "\n", + "files = os.listdir(path) # 해당 폴더에 있는 파일 이름을 리스트 형태로 받음\n", + "png_img = []\n", + "jpg_img = []\n", + "for file in files:\n", + " if '.png' in file: \n", + " f = cv2.imread(file)\n", + " png_img.append(f)\n", + " if '.jpg' in file: \n", + " f = cv2.imread(file)\n", + " jpg_img.append(f)\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\ncv2.imshow('grabCut', image_grabcut)\\ncv2.waitKey(0)\\n\"" + ] + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#grabcut , 배경 제거\n", + "points = []\n", + "height,width =image.shape[:2]\n", + "image_mask = np.zeros(image.shape[:2], np.uint8)\n", + "\n", + "bgdModel = np.zeros((1,65),np.float64)\n", + "fgdModel = np.zeros((1,65),np.float64)\n", + "\n", + "rect = (10,10,width-30,height-30)\n", + "cv2.grabCut(image, image_mask, rect, bgdModel,fgdModel, 3, cv2.GC_INIT_WITH_RECT)\n", + "\n", + "image_mask = np.where((image_mask==2)|(image_mask==0), 0, 1).astype('uint8')\n", + "image_grabcut = image*image_mask[:,:,np.newaxis]\n", + "\"\"\"\n", + "cv2.imshow('grabCut', image_grabcut)\n", + "cv2.waitKey(0)\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-1" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#케니에지 디텍션을 이용하여 에지 검출\n", + "image_gray = cv2.cvtColor(image_grabcut, cv2.COLOR_BGR2GRAY);\n", + "image_canny = cv2.Canny(image_gray, 30, 90);\n", + "\n", + "\"\"\"\n", + "cv2.imshow('Canny', image_canny)\n", + "cv2.waitKey(0)\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 110, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-1" + ] + }, + "execution_count": 110, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#모폴로지 클로즈 연산\n", + "\n", + "kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))\n", + "image_canny = cv2.morphologyEx(image_canny, cv2.MORPH_CLOSE, kernel, 1)\n", + "\n", + "\"\"\"\n", + "cv2.imshow('morphology', image_canny)\n", + "cv2.waitKey(0)\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\nimage_contour = image.copy()\\ncv2.drawContours(image_contour, [max_contour], 0, (0, 0, 255), 3)\\ncv2.imshow('Contour', image_contour)\\ncv2.waitKey(0)\\n\"" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# 캐니 에지 결과에서 컨투어를 검출하여 가장 큰 영역을 차지하는 컨투어를 찾는다.\n", + "contours, hierarchy = cv2.findContours(image_canny, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)\n", + "max_index = findMaxArea(contours) \n", + "\"\"\"\n", + "if max_index < 0:\n", + " return points\n", + "\"\"\"\n", + "if max_index < 0:\n", + " print(points)\n", + "max_contour = contours[max_index]\n", + "\n", + "\"\"\"\n", + "image_contour = image.copy()\n", + "cv2.drawContours(image_contour, [max_contour], 0, (0, 0, 255), 3)\n", + "cv2.imshow('Contour', image_contour)\n", + "cv2.waitKey(0)\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 111, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-1" + ] + }, + "execution_count": 111, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# approxPolyDP 함수를 사용하여 컨투어를 근사화하고 convex hull을 구한다.\n", + "max_contour = cv2.approxPolyDP(max_contour,0.02*cv2.arcLength(max_contour,True),True)\n", + "hull = cv2.convexHull(max_contour)\n", + "\n", + "\"\"\"\n", + "img_convexhull = image.copy()\n", + "cv2.drawContours(img_convexhull, [hull], 0, (255,255,0), 5)\n", + "cv2.imshow('convexHull', img_convexhull)\n", + "cv2.waitKey(0)\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "size = len(max_contour) \n", + "\n", + "if size == 4: # 책의 꼭지점을 구한다.\n", + " for c in hull:\n", + " points.append(c[0])\n", + " points = np.array(points)\n", + " print(points)\n", + "\n", + "else: # 꼭지점이 5개 이상인 경우 경계 박스를 이용한다.\n", + " \n", + " rect = cv2.minAreaRect(hull)\n", + " box = cv2.boxPoints(rect)\n", + " points = np.int0(box.tolist())\n", + "\n", + " # 검출된 꼭지점이 이미지 범위를 벗어난 경우 책 꼭지점 처리\n", + " found = False\n", + " for p in points:\n", + " if p[0] < 0 or p[0] > width-1 or p[1] < 0 or p[1] > height -1:\n", + " found = True \n", + " break\n", + "\n", + " if found:\n", + " points = np.array([[10,10], [width-11, 10], [width-11, height-11], [10, height-11]])\n", + " \"\"\"\n", + " return points\n", + " \"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[66. 24.] [1428. 10.] [1428. 1041.] [ 10. 1041.]\n", + "90.0 1438.0 2469.0 1051.0\n", + "END\n" + ] + } + ], + "source": [ + "size = len(points)\n", + "if size == 4:\n", + "\n", + "##############################################################\n", + "# 여기에 사용자가 마우스 사용할지 안할지에대한 GUI 넣기?\n", + "# \n", + "#\n", + "#\n", + "####################################################################\n", + " # 마우스 콜백 함수를 등록하고\n", + " cv2.namedWindow('input')\n", + " cv2.setMouseCallback(\"input\", mouse_callback, 0); \n", + "\n", + "\n", + " step = 1\n", + "\n", + " # 마우스를 이용하여 꼭지점을 조정한다.\n", + " while True:\n", + "\n", + " image_result = image.copy()\n", + " for point in points:\n", + " cv2.circle(image_result, tuple(point), 10, (255,0,0), 3 ) \n", + " cv2.imshow('input', image_result)\n", + "\n", + " key = cv2.waitKey(1)\n", + " if key == 32: # 스페이스바를 누르면 선택, \n", + " break\n", + "\n", + " # 꼭지점을 이용하여 정면에서 본 책으로 변환한다.\n", + " image_final = transform(image, points )\n", + "\n", + "\n", + " cv2.imshow('input', image_result)\n", + " cv2.imshow('result', image_final )\n", + " \n", + " print(\"END\")\n", + "\n", + "else:\n", + " cv2.imshow('input', image)\n", + "\n", + "cv2.waitKey(0)\n", + "cv2.destroyAllWindows()" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "-1" + ] + }, + "execution_count": 105, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "################################################################\n", + "############영수증의 경우 이것을 사용하면 글씨가 돋보임 ( 흑백대비)\n", + "gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n", + "\n", + "sharpen = cv2.GaussianBlur(gray, (0,0), 3)\n", + "sharpen = cv2.addWeighted(gray, 1.5, sharpen, -0.5, 0)\n", + "thresh = cv2.adaptiveThreshold(sharpen, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 15)\n", + "cv2.imshow('grayscale?', thresh)\n", + "cv2.waitKey(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "cv2.imwrite('C:/Users/User/Desktop/opencv/modified/modified.jpg', image_final) \n", + "#이미지 저장" + ] + }, + { + "cell_type": "code", + "execution_count": 107, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"\\nimport cv2\\nimage = cv2.imread('./images/book.jpg') \\n\\n# rgb 이미지 보기\\ncv2.imshow('images',image) \\ncv2.resizeWindow('images', 600, 600)\\ncv2.waitKey(0)\\ndef onMouse(event, x, y, flags, param): # 아무스 콜백 함수 구현 ---①\\n print(event, x, y, ) # 파라미터 출력\\n if event == cv2.EVENT_LBUTTONDOWN: # 왼쪽 버튼 누름인 경우 ---②\\n cv2.circle(image, (x,y), 30, (0,0,0), -1) # 지름 30 크기의 검은색 원을 해당 좌표에 그림\\n cv2.imshow(images, image) # 그려진 이미지를 다시 표시 ---③\\ncv2.setMouseCallback(images, onMouse) # 마우스 콜백 함수를 GUI 윈도우에 등록 ---④\\n\"" + ] + }, + "execution_count": 107, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\"\"\"\n", + "import cv2\n", + "image = cv2.imread('./images/book.jpg') \n", + "\n", + "# rgb 이미지 보기\n", + "cv2.imshow('images',image) \n", + "cv2.resizeWindow('images', 600, 600)\n", + "cv2.waitKey(0)\n", + "def onMouse(event, x, y, flags, param): # 아무스 콜백 함수 구현 ---①\n", + " print(event, x, y, ) # 파라미터 출력\n", + " if event == cv2.EVENT_LBUTTONDOWN: # 왼쪽 버튼 누름인 경우 ---②\n", + " cv2.circle(image, (x,y), 30, (0,0,0), -1) # 지름 30 크기의 검은색 원을 해당 좌표에 그림\n", + " cv2.imshow(images, image) # 그려진 이미지를 다시 표시 ---③\n", + "cv2.setMouseCallback(images, onMouse) # 마우스 콜백 함수를 GUI 윈도우에 등록 ---④\n", + "\"\"\"\n", + "\n", + "######################################################################################################\n", + "# 추가로 해야할것 :\n", + "# 1. 마우스 입력을 받을때와 안받을때 구분짓기\n", + "# 2. 폴더채로 이미지파일 받아와서 한번에 처리하기.\n", + "# 3. 흑백 색 강조하여 이미지 깔끔하게 만들기\n", + "# 4. 파일 저장형식을 사용자가 원하는대로 만들기" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/receipt.jpg b/receipt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6a1fea89295717a2dc66f273876b1f6e79f98053 Binary files /dev/null and b/receipt.jpg differ diff --git a/receipt2.jpg b/receipt2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03929d831c03842d6375aa051dd91b1362379008 Binary files /dev/null and b/receipt2.jpg differ