paper-scanner 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- paper_scanner-0.1.0/PKG-INFO +36 -0
- paper_scanner-0.1.0/README.md +23 -0
- paper_scanner-0.1.0/pyproject.toml +25 -0
- paper_scanner-0.1.0/setup.cfg +4 -0
- paper_scanner-0.1.0/src/crop_paper/__init__.py +0 -0
- paper_scanner-0.1.0/src/crop_paper/cli.py +147 -0
- paper_scanner-0.1.0/src/paper_scanner.egg-info/PKG-INFO +36 -0
- paper_scanner-0.1.0/src/paper_scanner.egg-info/SOURCES.txt +10 -0
- paper_scanner-0.1.0/src/paper_scanner.egg-info/dependency_links.txt +1 -0
- paper_scanner-0.1.0/src/paper_scanner.egg-info/entry_points.txt +2 -0
- paper_scanner-0.1.0/src/paper_scanner.egg-info/requires.txt +2 -0
- paper_scanner-0.1.0/src/paper_scanner.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: paper-scanner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A utility to automatically crop and flatten photos of documents into highly detailed scanned images.
|
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: opencv-python-headless>=4.0
|
|
12
|
+
Requires-Dist: numpy>=1.0
|
|
13
|
+
|
|
14
|
+
# Paper Scanner
|
|
15
|
+
|
|
16
|
+
A command-line utility to automatically transform tilted photographs of documents into perfectly flat, highly detailed scanned images.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
- **Automatic Edge Detection**: Detects the four corners of a document.
|
|
20
|
+
- **Perspective Flattening**: Warps a tilted photo into a perfect top-down view.
|
|
21
|
+
- **Flat-Field Illumination Correction**: Removes harsh shadows and uneven lighting to generate a perfect white background while preserving the original ink colors and anti-aliasing of the text.
|
|
22
|
+
- **Auto-Upscaling**: Mathematically doubles the megapixels and applies unsharp masking so your text stays razor-sharp even when zoomed all the way in.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install paper-scanner
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
paper-scanner path/to/your/image.jpg
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This will output a new file named `image_scanned.jpg` in the same directory.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Paper Scanner
|
|
2
|
+
|
|
3
|
+
A command-line utility to automatically transform tilted photographs of documents into perfectly flat, highly detailed scanned images.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **Automatic Edge Detection**: Detects the four corners of a document.
|
|
7
|
+
- **Perspective Flattening**: Warps a tilted photo into a perfect top-down view.
|
|
8
|
+
- **Flat-Field Illumination Correction**: Removes harsh shadows and uneven lighting to generate a perfect white background while preserving the original ink colors and anti-aliasing of the text.
|
|
9
|
+
- **Auto-Upscaling**: Mathematically doubles the megapixels and applies unsharp masking so your text stays razor-sharp even when zoomed all the way in.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install paper-scanner
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
paper-scanner path/to/your/image.jpg
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This will output a new file named `image_scanned.jpg` in the same directory.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "paper-scanner"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Your Name", email="your.email@example.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A utility to automatically crop and flatten photos of documents into highly detailed scanned images."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.7"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"opencv-python-headless>=4.0",
|
|
16
|
+
"numpy>=1.0"
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
paper-scanner = "crop_paper.cli:main"
|
|
Binary file
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import numpy as np
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
def order_points(pts):
|
|
7
|
+
# order points: top-left, top-right, bottom-right, bottom-left
|
|
8
|
+
rect = np.zeros((4, 2), dtype="float32")
|
|
9
|
+
|
|
10
|
+
# top-left will have the smallest sum, bottom-right the largest sum
|
|
11
|
+
s = pts.sum(axis=1)
|
|
12
|
+
rect[0] = pts[np.argmin(s)]
|
|
13
|
+
rect[2] = pts[np.argmax(s)]
|
|
14
|
+
|
|
15
|
+
# top-right will have the smallest difference, bottom-left the largest difference
|
|
16
|
+
diff = np.diff(pts, axis=1)
|
|
17
|
+
rect[1] = pts[np.argmin(diff)]
|
|
18
|
+
rect[3] = pts[np.argmax(diff)]
|
|
19
|
+
|
|
20
|
+
return rect
|
|
21
|
+
|
|
22
|
+
def four_point_transform(image, pts):
|
|
23
|
+
rect = order_points(pts)
|
|
24
|
+
(tl, tr, br, bl) = rect
|
|
25
|
+
|
|
26
|
+
# compute width of new image
|
|
27
|
+
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
|
|
28
|
+
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
|
|
29
|
+
maxWidth = max(int(widthA), int(widthB))
|
|
30
|
+
|
|
31
|
+
# compute height of new image
|
|
32
|
+
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
|
|
33
|
+
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
|
|
34
|
+
maxHeight = max(int(heightA), int(heightB))
|
|
35
|
+
|
|
36
|
+
dst = np.array([
|
|
37
|
+
[0, 0],
|
|
38
|
+
[maxWidth - 1, 0],
|
|
39
|
+
[maxWidth - 1, maxHeight - 1],
|
|
40
|
+
[0, maxHeight - 1]], dtype="float32")
|
|
41
|
+
|
|
42
|
+
M = cv2.getPerspectiveTransform(rect, dst)
|
|
43
|
+
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
|
|
44
|
+
return warped
|
|
45
|
+
|
|
46
|
+
def crop_paper(image_path):
|
|
47
|
+
print(f"Processing '{image_path}'...")
|
|
48
|
+
image = cv2.imread(image_path)
|
|
49
|
+
if image is None:
|
|
50
|
+
print(f"Error loading {image_path}. Check if the file exists.")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
# Resize image to speed up processing and improve contour detection
|
|
54
|
+
ratio = image.shape[0] / 500.0
|
|
55
|
+
orig = image.copy()
|
|
56
|
+
image = cv2.resize(image, (int(image.shape[1] / ratio), 500))
|
|
57
|
+
|
|
58
|
+
# Convert to grayscale, blur slightly to remove high frequency noise, and find edges
|
|
59
|
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
60
|
+
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
|
61
|
+
edged = cv2.Canny(gray, 75, 200)
|
|
62
|
+
|
|
63
|
+
# Find contours
|
|
64
|
+
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
|
65
|
+
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
|
|
66
|
+
|
|
67
|
+
screenCnt = None
|
|
68
|
+
for c in contours:
|
|
69
|
+
peri = cv2.arcLength(c, True)
|
|
70
|
+
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
|
|
71
|
+
# If our approximated contour has four points, we can assume that we have found our paper
|
|
72
|
+
if len(approx) == 4:
|
|
73
|
+
screenCnt = approx
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
if screenCnt is None:
|
|
77
|
+
print("Warning: Could not automatically detect the outline of the paper.")
|
|
78
|
+
print("Saving the original image without cropping.")
|
|
79
|
+
warped = orig
|
|
80
|
+
else:
|
|
81
|
+
# Apply the perspective transform to crop it out
|
|
82
|
+
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
|
|
83
|
+
|
|
84
|
+
# ---------------------------------------------------------
|
|
85
|
+
# NEW: Enhance the document to make it look scanned
|
|
86
|
+
|
|
87
|
+
# 1. Crop a small margin (2.5%) off the edges to firmly remove any background/table artifacts
|
|
88
|
+
margin_y = int(warped.shape[0] * 0.025)
|
|
89
|
+
margin_x = int(warped.shape[1] * 0.025)
|
|
90
|
+
if margin_y > 0 and margin_x > 0:
|
|
91
|
+
warped = warped[margin_y:-margin_y, margin_x:-margin_x]
|
|
92
|
+
|
|
93
|
+
# 2. Convert to grayscale to estimate background illumination
|
|
94
|
+
gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
|
|
95
|
+
|
|
96
|
+
# 3. Estimate background illumination to remove shadows
|
|
97
|
+
# Dilation erases the dark text, leaving just the paper's lighting
|
|
98
|
+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
|
|
99
|
+
bg = cv2.morphologyEx(gray, cv2.MORPH_DILATE, kernel)
|
|
100
|
+
bg = cv2.GaussianBlur(bg, (25, 25), 0)
|
|
101
|
+
|
|
102
|
+
# 4. Divide EACH color channel by the background (Flat-field correction for color)
|
|
103
|
+
# This removes shadows but preserves the natural colors and anti-aliasing!
|
|
104
|
+
scanned = np.zeros_like(warped)
|
|
105
|
+
for i in range(3):
|
|
106
|
+
scanned[:, :, i] = cv2.divide(warped[:, :, i], bg, scale=255)
|
|
107
|
+
|
|
108
|
+
# 5. Apply Gamma Correction to preserve and darken thin fonts!
|
|
109
|
+
# Gamma > 1 pulls light grays (thin text) down into darker shades,
|
|
110
|
+
# while leaving pure white (255) completely unaffected.
|
|
111
|
+
gamma = 2.2
|
|
112
|
+
table = np.array([((i / 255.0) ** gamma) * 255 for i in np.arange(0, 256)]).astype("uint8")
|
|
113
|
+
scanned = cv2.LUT(scanned, table)
|
|
114
|
+
|
|
115
|
+
# 6. Apply a gentle contrast stretch to deepen the blacks
|
|
116
|
+
scanned = cv2.convertScaleAbs(scanned, alpha=1.2, beta=-10)
|
|
117
|
+
|
|
118
|
+
# 7. Ensure the background is perfectly white without destroying colored elements
|
|
119
|
+
gray_scanned = cv2.cvtColor(scanned, cv2.COLOR_BGR2GRAY)
|
|
120
|
+
scanned[gray_scanned > 210] = [255, 255, 255]
|
|
121
|
+
|
|
122
|
+
# 8. Multiply Megapixels!
|
|
123
|
+
# Upscale the image 2x using Lanczos interpolation (great for keeping text edges sharp)
|
|
124
|
+
scanned = cv2.resize(scanned, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_LANCZOS4)
|
|
125
|
+
|
|
126
|
+
# 9. Apply a subtle Unsharp Mask to guarantee the text is razor-sharp when zooming in
|
|
127
|
+
gaussian = cv2.GaussianBlur(scanned, (0, 0), 2.0)
|
|
128
|
+
scanned = cv2.addWeighted(scanned, 1.5, gaussian, -0.5, 0)
|
|
129
|
+
# ---------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
base_name = os.path.basename(image_path)
|
|
132
|
+
name, _ = os.path.splitext(base_name)
|
|
133
|
+
output_path = f"{name}_scanned.jpg"
|
|
134
|
+
|
|
135
|
+
cv2.imwrite(output_path, scanned)
|
|
136
|
+
print(f"Successfully saved scanned image to '{output_path}'")
|
|
137
|
+
|
|
138
|
+
def main():
|
|
139
|
+
if len(sys.argv) < 2:
|
|
140
|
+
print("Usage: paper-scanner <path_to_image>")
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
target_image = sys.argv[1]
|
|
144
|
+
crop_paper(target_image)
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: paper-scanner
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A utility to automatically crop and flatten photos of documents into highly detailed scanned images.
|
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: opencv-python-headless>=4.0
|
|
12
|
+
Requires-Dist: numpy>=1.0
|
|
13
|
+
|
|
14
|
+
# Paper Scanner
|
|
15
|
+
|
|
16
|
+
A command-line utility to automatically transform tilted photographs of documents into perfectly flat, highly detailed scanned images.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
- **Automatic Edge Detection**: Detects the four corners of a document.
|
|
20
|
+
- **Perspective Flattening**: Warps a tilted photo into a perfect top-down view.
|
|
21
|
+
- **Flat-Field Illumination Correction**: Removes harsh shadows and uneven lighting to generate a perfect white background while preserving the original ink colors and anti-aliasing of the text.
|
|
22
|
+
- **Auto-Upscaling**: Mathematically doubles the megapixels and applies unsharp masking so your text stays razor-sharp even when zoomed all the way in.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install paper-scanner
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
paper-scanner path/to/your/image.jpg
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This will output a new file named `image_scanned.jpg` in the same directory.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/crop_paper/__init__.py
|
|
4
|
+
src/crop_paper/cli.py
|
|
5
|
+
src/paper_scanner.egg-info/PKG-INFO
|
|
6
|
+
src/paper_scanner.egg-info/SOURCES.txt
|
|
7
|
+
src/paper_scanner.egg-info/dependency_links.txt
|
|
8
|
+
src/paper_scanner.egg-info/entry_points.txt
|
|
9
|
+
src/paper_scanner.egg-info/requires.txt
|
|
10
|
+
src/paper_scanner.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
crop_paper
|