gursimar-singh commited on
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

Files changed (6) hide show
  1. CMakeLists.txt +11 -0
  2. LICENSE +21 -0
  3. README.md +55 -0
  4. demo.cpp +138 -0
  5. demo.py +51 -0
  6. 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
+ ![chicky](./example_outputs/chicky_output.jpg)
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