white-paper-scanner 1.0.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.
- white_paper_scanner-1.0.0/PKG-INFO +36 -0
- white_paper_scanner-1.0.0/README.md +23 -0
- white_paper_scanner-1.0.0/pyproject.toml +25 -0
- white_paper_scanner-1.0.0/setup.cfg +4 -0
- white_paper_scanner-1.0.0/src/crop_paper/__init__.py +1 -0
- white_paper_scanner-1.0.0/src/crop_paper/cli.py +145 -0
- white_paper_scanner-1.0.0/src/white_paper_scanner.egg-info/PKG-INFO +36 -0
- white_paper_scanner-1.0.0/src/white_paper_scanner.egg-info/SOURCES.txt +10 -0
- white_paper_scanner-1.0.0/src/white_paper_scanner.egg-info/dependency_links.txt +1 -0
- white_paper_scanner-1.0.0/src/white_paper_scanner.egg-info/entry_points.txt +2 -0
- white_paper_scanner-1.0.0/src/white_paper_scanner.egg-info/requires.txt +2 -0
- white_paper_scanner-1.0.0/src/white_paper_scanner.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: white-paper-scanner
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A utility to automatically crop and flatten photos of documents into highly detailed scanned images.
|
|
5
|
+
Author-email: Anouar Manaa <anouarmanaa19@gmail.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 = "white-paper-scanner"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Anouar Manaa", email="anouarmanaa19@gmail.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
|
+
white-paper-scanner = "crop_paper.cli:main"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cli import process_image, crop_paper
|
|
@@ -0,0 +1,145 @@
|
|
|
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 process_image(image):
|
|
47
|
+
"""
|
|
48
|
+
Takes an OpenCV image array (numpy ndarray) and returns the cropped, flattened, and scanned version.
|
|
49
|
+
"""
|
|
50
|
+
# Resize image to speed up processing and improve contour detection
|
|
51
|
+
ratio = image.shape[0] / 500.0
|
|
52
|
+
orig = image.copy()
|
|
53
|
+
resized_image = cv2.resize(image, (int(image.shape[1] / ratio), 500))
|
|
54
|
+
|
|
55
|
+
# Convert to grayscale, blur slightly to remove high frequency noise, and find edges
|
|
56
|
+
gray = cv2.cvtColor(resized_image, cv2.COLOR_BGR2GRAY)
|
|
57
|
+
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
|
58
|
+
edged = cv2.Canny(gray, 75, 200)
|
|
59
|
+
|
|
60
|
+
# Find contours
|
|
61
|
+
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
|
62
|
+
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
|
|
63
|
+
|
|
64
|
+
screenCnt = None
|
|
65
|
+
for c in contours:
|
|
66
|
+
peri = cv2.arcLength(c, True)
|
|
67
|
+
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
|
|
68
|
+
# If our approximated contour has four points, we can assume that we have found our paper
|
|
69
|
+
if len(approx) == 4:
|
|
70
|
+
screenCnt = approx
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
if screenCnt is None:
|
|
74
|
+
print("Warning: Could not automatically detect the outline of the paper. Proceeding without crop.")
|
|
75
|
+
warped = orig
|
|
76
|
+
else:
|
|
77
|
+
# Apply the perspective transform to crop it out
|
|
78
|
+
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
|
|
79
|
+
|
|
80
|
+
# 1. Crop a small margin (2.5%) off the edges to firmly remove any background/table artifacts
|
|
81
|
+
margin_y = int(warped.shape[0] * 0.025)
|
|
82
|
+
margin_x = int(warped.shape[1] * 0.025)
|
|
83
|
+
if margin_y > 0 and margin_x > 0:
|
|
84
|
+
warped = warped[margin_y:-margin_y, margin_x:-margin_x]
|
|
85
|
+
|
|
86
|
+
# 2. Convert to grayscale to estimate background illumination
|
|
87
|
+
gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
|
|
88
|
+
|
|
89
|
+
# 3. Estimate background illumination to remove shadows
|
|
90
|
+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 25))
|
|
91
|
+
bg = cv2.morphologyEx(gray, cv2.MORPH_DILATE, kernel)
|
|
92
|
+
bg = cv2.GaussianBlur(bg, (25, 25), 0)
|
|
93
|
+
|
|
94
|
+
# 4. Divide EACH color channel by the background (Flat-field correction for color)
|
|
95
|
+
scanned = np.zeros_like(warped)
|
|
96
|
+
for i in range(3):
|
|
97
|
+
scanned[:, :, i] = cv2.divide(warped[:, :, i], bg, scale=255)
|
|
98
|
+
|
|
99
|
+
# 5. Apply Gamma Correction to preserve and darken thin fonts!
|
|
100
|
+
gamma = 2.2
|
|
101
|
+
table = np.array([((i / 255.0) ** gamma) * 255 for i in np.arange(0, 256)]).astype("uint8")
|
|
102
|
+
scanned = cv2.LUT(scanned, table)
|
|
103
|
+
|
|
104
|
+
# 6. Apply a gentle contrast stretch to deepen the blacks
|
|
105
|
+
scanned = cv2.convertScaleAbs(scanned, alpha=1.2, beta=-10)
|
|
106
|
+
|
|
107
|
+
# 7. Ensure the background is perfectly white without destroying colored elements
|
|
108
|
+
gray_scanned = cv2.cvtColor(scanned, cv2.COLOR_BGR2GRAY)
|
|
109
|
+
scanned[gray_scanned > 210] = [255, 255, 255]
|
|
110
|
+
|
|
111
|
+
# 8. Multiply Megapixels!
|
|
112
|
+
scanned = cv2.resize(scanned, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_LANCZOS4)
|
|
113
|
+
|
|
114
|
+
# 9. Apply a subtle Unsharp Mask to guarantee the text is razor-sharp when zooming in
|
|
115
|
+
gaussian = cv2.GaussianBlur(scanned, (0, 0), 2.0)
|
|
116
|
+
scanned = cv2.addWeighted(scanned, 1.5, gaussian, -0.5, 0)
|
|
117
|
+
|
|
118
|
+
return scanned
|
|
119
|
+
|
|
120
|
+
def crop_paper(image_path):
|
|
121
|
+
print(f"Processing '{image_path}'...")
|
|
122
|
+
image = cv2.imread(image_path)
|
|
123
|
+
if image is None:
|
|
124
|
+
print(f"Error loading {image_path}. Check if the file exists.")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
scanned = process_image(image)
|
|
128
|
+
|
|
129
|
+
base_name = os.path.basename(image_path)
|
|
130
|
+
name, _ = os.path.splitext(base_name)
|
|
131
|
+
output_path = f"{name}_scanned.jpg"
|
|
132
|
+
|
|
133
|
+
cv2.imwrite(output_path, scanned)
|
|
134
|
+
print(f"Successfully saved scanned image to '{output_path}'")
|
|
135
|
+
|
|
136
|
+
def main():
|
|
137
|
+
if len(sys.argv) < 2:
|
|
138
|
+
print("Usage: white-paper-scanner <path_to_image>")
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
target_image = sys.argv[1]
|
|
142
|
+
crop_paper(target_image)
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: white-paper-scanner
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A utility to automatically crop and flatten photos of documents into highly detailed scanned images.
|
|
5
|
+
Author-email: Anouar Manaa <anouarmanaa19@gmail.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/white_paper_scanner.egg-info/PKG-INFO
|
|
6
|
+
src/white_paper_scanner.egg-info/SOURCES.txt
|
|
7
|
+
src/white_paper_scanner.egg-info/dependency_links.txt
|
|
8
|
+
src/white_paper_scanner.egg-info/entry_points.txt
|
|
9
|
+
src/white_paper_scanner.egg-info/requires.txt
|
|
10
|
+
src/white_paper_scanner.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
crop_paper
|