Spaces:
Running
Running
added files
Browse files- Corriger.py +152 -0
- Dockerfile +17 -0
- README.md +27 -10
- code/__init__.py +0 -0
- code/__pycache__/__init__.cpython-39.pyc +0 -0
- code/__pycache__/functions.cpython-39.pyc +0 -0
- code/functions.py +145 -0
- requirements.txt +8 -0
- seguinmoreau.png +0 -0
Corriger.py
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from code.functions import pipeline_svg
|
3 |
+
from PIL import Image
|
4 |
+
import cv2
|
5 |
+
import numpy as np
|
6 |
+
from io import BytesIO
|
7 |
+
import copy
|
8 |
+
|
9 |
+
inch_value = 2.54
|
10 |
+
|
11 |
+
logo = Image.open("seguinmoreau.png")
|
12 |
+
st.set_page_config(
|
13 |
+
page_title="Moulinette Logos",
|
14 |
+
page_icon=logo,
|
15 |
+
layout="wide",
|
16 |
+
initial_sidebar_state="expanded"
|
17 |
+
)
|
18 |
+
|
19 |
+
st.markdown(
|
20 |
+
"""
|
21 |
+
# Boîte à Outils de correction de logos :wrench:
|
22 |
+
|
23 |
+
Bienvenue dans la boîte à outils de correction de logos de Seguin Moreau.
|
24 |
+
|
25 |
+
### :hammer: Les outils
|
26 |
+
Dans cette boîte à outils, vous trouverez:
|
27 |
+
* Un outil de Correction automatique de logo (enlever les petits défauts, lissage, vectorisation, grossissement des traits trop fins.
|
28 |
+
* Un outil de Vectorisation (image en pixels => image vectorisée => image en pixels).
|
29 |
+
|
30 |
+
### :bulb: Mode d'emploi
|
31 |
+
* Cliquer sur 'Browse files'
|
32 |
+
* Sélectionner un logo
|
33 |
+
* La correction est automatique. Si la correction ne vous convient pas, il est possible de régler les paramètres en cliquant sur 'Paramétrage' à droite de l'image.
|
34 |
+
* Les deux paramètres permettent de corriger les défauts liés à la présence de gris sur le logo ou la 'pixélisation' du logo trop importante.
|
35 |
+
|
36 |
+
"""
|
37 |
+
)
|
38 |
+
|
39 |
+
logo = Image.open('seguinmoreau.png')
|
40 |
+
st.image(logo, width=100)
|
41 |
+
|
42 |
+
uploaded_files = st.file_uploader("Choisir un logo", accept_multiple_files=True)
|
43 |
+
|
44 |
+
image_width = 500
|
45 |
+
size_value = st.slider("Largeur de trait minimum", min_value=1, max_value=21, value=7, step=2)
|
46 |
+
|
47 |
+
size_value = (size_value - 1) // 2
|
48 |
+
|
49 |
+
#kernel_type_str = st.selectbox("Kernel type", ["Ellipse", "Rectangle", "Cross"])
|
50 |
+
kernel_type_str = "Ellipse"
|
51 |
+
dict_kernel_type = {"Ellipse": cv2.MORPH_ELLIPSE, "Rectangle": cv2.MORPH_RECT, "Cross": cv2.MORPH_CROSS}
|
52 |
+
kernel_type = dict_kernel_type[kernel_type_str]
|
53 |
+
|
54 |
+
for uploaded_file in uploaded_files:
|
55 |
+
col1, col2, col3 = st.columns([1, 1, 1])
|
56 |
+
col3.markdown("---")
|
57 |
+
|
58 |
+
image = Image.open(uploaded_file).convert('L')
|
59 |
+
image_input = np.array(image)
|
60 |
+
image = copy.deepcopy(image_input)
|
61 |
+
col1.image(image_input/255.0, caption="Image d'entrée", use_column_width='auto')
|
62 |
+
|
63 |
+
with col3:
|
64 |
+
with st.expander(":gear: Paramétrage"):
|
65 |
+
st.write("Si l'image contient du gris, faire varier le seuil ci-dessous:")
|
66 |
+
threshold = st.slider("Seuil pour convertir l'image en noir&blanc.", min_value=0, max_value=255, value=0,
|
67 |
+
step=1, key=f"{uploaded_file}_slider_threshold")
|
68 |
+
st.write("Si l'image est pixelisée, ou contient trop de détails, "
|
69 |
+
"augmenter la valeur ci-dessous:")
|
70 |
+
blur_value = st.slider("Seuil pour lisser l'image", min_value=1, max_value=11, value=1, step=2,
|
71 |
+
key=f"{uploaded_file}_slider_gaussian_sigma")
|
72 |
+
st.write("Si l'image contient des traits très fin (de l'odre du pixel),"
|
73 |
+
" augmenter le seuil ci-dessous, de 1 par 1:")
|
74 |
+
dilate_lines_value = st.slider("Dilatation de l'image d'origine: (en pixels)", min_value=0, max_value=5, value=0, step=1,key=f"{uploaded_file}_slider_dilation_image")
|
75 |
+
|
76 |
+
st.write("Taille d'exportation d'image:")
|
77 |
+
|
78 |
+
dpi_value = st.number_input("Valeur dpi:", key=f"{uploaded_file}_number_dpi_value", value=200)
|
79 |
+
side_width_value = st.number_input("Taille max de côté cible (cm):", key=f"{uploaded_file}_number_target_value", value=20)
|
80 |
+
new_largest_side_value = int(side_width_value / inch_value * dpi_value)
|
81 |
+
|
82 |
+
h, w, *_ = image.shape
|
83 |
+
|
84 |
+
# Resize image
|
85 |
+
ratio = w / h
|
86 |
+
if ratio > 1:
|
87 |
+
width = new_largest_side_value
|
88 |
+
height = int(new_largest_side_value / ratio)
|
89 |
+
else:
|
90 |
+
height = new_largest_side_value
|
91 |
+
width = int(ratio * new_largest_side_value)
|
92 |
+
|
93 |
+
target_width_value = st.number_input("Largeur cible (cm):", key=f"{uploaded_file}_number_width_value", value=0)
|
94 |
+
target_height_value = st.number_input("Hauteur cible (cm):", key=f"{uploaded_file}_number_height_value", value=0)
|
95 |
+
|
96 |
+
if target_width_value > 0 and target_height_value == 0:
|
97 |
+
width = int(target_width_value / inch_value * dpi_value)
|
98 |
+
height = int(width / ratio)
|
99 |
+
elif target_height_value > 0 and target_width_value == 0:
|
100 |
+
height = int(target_height_value / inch_value * dpi_value)
|
101 |
+
width = int(height * ratio)
|
102 |
+
elif target_height_value > 0 and target_width_value > 0:
|
103 |
+
st.warning("Vous ne pouvez pas modifier la largeur et la hauteur simultanément.")
|
104 |
+
|
105 |
+
if threshold > 0:
|
106 |
+
image = (image > threshold)*255
|
107 |
+
image = image.astype('uint8')
|
108 |
+
|
109 |
+
if blur_value > 0:
|
110 |
+
image = cv2.GaussianBlur(image, (blur_value, blur_value), blur_value - 1)
|
111 |
+
|
112 |
+
# Process image cv32f ==> cv32f
|
113 |
+
img_final = pipeline_svg(image, size_value=size_value, level=1, threshold=threshold, kernel_type=kernel_type, dilate_lines_value=dilate_lines_value)
|
114 |
+
|
115 |
+
col2.image(img_final, caption="Image corrigée", use_column_width='auto')
|
116 |
+
|
117 |
+
# Check for grayscale
|
118 |
+
tolerance = 10
|
119 |
+
ratio_of_gray_pixels = int(np.sum((tolerance < image)* (image < 255 - tolerance))/np.size(image)*100)
|
120 |
+
if ratio_of_gray_pixels > 1:
|
121 |
+
col3.warning(f":warning: Le nombre de pixels gris est élevé: {ratio_of_gray_pixels} % > 1%")
|
122 |
+
|
123 |
+
# Check reconstruction fidelity
|
124 |
+
distance = np.mean((np.array(image) - img_final)**2)
|
125 |
+
if distance > 10:
|
126 |
+
col3.warning(f":warning: Le logo est peut-être trop dégradé (MSE={distance:.2f} > 10).\nVérifier visuellement.")
|
127 |
+
|
128 |
+
|
129 |
+
|
130 |
+
dim = (width, height)
|
131 |
+
# resize image
|
132 |
+
resized_img_final = cv2.resize(img_final, dim, interpolation=cv2.INTER_AREA)
|
133 |
+
resized_image_input = cv2.resize(image_input, dim, interpolation=cv2.INTER_AREA)
|
134 |
+
|
135 |
+
buf = BytesIO()
|
136 |
+
img_stacked = np.hstack((resized_image_input, resized_img_final))
|
137 |
+
img_final = Image.fromarray(img_stacked).convert("L")
|
138 |
+
img_final.save(buf, format="PNG")
|
139 |
+
byte_im= buf.getvalue()
|
140 |
+
|
141 |
+
btn = col3.download_button(
|
142 |
+
label=":inbox_tray: Télécharger l'image",
|
143 |
+
data=byte_im,
|
144 |
+
file_name=f"corrected_{uploaded_file.name}",
|
145 |
+
mime="image/png"
|
146 |
+
)
|
147 |
+
|
148 |
+
|
149 |
+
|
150 |
+
|
151 |
+
|
152 |
+
|
Dockerfile
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9-slim
|
2 |
+
|
3 |
+
EXPOSE 7860
|
4 |
+
|
5 |
+
RUN apt-get update && apt-get install -y \
|
6 |
+
build-essential \
|
7 |
+
software-properties-common \
|
8 |
+
git \
|
9 |
+
&& rm -rf /var/lib/apt/lists/*
|
10 |
+
|
11 |
+
WORKDIR /moulinette
|
12 |
+
COPY . .
|
13 |
+
RUN echo $(ls -1 .. )
|
14 |
+
|
15 |
+
RUN pip3 install -r requirements.txt
|
16 |
+
|
17 |
+
ENTRYPOINT ["streamlit", "run", "Corriger.py", "--server.port=7860"]
|
README.md
CHANGED
@@ -1,10 +1,27 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# moulinette
|
2 |
+
moulinette
|
3 |
+
|
4 |
+
|
5 |
+
#Command line svg to png
|
6 |
+
inkscape -h 1468 -w 1576 imagea10.svg -o output.png --export-background-opacity=255
|
7 |
+
|
8 |
+
# command line png to svg
|
9 |
+
pip install potrace-cli
|
10 |
+
potracer ../SMoreau/images/flower.png -b svg -o image.png
|
11 |
+
|
12 |
+
# Or image j
|
13 |
+
convert image10.svg -colorspace Gray output.png
|
14 |
+
|
15 |
+
# Put to aws
|
16 |
+
|
17 |
+
Follow
|
18 |
+
https://itnext.io/run-your-containers-on-aws-fargate-c2d4f6a47fda
|
19 |
+
Permission denied ==> use sudo.
|
20 |
+
|
21 |
+
1/ build docker
|
22 |
+
2/ push docker image on aws ECR (elastic container registery)
|
23 |
+
|
24 |
+
Error in building the docker use
|
25 |
+
https://stackoverflow.com/questions/72564830/python3-minimal-error-during-pip-installation-in-docker-build-permissionerro
|
26 |
+
|
27 |
+
Il faut absolument ne pas être sudo pour docker !
|
code/__init__.py
ADDED
File without changes
|
code/__pycache__/__init__.cpython-39.pyc
ADDED
Binary file (135 Bytes). View file
|
|
code/__pycache__/functions.cpython-39.pyc
ADDED
Binary file (4.52 kB). View file
|
|
code/functions.py
ADDED
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
import numpy as np
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
import os
|
5 |
+
import cairosvg
|
6 |
+
from potrace import POTRACE_CORNER, Path, Bitmap
|
7 |
+
import io
|
8 |
+
from PIL import Image, ImageStat
|
9 |
+
|
10 |
+
import streamlit
|
11 |
+
from PIL import Image
|
12 |
+
|
13 |
+
@streamlit.cache_data
|
14 |
+
def pipeline_svg(image_input, size_value, level=3, streamlit=False, threshold=0, kernel_type=cv2.MORPH_ELLIPSE, dilate_lines_value=0):
|
15 |
+
"""
|
16 |
+
uint8 ==> uint8
|
17 |
+
|
18 |
+
Args:
|
19 |
+
streamlit:
|
20 |
+
size_value:
|
21 |
+
image_input:
|
22 |
+
|
23 |
+
Returns:
|
24 |
+
|
25 |
+
"""
|
26 |
+
|
27 |
+
# Process image
|
28 |
+
image_processed = process_svg(image_input, size_value=size_value, streamlit=streamlit, kernel_type=kernel_type, dilate_lines_value=dilate_lines_value)
|
29 |
+
|
30 |
+
return image_processed
|
31 |
+
|
32 |
+
def process_svg(img, size_value=12, level=1, streamlit=False, kernel_type=cv2.MORPH_ELLIPSE, dilate_lines_value=0):
|
33 |
+
|
34 |
+
image_path = "input_image.png"
|
35 |
+
img = img.astype('uint8')
|
36 |
+
|
37 |
+
# Lines very small
|
38 |
+
if dilate_lines_value > 0:
|
39 |
+
size = dilate_lines_value + 1 # No sens to dilate by one pixel (doesn't do anything).
|
40 |
+
kernel = get_kernel_ellipse(size, kernel_type=kernel_type)
|
41 |
+
img = cv2.erode(img, kernel, iterations=1)
|
42 |
+
|
43 |
+
cv2.imwrite(image_path, img)
|
44 |
+
img_array = convert_to_svg_and_back(img)
|
45 |
+
|
46 |
+
img_array = binarise(img_array)
|
47 |
+
img_bin = (255 - img_array)
|
48 |
+
img_bin = img_bin.astype('uint8')
|
49 |
+
image_already_added = np.zeros_like(img_bin)
|
50 |
+
|
51 |
+
target_min_size = max(1, size_value)
|
52 |
+
|
53 |
+
image_final = img_bin
|
54 |
+
for i in range(target_min_size+1):
|
55 |
+
size = 2 * i + 1
|
56 |
+
kernel = get_kernel_ellipse(size, kernel_type=kernel_type)
|
57 |
+
|
58 |
+
erosion = cv2.erode((img_bin - image_already_added), kernel, iterations=1)
|
59 |
+
dilation = cv2.dilate(erosion, kernel, iterations=1)
|
60 |
+
|
61 |
+
image_petits_objets = (img_bin - dilation)
|
62 |
+
image_petits_objets = remove_solo_pixels(image_petits_objets, kernel_size=3)
|
63 |
+
|
64 |
+
size = 2 * (target_min_size - i) + 1
|
65 |
+
kernel = get_kernel_ellipse(size, kernel_type=kernel_type)
|
66 |
+
dilate_image_petits_objets = cv2.dilate(image_petits_objets, kernel, iterations=1)
|
67 |
+
|
68 |
+
image_already_added = (image_already_added + image_petits_objets)
|
69 |
+
|
70 |
+
if i > level:
|
71 |
+
image_final = (image_final + dilate_image_petits_objets)
|
72 |
+
|
73 |
+
cv2.imwrite("image_finale.png", (255 - image_final))
|
74 |
+
image = convert_to_svg_and_back((255 - image_final))
|
75 |
+
return image
|
76 |
+
def get_kernel_ellipse(size, kernel_type=cv2.MORPH_ELLIPSE):
|
77 |
+
list_coords = [size, size]
|
78 |
+
return cv2.getStructuringElement(kernel_type, (list_coords[0], list_coords[1]),
|
79 |
+
(int((list_coords[0] - 1) / 2), int((list_coords[1] - 1) / 2)))
|
80 |
+
|
81 |
+
|
82 |
+
def binarise(img):
|
83 |
+
img = img > 200
|
84 |
+
img = img * 255
|
85 |
+
img = img.astype('uint8')
|
86 |
+
return img
|
87 |
+
|
88 |
+
|
89 |
+
def imshow(title, image, vmin=0, vmax=1):
|
90 |
+
plt.figure()
|
91 |
+
plt.title(title)
|
92 |
+
plt.imshow(image * 255, vmin=vmin * 255, vmax=vmax * 255, cmap='gray')
|
93 |
+
|
94 |
+
|
95 |
+
def remove_solo_pixels(image, kernel_size=3):
|
96 |
+
kernel = get_kernel_ellipse(kernel_size)
|
97 |
+
|
98 |
+
erosion = cv2.erode(image, kernel, iterations=1)
|
99 |
+
dilation = cv2.dilate(erosion, kernel, iterations=1)
|
100 |
+
|
101 |
+
dilation = dilation.astype('uint8')
|
102 |
+
return dilation
|
103 |
+
|
104 |
+
def convert_to_svg_and_back(image_array) -> np.array:
|
105 |
+
image_pil = Image.fromarray(image_array)
|
106 |
+
|
107 |
+
bm = Bitmap(image_pil, blacklevel=0.5)
|
108 |
+
|
109 |
+
plist = bm.trace(
|
110 |
+
turdsize=2,
|
111 |
+
turnpolicy=4,
|
112 |
+
alphamax=1,
|
113 |
+
opticurve= False,
|
114 |
+
opttolerance=0.2)
|
115 |
+
|
116 |
+
image = backend_svg_no_file(image_pil, plist)
|
117 |
+
|
118 |
+
return np.array(image)
|
119 |
+
|
120 |
+
def backend_svg_no_file(image, path: Path):
|
121 |
+
output = f'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{image.width}" height="{image.height}" viewBox="0 0 {image.width} {image.height}">'
|
122 |
+
|
123 |
+
parts = []
|
124 |
+
for curve in path:
|
125 |
+
fs = curve.start_point
|
126 |
+
parts.append("M%f,%f" % (fs.x, fs.y))
|
127 |
+
for segment in curve.segments:
|
128 |
+
if segment.is_corner:
|
129 |
+
a = segment.c
|
130 |
+
parts.append("L%f,%f" % (a.x, a.y))
|
131 |
+
b = segment.end_point
|
132 |
+
parts.append("L%f,%f" % (b.x, b.y))
|
133 |
+
else:
|
134 |
+
a = segment.c1
|
135 |
+
b = segment.c2
|
136 |
+
c = segment.end_point
|
137 |
+
parts.append("C%f,%f %f,%f %f,%f" % (a.x, a.y, b.x, b.y, c.x, c.y))
|
138 |
+
parts.append("z")
|
139 |
+
output += f'<path stroke="none" fill="#000000" fill-rule="evenodd" d="{"".join(parts)}"/>'
|
140 |
+
|
141 |
+
output += "</svg>"
|
142 |
+
# From svg to png (bytes)
|
143 |
+
image_data = cairosvg.surface.PNGSurface.convert(output)
|
144 |
+
image = Image.open(io.BytesIO(image_data)).split()[-1]
|
145 |
+
return image
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
matplotlib==3.4.1
|
2 |
+
numpy==1.20.2
|
3 |
+
opencv_python_headless==4.5.5.64
|
4 |
+
Pillow==9.4.0
|
5 |
+
scipy==1.6.2
|
6 |
+
streamlit==1.20.0
|
7 |
+
potracer==0.0.4
|
8 |
+
cairosvg==2.7.0
|
seguinmoreau.png
ADDED