Commit
·
68c5e47
0
Parent(s):
Added dexined quantized model for edge detection (#272)
Browse files* Added dexined.onnx file
* Added sample, license, example outputs
* Added a seperate wrapper class for supporting functions
* Shifted to Tickmeter, and renamed files to demo.cpp and demo.py
- CMakeLists.txt +11 -0
- LICENSE +21 -0
- README.md +55 -0
- demo.cpp +138 -0
- demo.py +51 -0
- dexined.py +50 -0
CMakeLists.txt
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
cmake_minimum_required(VERSION 3.22.2)
|
2 |
+
project(opencv_zoo_edge_detection_dexined)
|
3 |
+
|
4 |
+
set(OPENCV_VERSION "5.0.0")
|
5 |
+
set(OPENCV_INSTALLATION_PATH "" CACHE PATH "Where to look for OpenCV installation")
|
6 |
+
|
7 |
+
# Find OpenCV
|
8 |
+
find_package(OpenCV ${OPENCV_VERSION} REQUIRED HINTS ${OPENCV_INSTALLATION_PATH})
|
9 |
+
|
10 |
+
add_executable(edge_detection edge_detection.cpp)
|
11 |
+
target_link_libraries(edge_detection ${OpenCV_LIBS})
|
LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2019 Xavier Soria Poma
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files (the "Software"), to deal
|
7 |
+
in the Software without restriction, including without limitation the rights
|
8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9 |
+
copies of the Software, and to permit persons to whom the Software is
|
10 |
+
furnished to do so, subject to the following conditions:
|
11 |
+
|
12 |
+
The above copyright notice and this permission notice shall be included in all
|
13 |
+
copies or substantial portions of the Software.
|
14 |
+
|
15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21 |
+
SOFTWARE.
|
README.md
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# DexiNed
|
2 |
+
|
3 |
+
DexiNed is a Convolutional Neural Network (CNN) architecture for edge detection.
|
4 |
+
|
5 |
+
Notes:
|
6 |
+
|
7 |
+
- Model source: [ONNX](https://drive.google.com/file/d/1u_qXqXqaIP_SqdGaq4CbZyjzkZb02XTs/view).
|
8 |
+
- Model source: [.pth](https://drive.google.com/file/d/1V56vGTsu7GYiQouCIKvTWl5UKCZ6yCNu/view).
|
9 |
+
- This ONNX model has fixed input shape, but OpenCV DNN infers on the exact shape of input image. See https://github.com/opencv/opencv_zoo/issues/44 for more information.
|
10 |
+
|
11 |
+
## Requirements
|
12 |
+
Install latest OpenCV >=5.0.0 and CMake >= 3.22.2 to get started with.
|
13 |
+
|
14 |
+
## Demo
|
15 |
+
|
16 |
+
### Python
|
17 |
+
|
18 |
+
Run the following command to try the demo:
|
19 |
+
|
20 |
+
```shell
|
21 |
+
# detect on camera input
|
22 |
+
python demo.py
|
23 |
+
# detect on an image
|
24 |
+
python demo.py --input /path/to/image
|
25 |
+
|
26 |
+
# get help regarding various parameters
|
27 |
+
python demo.py --help
|
28 |
+
```
|
29 |
+
|
30 |
+
### C++
|
31 |
+
|
32 |
+
```shell
|
33 |
+
# A typical and default installation path of OpenCV is /usr/local
|
34 |
+
cmake -B build -D OPENCV_INSTALLATION_PATH=/path/to/opencv/installation .
|
35 |
+
cmake --build build
|
36 |
+
|
37 |
+
# detect on camera input
|
38 |
+
./build/demo
|
39 |
+
# detect on an image
|
40 |
+
./build/demo --input=/path/to/image
|
41 |
+
# get help messages
|
42 |
+
./build/demo -h
|
43 |
+
```
|
44 |
+
|
45 |
+
### Example outputs
|
46 |
+
|
47 |
+

|
48 |
+
|
49 |
+
## License
|
50 |
+
|
51 |
+
All files in this directory are licensed under [MIT License](./LICENSE).
|
52 |
+
|
53 |
+
## Reference
|
54 |
+
|
55 |
+
- https://github.com/xavysp/DexiNed
|
demo.cpp
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#include <opencv2/dnn.hpp>
|
2 |
+
#include <opencv2/imgproc.hpp>
|
3 |
+
#include <opencv2/highgui.hpp>
|
4 |
+
#include <iostream>
|
5 |
+
#include <string>
|
6 |
+
#include <cmath>
|
7 |
+
#include <vector>
|
8 |
+
|
9 |
+
using namespace cv;
|
10 |
+
using namespace cv::dnn;
|
11 |
+
using namespace std;
|
12 |
+
|
13 |
+
class Dexined {
|
14 |
+
public:
|
15 |
+
Dexined(const string& modelPath) {
|
16 |
+
loadModel(modelPath);
|
17 |
+
}
|
18 |
+
|
19 |
+
// Function to set up the input image and process it
|
20 |
+
void processFrame(const Mat& image, Mat& result) {
|
21 |
+
Mat blob = blobFromImage(image, 1.0, Size(512, 512), Scalar(103.5, 116.2, 123.6), false, false, CV_32F);
|
22 |
+
net.setInput(blob);
|
23 |
+
applyDexined(image, result);
|
24 |
+
}
|
25 |
+
|
26 |
+
private:
|
27 |
+
Net net;
|
28 |
+
|
29 |
+
// Load Model
|
30 |
+
void loadModel(const string modelPath) {
|
31 |
+
net = readNetFromONNX(modelPath);
|
32 |
+
net.setPreferableBackend(DNN_BACKEND_DEFAULT);
|
33 |
+
net.setPreferableTarget(DNN_TARGET_CPU);
|
34 |
+
}
|
35 |
+
|
36 |
+
// Function to apply sigmoid activation
|
37 |
+
static void sigmoid(Mat& input) {
|
38 |
+
exp(-input, input); // e^-input
|
39 |
+
input = 1.0 / (1.0 + input); // 1 / (1 + e^-input)
|
40 |
+
}
|
41 |
+
|
42 |
+
// Function to process the neural network output to generate edge maps
|
43 |
+
static pair<Mat, Mat> postProcess(const vector<Mat>& output, int height, int width) {
|
44 |
+
vector<Mat> preds;
|
45 |
+
preds.reserve(output.size());
|
46 |
+
for (const Mat &p : output) {
|
47 |
+
Mat img;
|
48 |
+
Mat processed;
|
49 |
+
if (p.dims == 4 && p.size[0] == 1 && p.size[1] == 1) {
|
50 |
+
processed = p.reshape(0, {p.size[2], p.size[3]});
|
51 |
+
} else {
|
52 |
+
processed = p.clone();
|
53 |
+
}
|
54 |
+
sigmoid(processed);
|
55 |
+
normalize(processed, img, 0, 255, NORM_MINMAX, CV_8U);
|
56 |
+
resize(img, img, Size(width, height));
|
57 |
+
preds.push_back(img);
|
58 |
+
}
|
59 |
+
Mat fuse = preds.back();
|
60 |
+
Mat ave = Mat::zeros(height, width, CV_32F);
|
61 |
+
for (Mat &pred : preds) {
|
62 |
+
Mat temp;
|
63 |
+
pred.convertTo(temp, CV_32F);
|
64 |
+
ave += temp;
|
65 |
+
}
|
66 |
+
ave /= static_cast<float>(preds.size());
|
67 |
+
ave.convertTo(ave, CV_8U);
|
68 |
+
return {fuse, ave};
|
69 |
+
}
|
70 |
+
|
71 |
+
// Function to apply the Dexined model
|
72 |
+
void applyDexined(const Mat& image, Mat& result) {
|
73 |
+
int originalWidth = image.cols;
|
74 |
+
int originalHeight = image.rows;
|
75 |
+
vector<Mat> outputs;
|
76 |
+
net.forward(outputs);
|
77 |
+
pair<Mat, Mat> res = postProcess(outputs, originalHeight, originalWidth);
|
78 |
+
result = res.first; // or res.second for average edge map
|
79 |
+
}
|
80 |
+
};
|
81 |
+
|
82 |
+
int main(int argc, char** argv) {
|
83 |
+
const string about =
|
84 |
+
"This sample demonstrates edge detection with dexined edge detection techniques.\n\n";
|
85 |
+
const string keys =
|
86 |
+
"{ help h | | Print help message. }"
|
87 |
+
"{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera.}"
|
88 |
+
"{ model | edge_detection_dexined_2024sep.onnx | Path to the dexined.onnx model file }";
|
89 |
+
|
90 |
+
CommandLineParser parser(argc, argv, keys);
|
91 |
+
if (parser.has("help"))
|
92 |
+
{
|
93 |
+
cout << about << endl;
|
94 |
+
parser.printMessage();
|
95 |
+
return -1;
|
96 |
+
}
|
97 |
+
|
98 |
+
parser = CommandLineParser(argc, argv, keys);
|
99 |
+
string model = parser.get<String>("model");
|
100 |
+
parser.about(about);
|
101 |
+
|
102 |
+
VideoCapture cap;
|
103 |
+
if (parser.has("input"))
|
104 |
+
cap.open(samples::findFile(parser.get<String>("input")));
|
105 |
+
else
|
106 |
+
cap.open(0);
|
107 |
+
|
108 |
+
namedWindow("Input", WINDOW_AUTOSIZE);
|
109 |
+
namedWindow("Output", WINDOW_AUTOSIZE);
|
110 |
+
moveWindow("Output", 200, 0);
|
111 |
+
|
112 |
+
// Create an instance of Dexined
|
113 |
+
Dexined dexined(model);
|
114 |
+
Mat image;
|
115 |
+
|
116 |
+
for (;;){
|
117 |
+
cap >> image;
|
118 |
+
if (image.empty())
|
119 |
+
{
|
120 |
+
cout << "Press any key to exit" << endl;
|
121 |
+
waitKey();
|
122 |
+
break;
|
123 |
+
}
|
124 |
+
|
125 |
+
Mat result;
|
126 |
+
dexined.processFrame(image, result);
|
127 |
+
|
128 |
+
imshow("Input", image);
|
129 |
+
imshow("Output", result);
|
130 |
+
int key = waitKey(1);
|
131 |
+
if (key == 27 || key == 'q')
|
132 |
+
{
|
133 |
+
break;
|
134 |
+
}
|
135 |
+
}
|
136 |
+
destroyAllWindows();
|
137 |
+
return 0;
|
138 |
+
}
|
demo.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2 as cv
|
2 |
+
import argparse
|
3 |
+
from dexined import Dexined
|
4 |
+
|
5 |
+
def get_args_parser(func_args):
|
6 |
+
parser = argparse.ArgumentParser(add_help=False)
|
7 |
+
parser.add_argument('--input', help='Path to input image or video file. Skip this argument to capture frames from a camera.', default=0, required=False)
|
8 |
+
parser.add_argument('--model', help='Path to dexined.onnx', default='edge_detection_dexined_2024sep.onnx', required=False)
|
9 |
+
|
10 |
+
args, _ = parser.parse_known_args()
|
11 |
+
parser = argparse.ArgumentParser(parents=[parser],
|
12 |
+
description='', formatter_class=argparse.RawTextHelpFormatter)
|
13 |
+
return parser.parse_args(func_args)
|
14 |
+
|
15 |
+
def main(func_args=None):
|
16 |
+
args = get_args_parser(func_args)
|
17 |
+
|
18 |
+
dexined = Dexined(modelPath=args.model)
|
19 |
+
|
20 |
+
# Open video or capture from camera
|
21 |
+
cap = cv.VideoCapture(cv.samples.findFile(args.input) if args.input else 0)
|
22 |
+
if not cap.isOpened():
|
23 |
+
print("Failed to open the input video")
|
24 |
+
exit(-1)
|
25 |
+
|
26 |
+
cv.namedWindow('Input', cv.WINDOW_AUTOSIZE)
|
27 |
+
cv.namedWindow('Output', cv.WINDOW_AUTOSIZE)
|
28 |
+
cv.moveWindow('Output', 200, 50)
|
29 |
+
|
30 |
+
# Process frames
|
31 |
+
tm = cv.TickMeter()
|
32 |
+
while cv.waitKey(1) < 0:
|
33 |
+
hasFrame, image = cap.read()
|
34 |
+
if not hasFrame:
|
35 |
+
print("Press any key to exit")
|
36 |
+
cv.waitKey(0)
|
37 |
+
break
|
38 |
+
|
39 |
+
tm.start()
|
40 |
+
result = dexined.infer(image)
|
41 |
+
tm.stop()
|
42 |
+
label = 'Inference time: {:.2f} ms, FPS: {:.2f}'.format(tm.getTimeMilli(), tm.getFPS())
|
43 |
+
|
44 |
+
cv.imshow("Input", image)
|
45 |
+
cv.putText(result, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))
|
46 |
+
cv.imshow("Output", result)
|
47 |
+
|
48 |
+
cv.destroyAllWindows()
|
49 |
+
|
50 |
+
if __name__ == '__main__':
|
51 |
+
main()
|
dexined.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2 as cv
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
class Dexined:
|
5 |
+
def __init__(self, modelPath='edge_detection_dexined_2024sep.onnx', backendId=0, targetId=0):
|
6 |
+
self._modelPath = modelPath
|
7 |
+
self._backendId = backendId
|
8 |
+
self._targetId = targetId
|
9 |
+
|
10 |
+
# Load the model
|
11 |
+
self._model = cv.dnn.readNetFromONNX(self._modelPath)
|
12 |
+
self.setBackendAndTarget(self._backendId, self._targetId)
|
13 |
+
|
14 |
+
@property
|
15 |
+
def name(self):
|
16 |
+
return self.__class__.__name__
|
17 |
+
|
18 |
+
def setBackendAndTarget(self, backendId, targetId):
|
19 |
+
self._backendId = backendId
|
20 |
+
self._targetId = targetId
|
21 |
+
self._model.setPreferableBackend(self._backendId)
|
22 |
+
self._model.setPreferableTarget(self._targetId)
|
23 |
+
|
24 |
+
@staticmethod
|
25 |
+
def sigmoid(x):
|
26 |
+
return 1.0 / (1.0 + np.exp(-x))
|
27 |
+
|
28 |
+
def postProcessing(self, output, shape):
|
29 |
+
h, w = shape
|
30 |
+
preds = []
|
31 |
+
for p in output:
|
32 |
+
img = self.sigmoid(p)
|
33 |
+
img = np.squeeze(img)
|
34 |
+
img = cv.normalize(img, None, 0, 255, cv.NORM_MINMAX, cv.CV_8U)
|
35 |
+
img = cv.resize(img, (w, h))
|
36 |
+
preds.append(img)
|
37 |
+
fuse = preds[-1]
|
38 |
+
ave = np.array(preds, dtype=np.float32)
|
39 |
+
ave = np.uint8(np.mean(ave, axis=0))
|
40 |
+
return fuse, ave
|
41 |
+
|
42 |
+
def infer(self, image):
|
43 |
+
inp = cv.dnn.blobFromImage(image, 1.0, (512, 512), (103.5, 116.2, 123.6), swapRB=False, crop=False)
|
44 |
+
self._model.setInput(inp)
|
45 |
+
|
46 |
+
# Forward pass through the model
|
47 |
+
out = self._model.forward()
|
48 |
+
result, _ = self.postProcessing(out, image.shape[:2])
|
49 |
+
|
50 |
+
return result
|