Skip to content

Commit 1aad3c8

Browse files
committed
Add a Flask web example
1 parent 8d6c7ee commit 1aad3c8

File tree

9 files changed

+248
-0
lines changed

9 files changed

+248
-0
lines changed

examples/web/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.pyc

examples/web/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Python Flask Web Document Scanner
2+
This project demonstrates how to build a web-based document scanner using the **Dynamsoft Document Normalizer SDK** and **Flask**. The application leverages a connected camera to capture documents, processes them on the server-side, and presents the results in the web browser.
3+
4+
## Installation
5+
To install the required dependencies, run:
6+
7+
```bash
8+
pip install -r requirements.txt
9+
```
10+
11+
## Prerequisites
12+
- Obtain a [30-day free trial license](https://www.dynamsoft.com/customer/license/trialLicense/?product=ddn) for the Dynamsoft Document Normalizer SDK.
13+
14+
## How to Run
15+
1. **Set the License Key**: Update the license key in `document.py`:
16+
17+
```python
18+
docscanner.initLicense("LICENSE-KEY")
19+
```
20+
21+
2. **Connect a Camera**: Ensure your camera is properly connected to your computer.
22+
3. **Start the Application**: Run the Flask server and open the application in your web browser:
23+
24+
```bash
25+
python server.py
26+
```
27+
4. **Access the Application**: Visit `http://127.0.0.1:5000` in your web browser to use the document scanner.
28+
29+
![Python Flask web document scanner](https://www.dynamsoft.com/codepool/img/2024/08/python-flask-web-document-scanner.png)
30+
31+

examples/web/camera.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import cv2
2+
from document import Scanner
3+
4+
5+
class VideoCamera(object):
6+
def __init__(self):
7+
# Open a camera
8+
self.cap = cv2.VideoCapture(0)
9+
10+
# Initialize video recording environment
11+
self.is_record = False
12+
self.out = None
13+
self.transformed_frame = None
14+
15+
self.scanner = Scanner()
16+
self.cached_frame = None
17+
18+
def __del__(self):
19+
self.cap.release()
20+
21+
def get_video_frame(self):
22+
ret, frame = self.cap.read()
23+
if ret:
24+
frame, _ = self.scanner.detect_edge(frame)
25+
self.cached_frame = frame
26+
ret, jpeg = cv2.imencode('.jpg', frame)
27+
return jpeg.tobytes()
28+
else:
29+
return None
30+
31+
def capture_frame(self):
32+
ret, frame = self.cap.read()
33+
if ret:
34+
_, frame = self.scanner.detect_edge(frame, True)
35+
ret, jpeg = cv2.imencode('.jpg', frame)
36+
self.transformed_frame = jpeg.tobytes()
37+
else:
38+
return None
39+
40+
def get_cached_frame(self):
41+
return self.cached_frame
42+
43+
def get_image_frame(self):
44+
return self.transformed_frame

examples/web/desktop.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import numpy as np
2+
import cv2
3+
from document import Scanner
4+
5+
cap = cv2.VideoCapture(0)
6+
scanner = Scanner()
7+
8+
while (cap.isOpened()):
9+
10+
ret, frame = cap.read()
11+
video_frame = None
12+
image_frame = None
13+
14+
if cv2.waitKey(10) & 0xFF == ord('q'):
15+
break
16+
17+
if ret:
18+
if cv2.waitKey(10) & 0xFF == ord('p'):
19+
video_frame, image_frame = scanner.detect_edge(frame, True)
20+
else:
21+
video_frame, _ = scanner.detect_edge(frame)
22+
23+
if video_frame is not None:
24+
cv2.imshow("Edge Detection", video_frame)
25+
26+
if image_frame is not None:
27+
cv2.imshow("Rectified Document", image_frame)
28+
29+
# Release everything if job is finished
30+
cap.release()
31+
cv2.destroyAllWindows()

examples/web/document.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import cv2
2+
import numpy as np
3+
import docscanner
4+
5+
docscanner.initLicense(
6+
"DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==")
7+
8+
9+
class Scanner(object):
10+
def __init__(self):
11+
self.scanner = docscanner.createInstance()
12+
self.scanner.setParameters(docscanner.Templates.color)
13+
14+
def __del__(self):
15+
pass
16+
17+
def detect_edge(self, image, enabled_transform=False):
18+
results = self.scanner.detectMat(image)
19+
normalized_image = None
20+
for result in results:
21+
x1 = result.x1
22+
y1 = result.y1
23+
x2 = result.x2
24+
y2 = result.y2
25+
x3 = result.x3
26+
y3 = result.y3
27+
x4 = result.x4
28+
y4 = result.y4
29+
30+
cv2.drawContours(
31+
image, [np.intp([(x1, y1), (x2, y2), (x3, y3), (x4, y4)])], 0, (0, 255, 0), 2)
32+
33+
if enabled_transform:
34+
normalized_image = self.scanner.normalizeBuffer(
35+
image, x1, y1, x2, y2, x3, y3, x4, y4)
36+
normalized_image = docscanner.convertNormalizedImage2Mat(
37+
normalized_image)
38+
break
39+
40+
return image, normalized_image

examples/web/requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
flask
2+
document-scanner-sdk
3+
opencv-python

examples/web/server.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from flask import Flask, render_template, Response, jsonify, request
2+
from camera import VideoCamera
3+
4+
app = Flask(__name__)
5+
6+
video_camera = None
7+
8+
@app.route('/')
9+
def index():
10+
return render_template('index.html')
11+
12+
@app.route('/capture_status', methods=['POST'])
13+
def capture_status():
14+
global video_camera
15+
16+
if video_camera == None:
17+
video_camera = VideoCamera()
18+
19+
json = request.get_json()
20+
21+
status = json['status']
22+
23+
if status == "true":
24+
video_camera.capture_frame()
25+
return jsonify(result="done")
26+
27+
def video_frame():
28+
global video_camera
29+
30+
if video_camera == None:
31+
video_camera = VideoCamera()
32+
33+
while True:
34+
frame = video_camera.get_video_frame()
35+
36+
if frame is not None:
37+
yield (b'--frame\r\n'
38+
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
39+
else:
40+
yield (b'--frame\r\n'
41+
b'Content-Type: image/jpeg\r\n\r\n' + video_camera.get_cached_frame() + b'\r\n\r\n')
42+
43+
def image_frame():
44+
global video_camera
45+
46+
if video_camera == None:
47+
video_camera = VideoCamera()
48+
49+
frame = video_camera.get_image_frame()
50+
51+
if frame is not None:
52+
yield (b'--frame\r\n'
53+
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
54+
55+
@app.route('/video_viewer')
56+
def video_viewer():
57+
return Response(video_frame(),
58+
mimetype='multipart/x-mixed-replace; boundary=frame')
59+
60+
@app.route('/image_viewer')
61+
def image_viewer():
62+
return Response(image_frame(),
63+
mimetype='multipart/x-mixed-replace; boundary=frame')
64+
65+
if __name__ == '__main__':
66+
app.run(host='0.0.0.0', threaded=True)

examples/web/static/controller.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
var buttonCapture = document.getElementById("capture");
2+
3+
buttonCapture.onclick = function() {
4+
// XMLHttpRequest
5+
var xhr = new XMLHttpRequest();
6+
xhr.onreadystatechange = function() {
7+
if (xhr.readyState == 4 && xhr.status == 200) {
8+
// alert(xhr.responseText);
9+
var image = document.getElementById("image");
10+
image.src = "/image_viewer?" + new Date().getTime();
11+
}
12+
}
13+
xhr.open("POST", "/capture_status");
14+
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
15+
xhr.send(JSON.stringify({ status: "true" }));
16+
};
17+

examples/web/templates/index.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Document Scanner</title>
5+
</head>
6+
<body>
7+
<h1>Document Edge Detection and Perspective Transformation</h1>
8+
<div id="controller">
9+
<button id="capture">Capture</button>
10+
<script type="text/javascript" src="{{ url_for('static', filename='controller.js') }}"></script>
11+
</div>
12+
<img id="video" src="{{ url_for('video_viewer') }}" width="640" height="480">
13+
<img id="image" style="max-width:640px; max-height:480px">
14+
</body>
15+
</html>

0 commit comments

Comments
 (0)