File size: 8,749 Bytes
d1df3f4
5e568dc
1c48bcd
d1df3f4
 
 
1c48bcd
5e568dc
d1df3f4
 
 
 
1c48bcd
d1df3f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88f1d1e
d1df3f4
 
 
 
 
 
 
e310885
d1df3f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import glob
import os

import cv2
import gradio as gr


def center_crop_mod64(img):
	"""Crop image to largest area that's divisible by 64"""
	h, w	= img.shape[:2]
	shortside, longside	= min(w, h), max(w, h)
	longside_crop	= (longside // 64) * 64

	left	= (w - longside_crop) // 2	if w > h else 0
	top	= (h - longside_crop) // 2	if h > w else 0
	right	= left + longside_crop	if w > h else shortside
	bottom	= top  + longside_crop	if h > w else shortside

	return img[top:bottom, left:right]

def load_example_images():
	"""Load all images from images/ directory"""
	image_dir = os.path.join(os.path.dirname(__file__), "images")
	if not os.path.exists(image_dir):
		return []

	extensions = ['*.png', '*.jpg', '*.jpeg', '*.webp']
	examples = []
	for ext in extensions:
		examples.extend(glob.glob(os.path.join(image_dir, ext)))
		examples.extend(glob.glob(os.path.join(image_dir, ext.upper())))

	return sorted(examples)

def apply_edge_detection(image_rgb, algorithm, apply_crop, convert2grayscale, params):
	"""Apply selected edge detection algorithm with specified parameters"""

	if image_rgb is None:
		return None

	# Apply cropping if requested
	if apply_crop:
		img_processed = center_crop_mod64(image_rgb)
	else:
		img_processed = image_rgb.copy()

	# Convert to grayscale if requested
	if convert2grayscale and len(img_processed.shape) == 3:
		img_input = cv2.cvtColor(cv2.cvtColor(img_processed, cv2.COLOR_RGB2GRAY), cv2.COLOR_GRAY2RGB)
	else:
		img_input = img_processed
	ret = img_input.copy()

	# set parameters


	# Detect edges
	ed = cv2.ximgproc.createEdgeDrawing()
	ed.setParams(params)
	ed.detectEdges(img_input)

	if algorithm == "Edges":
		ret = ed.getEdgeImage()
		return ret

	elif algorithm == "Lines":
		lines = ed.detectLines()

		# draw lines
		if lines is not None:
			for line in lines:
				x1, y1, x2, y2 = map(int, line[0])
				cv2.line(ret, (x1, y1), (x2, y2), (0, 255, 0), 1)

		return ret

	elif algorithm == "Ellipses":
		ellipsess = ed.detectEllipses()

		# draw ellipses
		if ellipsess is not None:
			for ellipses in ellipsess:
				ellipse = ellipses[0]
				center = (int(ellipse[0]), int(ellipse[1]))
				if ellipse[2] == 0: # Ellipse
					axes = (int(ellipse[3]), int(ellipse[4]))
					angle = ellipse[5]
					cv2.ellipse(ret, center, axes, angle, 0, 360, (0, 255, 0), 1)
				else: # Circle
					radius = int(ellipse[2])
					cv2.circle(ret, center, radius, (0, 255, 0), 1)

		return ret

	return img_input

# Create Gradio interface
with gr.Blocks(title="Edge Drawing") as app:
	examples = load_example_images()
	gr.Markdown("# Enhanced Edge Detection using [Edge Drawing](https://github.com/CihanTopal/ED_Lib) with [opencv-contrib-python](https://docs.opencv.org/4.x/d1/d1c/classcv_1_1ximgproc_1_1EdgeDrawing.html)")

	# Image row - Input and Output side by side
	with gr.Row():
		input_image = gr.Image(value=examples[0] if examples else None , label="Input Image" , type="numpy")
		output_image = gr.Image(value=None , label="Detection Result" , type="numpy")

	# Controls row - Processing Options on left, Parameters on right
	with gr.Row():
		# Processing Options
		with gr.Column():
			with gr.Group():
				gr.Markdown("### Processing Options")
				algorithm_radio	= gr.Radio(label="Detection mode", value="Edges", choices=["Edges", "Lines", "Ellipses"])

				paramterfree_checkbox	= gr.Checkbox(label	="Parameter Free Mode"	, value=True	, info="Auto-determine optimal parameters")
				convert2grayscale_checkbox	= gr.Checkbox(label	="Convert to Grayscale"	, value=False	, info="Force conversion to grayscale prior to edge detection (note that Edge Drawing has native support for color images)")
				crop_checkbox	= gr.Checkbox(label	="Apply center crop mod-64"	, value=False	, info="Crop to largest area divisible by 64 (used for Stable Diffusion)")

		# EdgeDrawing Parameters
		with gr.Column():
			with gr.Group():
				gr.Markdown("### Edge Drawing Parameters")

				# Parameter controls group (disabled by default)
				with gr.Group(visible=False) as param_group:
					with gr.Column():
						with gr.Row():
							gradient_operator = gr.Radio(choices=["PREWITT", "SOBEL", "SCHARR", "LSD"], value="PREWITT", label="Gradient Operator", info="indicates the operator used for gradient calculation. Default value is PREWITT")
							gradient_threshold	= gr.Slider(minimum=	1	, maximum=	100	, value=	20	, step=	1	, label="Gradient Threshold"	, scale=  1	, info="Threshold value of gradiential difference between pixels. Used to create gradient image. Default value is 20"	)

						with gr.Row():
							anchor_threshold	= gr.Slider(minimum=	0	, maximum=	255	, value=	0	, step=	1	, label="Anchor Threshold"	, scale=  1	, info="Threshold value used to select anchor points. Default value is 0"	)
							min_path_length	= gr.Slider(minimum=	1	, maximum=	50	, value=	10	, step=	1	, label="Min Path Length"	, scale=  1	, info="Minimum connected pixels length processed to create an edge segment. Default value is 10"	)

						with gr.Row():
							min_line_length	= gr.Slider(minimum=	-1	, maximum=	100	, value=	-1	, step=	1	, label="Min Line Length"	, scale=  1	, info="Minimum line length to detect. Default value is -1"	)
							line_fit_error_threshold	= gr.Slider(minimum=	0.1	, maximum=	10.0	, value=	1.0	, step=	0.1	, label="Line Fit Error Threshold"	, scale=  1	, info="Default value is 1.0"	)
							max_distance_between_two_lines	= gr.Slider(minimum=	1.0	, maximum=	20.0	, value=	6.0	, step=	0.1	, label="Max Distance Between Two Lines"	, scale=  1	, info="Default value is 6.0"	)

						with gr.Row():
							max_error_threshold	= gr.Slider(minimum=	0.1	, maximum=	5.0	, value=	1.3	, step=	0.1	, label="Max Error Threshold"	, scale=  1	, info="Default value is 1.3"	)
							scan_interval	= gr.Slider(minimum=	1	, maximum=	10	, value=	1	, step=	1	, label="Scan Interval"	, scale=  1	, info="Default value is 1"	)

						with gr.Row():
							sigma	= gr.Slider(minimum=	0.1	, maximum=	5.0	, value=	1.0	, step=	0.1	, label="Sigma"	, scale=  1	, info="Sigma value for internal GaussianBlur() function. Default value is 1.0"	)
							nfa_validation	= gr.Checkbox(label="NFA Validation"	, value=True	, info="Indicates if NFA (Number of False Alarms) algorithm will be used for line and ellipse validation. Default value is true")
							sum_flag	= gr.Checkbox(label="Sum Flag"	, value=True	, info="Default value is true")

	# Update function
	def update_output(
			image_rgb, algorithm, apply_crop, conv2grayscale, pf_mode,
			gradient_operator, anchor_threshold, gradient_threshold, min_path_length, min_line_length, line_fit_error_threshold, max_distance_between_two_lines, max_error_threshold, nfa_validation, scan_interval, sigma, sum_flag
		):
		operator_map = {
			"PREWITT"	: cv2.ximgproc.EdgeDrawing_PREWITT,
			"SOBEL"	: cv2.ximgproc.EdgeDrawing_SOBEL,
			"SCHARR"	: cv2.ximgproc.EdgeDrawing_SCHARR,
			"LSD"	: cv2.ximgproc.EdgeDrawing_LSD,
		}

		params = cv2.ximgproc.EdgeDrawing.Params()
		params.PFmode = pf_mode
		params.EdgeDetectionOperator = operator_map.get(gradient_operator, cv2.ximgproc.EdgeDrawing_PREWITT)
		if not pf_mode:
			params.AnchorThresholdValue	= anchor_threshold
			params.GradientThresholdValue	= gradient_threshold
			params.MinPathLength	= min_path_length
			params.MinLineLength	= min_line_length
			params.LineFitErrorThreshold	= line_fit_error_threshold
			params.MaxDistanceBetweenTwoLines	= max_distance_between_two_lines
			params.MaxErrorThreshold	= max_error_threshold
			params.NFAValidation	= nfa_validation
			params.ScanInterval	= scan_interval
			params.Sigma	= sigma
			params.SumFlag	= sum_flag

		ret = apply_edge_detection(image_rgb, algorithm, apply_crop, conv2grayscale, params)
		return ret

	# Function to toggle parameter group visibility
	def toggle_params(pf_mode):
		return gr.Group(visible=not pf_mode)

	# Set up event handlers
	inputs = [
		input_image,
		algorithm_radio,
		crop_checkbox,
		convert2grayscale_checkbox,
		paramterfree_checkbox,
		gradient_operator,
		anchor_threshold,
		gradient_threshold,
		min_path_length,
		min_line_length,
		line_fit_error_threshold,
		max_distance_between_two_lines,
		max_error_threshold,
		nfa_validation,
		scan_interval,
		sigma,
		sum_flag,
	]

	# Update output when any input changes
	for inp in inputs:
		inp.change(fn=update_output, inputs=inputs, outputs=output_image)

	# Toggle parameter group when parameter free mode changes
	paramterfree_checkbox.change(fn=toggle_params, inputs=paramterfree_checkbox, outputs=param_group)

	# Apply edge detection immediately on startup
	app.load(fn=update_output, inputs=inputs, outputs=output_image)

	# Examples
	if examples:
		gr.Examples(examples=examples, inputs=input_image, label="Example Images" )

if __name__ == "__main__":
	app.launch()