vendor: OpenCV 5.0.0 snapshot at 40738fb16ceddb5fb3fea747585f7ce6abb0605b

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:10:33 +08:00
commit f7f077da11
6933 changed files with 2335208 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
*.caffemodel
*.pb
*.weights
+24
View File
@@ -0,0 +1,24 @@
ocv_install_example_src(dnn *.cpp *.hpp CMakeLists.txt)
set(OPENCV_DNN_SAMPLES_REQUIRED_DEPS
opencv_core
opencv_imgproc
opencv_dnn
opencv_objdetect
opencv_video
opencv_imgcodecs
opencv_videoio
opencv_highgui)
ocv_check_dependencies(${OPENCV_DNN_SAMPLES_REQUIRED_DEPS})
if(NOT BUILD_EXAMPLES OR NOT OCV_DEPENDENCIES_FOUND)
return()
endif()
project(dnn_samples)
ocv_include_modules_recurse(${OPENCV_DNN_SAMPLES_REQUIRED_DEPS})
file(GLOB_RECURSE dnn_samples RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
foreach(sample_filename ${dnn_samples})
ocv_define_sample(tgt ${sample_filename} dnn)
ocv_target_link_libraries(${tgt} PRIVATE ${OPENCV_LINKER_LIBS} ${OPENCV_DNN_SAMPLES_REQUIRED_DEPS})
endforeach()
+84
View File
@@ -0,0 +1,84 @@
# OpenCV deep learning module samples
## Model Zoo
Check [a wiki](https://github.com/opencv/opencv/wiki/Deep-Learning-in-OpenCV) for a list of tested models.
If OpenCV is built with [Intel's Inference Engine support](https://github.com/opencv/opencv/wiki/Intel%27s-Deep-Learning-Inference-Engine-backend) you can use [Intel's pre-trained](https://github.com/opencv/open_model_zoo) models.
There are different preprocessing parameters such mean subtraction or scale factors for different models.
You may check the most popular models and their parameters at [models.yml](https://github.com/opencv/opencv/blob/5.x/samples/dnn/models.yml) configuration file. It might be also used for aliasing samples parameters. In example,
```bash
python object_detection.py opencv_fd --model /path/to/model.onnx
```
Check `-h` option to know which values are used by default:
```bash
python object_detection.py opencv_fd -h
```
### Sample models
You can download sample models using ```download_models.py```. For example, the following command will download network weights for OpenCV Face Detector model and store them in FaceDetector folder:
```bash
python download_models.py --save_dir FaceDetector opencv_fd
```
You can use default configuration files adopted for OpenCV from [here](https://github.com/opencv/opencv_extra/tree/5.x/testdata/dnn).
You also can use the script to download necessary files from your code. Assume you have the following code inside ```your_script.py```:
```python
from download_models import downloadFile
filepath1 = downloadFile("https://huggingface.co/onnxmodelzoo/ssd_mobilenet_v1_12/resolve/main/ssd_mobilenet_v1_12.onnx", None, filename="ssd_mobilenet_v1_12.onnx", save_dir="save_dir_1")
filepath2 = downloadFile("https://huggingface.co/onnxmodelzoo/ssd_mobilenet_v1_12/resolve/main/ssd_mobilenet_v1_12.onnx", "83536889adce1eda154175f8e3b156dd20443631", filename="ssd_mobilenet_v1_12.onnx")
print(filepath1)
print(filepath2)
# Your code
```
By running the following commands, you will get **ssd_mobilenet_v1_12.onnx** file:
```bash
export OPENCV_DOWNLOAD_DATA_PATH=download_folder
python your_script.py
```
**Note** that you can provide a directory using **save_dir** parameter or via **OPENCV_SAVE_DIR** environment variable.
#### Face detection
[An origin model](https://github.com/opencv/opencv/tree/5.x/samples/dnn/face_detector)
with single precision floating point weights has been quantized using [TensorFlow framework](https://www.tensorflow.org/).
To achieve the best accuracy run the model on BGR images resized to `300x300` applying mean subtraction
of values `(104, 177, 123)` for each blue, green and red channels correspondingly.
The following are accuracy metrics obtained using [COCO object detection evaluation
tool](http://cocodataset.org/#detections-eval) on [FDDB dataset](http://vis-www.cs.umass.edu/fddb/)
(see [script](https://github.com/opencv/opencv/blob/5.x/modules/dnn/misc/face_detector_accuracy.py))
applying resize to `300x300` and keeping an origin images' sizes.
```
AP - Average Precision | FP32/FP16 | UINT8 | FP32/FP16 | UINT8 |
AR - Average Recall | 300x300 | 300x300 | any size | any size |
--------------------------------------------------|-----------|----------------|-----------|----------------|
AP @[ IoU=0.50:0.95 | area= all | maxDets=100 ] | 0.408 | 0.408 | 0.378 | 0.328 (-0.050) |
AP @[ IoU=0.50 | area= all | maxDets=100 ] | 0.849 | 0.849 | 0.797 | 0.790 (-0.007) |
AP @[ IoU=0.75 | area= all | maxDets=100 ] | 0.251 | 0.251 | 0.208 | 0.140 (-0.068) |
AP @[ IoU=0.50:0.95 | area= small | maxDets=100 ] | 0.050 | 0.051 (+0.001) | 0.107 | 0.070 (-0.037) |
AP @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] | 0.381 | 0.379 (-0.002) | 0.380 | 0.368 (-0.012) |
AP @[ IoU=0.50:0.95 | area= large | maxDets=100 ] | 0.455 | 0.455 | 0.412 | 0.337 (-0.075) |
AR @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] | 0.299 | 0.299 | 0.279 | 0.246 (-0.033) |
AR @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] | 0.482 | 0.482 | 0.476 | 0.436 (-0.040) |
AR @[ IoU=0.50:0.95 | area= all | maxDets=100 ] | 0.496 | 0.496 | 0.491 | 0.451 (-0.040) |
AR @[ IoU=0.50:0.95 | area= small | maxDets=100 ] | 0.189 | 0.193 (+0.004) | 0.284 | 0.232 (-0.052) |
AR @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] | 0.481 | 0.480 (-0.001) | 0.470 | 0.458 (-0.012) |
AR @[ IoU=0.50:0.95 | area= large | maxDets=100 ] | 0.528 | 0.528 | 0.520 | 0.462 (-0.058) |
```
## References
* [Models downloading script](https://github.com/opencv/opencv/blob/5.x/samples/dnn/download_models.py)
* [Configuration files adopted for OpenCV](https://github.com/opencv/opencv_extra/tree/5.x/testdata/dnn)
* [How to import models from TensorFlow Object Detection API](https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API)
* [Names of classes from different datasets](https://github.com/opencv/opencv/tree/5.x/samples/data/dnn)
+82
View File
@@ -0,0 +1,82 @@
import os
import numpy as np
import cv2 as cv
import argparse
from common import findFile
parser = argparse.ArgumentParser(description='Use this script to run action recognition using 3D ResNet34',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--input', '-i', help='Path to input video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--model', required=True, help='Path to model.')
parser.add_argument('--classes', default=findFile('action_recongnition_kinetics.txt'), help='Path to classes list.')
# To get net download original repository https://github.com/kenshohara/video-classification-3d-cnn-pytorch
# For correct ONNX export modify file: video-classification-3d-cnn-pytorch/models/resnet.py
# change
# - def downsample_basic_block(x, planes, stride):
# - out = F.avg_pool3d(x, kernel_size=1, stride=stride)
# - zero_pads = torch.Tensor(out.size(0), planes - out.size(1),
# - out.size(2), out.size(3),
# - out.size(4)).zero_()
# - if isinstance(out.data, torch.cuda.FloatTensor):
# - zero_pads = zero_pads.cuda()
# -
# - out = Variable(torch.cat([out.data, zero_pads], dim=1))
# - return out
# To
# + def downsample_basic_block(x, planes, stride):
# + out = F.avg_pool3d(x, kernel_size=1, stride=stride)
# + out = F.pad(out, (0, 0, 0, 0, 0, 0, 0, int(planes - out.size(1)), 0, 0), "constant", 0)
# + return out
# To ONNX export use torch.onnx.export(model, inputs, model_name)
def get_class_names(path):
class_names = []
with open(path) as f:
for row in f:
class_names.append(row[:-1])
return class_names
def classify_video(video_path, net_path):
SAMPLE_DURATION = 16
SAMPLE_SIZE = 112
mean = (114.7748, 107.7354, 99.4750)
class_names = get_class_names(args.classes)
net = cv.dnn.readNet(net_path)
net.setPreferableBackend(cv.dnn.DNN_BACKEND_INFERENCE_ENGINE)
net.setPreferableTarget(cv.dnn.DNN_TARGET_CPU)
winName = 'Deep learning image classification in OpenCV'
cv.namedWindow(winName, cv.WINDOW_AUTOSIZE)
cap = cv.VideoCapture(video_path)
while cv.waitKey(1) < 0:
frames = []
for _ in range(SAMPLE_DURATION):
hasFrame, frame = cap.read()
if not hasFrame:
exit(0)
frames.append(frame)
inputs = cv.dnn.blobFromImages(frames, 1, (SAMPLE_SIZE, SAMPLE_SIZE), mean, True, crop=True)
inputs = np.transpose(inputs, (1, 0, 2, 3))
inputs = np.expand_dims(inputs, axis=0)
net.setInput(inputs)
outputs = net.forward()
class_pred = np.argmax(outputs)
label = class_names[class_pred]
for frame in frames:
labelSize, baseLine = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, 0.5, 1)
cv.rectangle(frame, (0, 10 - labelSize[1]),
(labelSize[0], 10 + baseLine), (255, 255, 255), cv.FILLED)
cv.putText(frame, label, (0, 10), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
cv.imshow(winName, frame)
if cv.waitKey(1) & 0xFF == ord('q'):
break
if __name__ == "__main__":
args, _ = parser.parse_known_args()
classify_video(args.input if args.input else 0, args.model)
+201
View File
@@ -0,0 +1,201 @@
/*
* This file is part of OpenCV project.
* It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at http://opencv.org/license.html.
*
* Copyright (C) 2025, Bigvision LLC.
*
* @file alpha_matting.cpp
* @brief MODNet Alpha Matting using OpenCV DNN
*
* This sample demonstrates human portrait alpha matting using MODNet model.
* MODNet is a trimap-free portrait matting method that can produce high-quality
* alpha mattes for portrait images in real-time.
*
* Reference:
* Github: https://github.com/ZHKKKe/MODNet
*
* Usage:
* ./example_dnn_alpha_matting --input=image.jpg # Process image
*
* Requirements:
* - OpenCV >= 5.0.0 with DNN module
* - MODNet ONNX model
*/
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <vector>
#include <string>
#include "common.hpp"
using namespace cv;
using namespace cv::dnn;
using namespace std;
const string about =
"This sample demonstrates human portrait alpha matting using MODNet model.\n"
"MODNet is a trimap-free portrait matting method that can produce high-quality\n"
"alpha mattes for portrait images in real-time.\n\n"
"Usage examples:\n"
"\t./example_alpha_matting --input=image.jpg\n"
"\t./example_alpha_matting modnet (using config alias)\n\n"
"To download the MODNet model, run: python download_models.py modnet\n"
"Press any key to exit \n";
const string param_keys =
"{ help h | | Print help message }"
"{ @alias | modnet | An alias name of model to extract preprocessing parameters from models.yml file }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ input i | messi5.jpg | Path to input image file }"
"{ model | | Path to MODNet ONNX model file }";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine, "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float precision) }");
string keys = param_keys + backend_keys + target_keys;
static void loadModel(const string modelPath, String backend, String target, Net &net, EngineType engine)
{
net = readNetFromONNX(modelPath, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
}
static void postprocess(const Mat &image, const Mat &alpha_output, Mat &alpha_mask)
{
int h = image.rows;
int w = image.cols;
Mat alpha;
if (alpha_output.dims == 4 && alpha_output.size[0] == 1 && alpha_output.size[1] == 1)
{
alpha = alpha_output.reshape(0, {alpha_output.size[2], alpha_output.size[3]});
}
else
{
alpha = alpha_output.clone();
}
resize(alpha, alpha, Size(w, h));
alpha = cv::min(cv::max(alpha, 0.0), 1.0);
alpha.convertTo(alpha_mask, CV_8U, 255.0);
}
static void processImage(const Mat &image, Mat &alpha_mask, Mat &composite, Net &net,
float scale, int width, int height, const Scalar &mean, bool swapRB)
{
if (image.empty())
return;
Mat blob = blobFromImage(image, scale, Size(width, height), mean, swapRB, false, CV_32F);
net.setInput(blob);
Mat output = net.forward();
postprocess(image, output, alpha_mask);
Mat alpha_3ch;
cvtColor(alpha_mask, alpha_3ch, COLOR_GRAY2BGR);
alpha_3ch.convertTo(alpha_3ch, CV_32F, 1.0 / 255.0);
Mat image_f;
image.convertTo(image_f, CV_32F);
multiply(image_f, alpha_3ch, composite);
composite.convertTo(composite, CV_8U);
}
static void setupWindows()
{
namedWindow("Original", WINDOW_AUTOSIZE);
namedWindow("Alpha Mask", WINDOW_AUTOSIZE);
namedWindow("Composite", WINDOW_AUTOSIZE);
moveWindow("Alpha Mask", 200, 0);
moveWindow("Composite", 400, 0);
}
int main(int argc, char **argv)
{
CommandLineParser parser(argc, argv, keys);
if (parser.has("help"))
{
cout << about << endl;
parser.printMessage();
return 0;
}
string modelName = parser.get<String>("@alias");
string zooFile = parser.get<String>("zoo");
zooFile = findFile(zooFile);
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
int input_width = parser.get<int>("width");
int input_height = parser.get<int>("height");
float scale_factor = parser.get<float>("scale");
Scalar mean_values = parser.get<Scalar>("mean");
bool swapRB = parser.get<bool>("rgb");
String backend = parser.get<String>("backend");
String target = parser.get<String>("target");
String sha1 = parser.get<String>("sha1");
string model = findModel(parser.get<String>("model"), sha1);
parser.about(about);
EngineType engine = ENGINE_AUTO;
if (backend != "default" || target != "cpu")
{
engine = ENGINE_CLASSIC;
}
Net net;
loadModel(model, backend, target, net, engine);
string input_path = samples::findFile(parser.get<String>("input"));
Mat image = imread(input_path);
if (image.empty())
{
cout << "[ERROR] Cannot load input image: " << input_path << endl;
return -1;
}
setupWindows();
cout << "Processing image: " << input_path << endl;
cout << "Press any key to exit" << endl;
Mat alpha_mask, composite;
processImage(image, alpha_mask, composite, net, scale_factor, input_width, input_height, mean_values, swapRB);
imshow("Original", image);
imshow("Alpha Mask", alpha_mask);
imshow("Composite", composite);
waitKey(0);
destroyAllWindows();
return 0;
}
+177
View File
@@ -0,0 +1,177 @@
"""
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory of this distribution and at http://opencv.org/license.html.
Copyright (C) 2025, Bigvision LLC.
MODNet Alpha Matting with OpenCV DNN
This sample demonstrates human portrait alpha matting using MODNet model.
MODNet is a trimap-free portrait matting method that can produce high-quality
alpha mattes for portrait images in real-time.
Reference:
Github: https://github.com/ZHKKKe/MODNet
To download the MODNet model, run:
python download_models.py modnet
Usage:
python alpha_matting.py --input=image.jpg
"""
import cv2 as cv
import numpy as np
import argparse
import os
from common import *
def get_args_parser(func_args):
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = (
"cpu",
"opencl",
"opencl_fp16",
"ncs2_vpu",
"hddl_vpu",
"vulkan",
"cuda",
"cuda_fp16",
)
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
"--zoo",
default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "models.yml"),
help="An optional path to file with preprocessing parameters.",
)
parser.add_argument(
"--input",
default="messi5.jpg",
help="Path to input image or video file. Defaults to messi5.jpg in samples/data.",
)
parser.add_argument(
"--backend",
default="default",
type=str,
choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine, "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA",
)
parser.add_argument(
"--target",
default="cpu",
type=str,
choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float precision)",
)
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, "alpha_matting", "modnet")
parser = argparse.ArgumentParser(
parents=[parser],
description="""
To run:
python alpha_matting.py --input=path/to/your/input/image
Model path can also be specified using --model argument
""",
formatter_class=argparse.RawTextHelpFormatter,
)
return parser.parse_args(func_args)
def postprocess_output(image, alpha_output):
"""Process model output to create alpha mask."""
h, w = image.shape[:2]
alpha = alpha_output[0, 0] if alpha_output.ndim == 4 else alpha_output[0]
alpha = cv.resize(alpha, (w, h))
alpha = np.clip(alpha, 0, 1)
alpha_mask = (alpha * 255).astype(np.uint8)
return alpha_mask
def loadModel(args, engine):
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
return net
def draw_label(img, text, color):
h, w = img.shape[:2]
font_scale = max(h, w) / 1000.0
thickness = 1
text_size, _ = cv.getTextSize(text, cv.FONT_HERSHEY_SIMPLEX, font_scale, thickness)
x = 10
y = text_size[1] + 10
cv.putText(img, text, (x, y), cv.FONT_HERSHEY_SIMPLEX, font_scale, color, thickness)
def apply_modnet(args, model, image):
inp = cv.dnn.blobFromImage(
image, args.scale, (args.width, args.height), args.mean, swapRB=args.rgb
)
model.setInput(inp)
t0 = cv.getTickCount()
out = model.forward()
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
alpha_mask = postprocess_output(image, out)
alpha_3ch = cv.merge([alpha_mask / 255.0, alpha_mask / 255.0, alpha_mask / 255.0])
composite = (image.astype(np.float32) * alpha_3ch).astype(np.uint8)
return alpha_mask, composite, t
def main(func_args=None):
args = get_args_parser(func_args)
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
image = cv.imread(cv.samples.findFile(args.input))
if image is None:
print("Failed to load the input image")
exit(-1)
cv.namedWindow("Input", cv.WINDOW_AUTOSIZE)
cv.namedWindow("Alpha Mask", cv.WINDOW_AUTOSIZE)
cv.namedWindow("Composite", cv.WINDOW_AUTOSIZE)
cv.moveWindow("Alpha Mask", 200, 50)
cv.moveWindow("Composite", 400, 50)
args.model = findModel(args.model, args.sha1)
net = loadModel(args, engine)
alpha_mask, composite, t = apply_modnet(args, net, image)
label = "Inference time: %.2f ms" % (t * 1000.0)
draw_label(image, label, (0, 255, 0))
draw_label(alpha_mask, label, (255, 255, 255))
draw_label(composite, label, (0, 255, 0))
cv.imshow("Input", image)
cv.imshow("Alpha Mask", alpha_mask)
cv.imshow("Composite", composite)
print("Press any key to exit")
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == "__main__":
main()
+265
View File
@@ -0,0 +1,265 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html.
/*
Auto white balance using FC4: https://github.com/yuanming-hu/fc4
Color constancy is a method to make colors of objects render correctly on a photo.
White balance aims to make white objects appear white on an image and not a shade of any
other color, independent of the actual light setting. White balance correction creates
a neutral looking coloring of the objects, and generally makes colors look more similar
to their 'true' colors under different light conditions.
Given an RGB image, the FC4 model predicts scene illuminant (R,G,B). We then apply
the illuminant to the image, applying the correction in the linear RGB space.
The transformation between linear and sRGB spaces is done as described in the sRGB standard,
which is a nonlinear Gamma correction with exponent 2.4 and extra handling of very small values.
This sample is written for 8bit images. The FC4 model accepts RGB images with applied Gamma scaling.
The training of the FC4 model was done on the Gehler-Shi dataset. The dataset includes
568 images and ground truth corrections, as well as ground truth illuminants. The linear
RGB images from the dataset were used with Gamma correction of 2.2 applied.
The model is a pretrained fold 0 of a training pipeline on the Gehler-Shi dataset, from the PyTorch
implementation of the FC4 algorithm by Mateo Rizzo. The model was converted from a .pth file to onnx
using torch.onnx.export. The model can be downloaded in the following link:
https://raw.githubusercontent.com/MykhailoTrushch/opencv/d6ab21353a87e4c527e38e464384c7ee78e96e22/samples/dnn/models/fc4_fold_0.onnx
Copyright (c) 2017 Yuanming Hu, Baoyuan Wang, Stephen Lin
Copyright (c) 2021 Matteo Rizzo
Licensed under the MIT license.
References:
Yuanming Hu, Baoyuan Wang, and Stephen Lin. “FC⁴: Fully Convolutional Color
Constancy with Confidence-Weighted Pooling.” CVPR, 2017, pp. 40854094.
Implementations of FC4:
https://github.com/yuanming-hu/fc4/
https://github.com/matteo-rizzo/fc4-pytorch
Lilong Shi and Brian Funt, "Re-processed Version of the Gehler Color Constancy Dataset of 568 Images,"
accessed from http://www.cs.sfu.ca/~colour/data/
“IEC 61966-2-1:1999 Multimedia Systems and Equipment Colour Measurement and Management Part 2-1: Colour Management Default RGB Colour Space sRGB.” IEC Standard, 1999.
*/
#include <iostream>
#include <opencv2/dnn.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include "common.hpp"
using namespace cv;
using namespace cv::dnn;
using namespace std;
const string param_keys =
"{ help h | | Print help message }"
"{ @alias | fc4 | Model alias from models.yml "
"(optional) }"
"{ zoo | ../dnn/models.yml | Path to models.yml file "
"(optional) }"
"{ input i | castle.png | Path to input image }";
;
const string backend_keys =
format("{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine "
"(https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys =
format("{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
// Normalization constant for 8bit values
const float NORMALIZE_FACTOR = 1.0f / 255.0f;
// sRGB to linear conversion constants (or vice versa):
// SRGB_THRESHOLD / LINEAR_THRESHOLD: breakpoints between linear and gamma regions
// SRGB_SLOPE: slope of the linear segment near black
// SRGB_ALPHA: offset to ensure continuity at the threshold
// SRGB_EXP: gamma exponent
const float SRGB_THRESHOLD = 0.04045f;
const float SRGB_ALPHA = 0.055f;
const float SRGB_SLOPE = 12.92f;
const float SRGB_EXP = 2.4f;
const float LINEAR_THRESHOLD = 0.0031308f;
const float EPS = 1e-10f;
static Mat srgbToLinear(const Mat &srgb32f) {
CV_Assert(srgb32f.type() == CV_32FC3);
const float a = SRGB_ALPHA;
Mat y = srgb32f;
Mat mask_low;
compare(y, SRGB_THRESHOLD, mask_low, CMP_LE);
Mat low = y / SRGB_SLOPE;
Mat t = (y + a) / (1.0f + a);
Mat high;
pow(t, SRGB_EXP, high);
Mat lin(y.size(), y.type(), Scalar(0, 0, 0));
low.copyTo(lin, mask_low);
Mat mask_high;
bitwise_not(mask_low, mask_high);
high.copyTo(lin, mask_high);
return lin;
}
static Mat linearToSrgb(const Mat &lin32f) {
CV_Assert(lin32f.type() == CV_32FC3);
const float a = SRGB_ALPHA;
Mat x = lin32f;
Mat mask_low;
compare(x, LINEAR_THRESHOLD, mask_low, CMP_LE);
Mat low = x * SRGB_SLOPE;
Mat powPart;
pow(x, 1.0 / SRGB_EXP, powPart);
Mat high = (1.0f + a) * powPart - a;
Mat srgb(x.size(), x.type(), Scalar(0, 0, 0));
low.copyTo(srgb, mask_low);
Mat mask_high;
bitwise_not(mask_low, mask_high);
high.copyTo(srgb, mask_high);
return srgb;
}
static Mat correct(const Mat &bgr8u, const Vec3f &illumRGB_linear) {
Mat f32;
bgr8u.convertTo(f32, CV_32F, NORMALIZE_FACTOR);
Mat lin = srgbToLinear(f32);
const float eR = std::max(illumRGB_linear[0], EPS);
const float eG = std::max(illumRGB_linear[1], EPS);
const float eB = std::max(illumRGB_linear[2], EPS);
float s3 = std::sqrt(3.0f);
Scalar corr(eB * s3 + EPS, eG * s3 + EPS, eR * s3 + EPS);
Mat corrected;
divide(lin, corr, corrected);
std::vector<Mat> ch;
split(corrected, ch);
double m0, m1, m2;
minMaxLoc(ch[0], nullptr, &m0);
minMaxLoc(ch[1], nullptr, &m1);
minMaxLoc(ch[2], nullptr, &m2);
float maxVal = static_cast<float>(std::max({m0, m1, m2})) + EPS;
corrected /= maxVal;
min(corrected, 1.0, corrected);
max(corrected, 0.0, corrected);
Mat srgb = linearToSrgb(corrected);
Mat out;
srgb.convertTo(out, CV_8U, 255.0);
return out;
}
static void annotate(Mat &img, const string &title) {
double fs = std::max(0.5, std::min(img.cols, img.rows) / 800.0);
int th = std::max(1, (int)std::round(fs * 2));
putText(img, title, Point(10, 30), FONT_HERSHEY_SIMPLEX, fs,
Scalar(0, 255, 0), th);
}
int main(int argc, char **argv) {
const string about = "FC4 Color Constancy (ONNX) sample.\n"
"Predicts scene illuminant and corrects the white "
"balance of the image.\n";
string keys = param_keys + backend_keys + target_keys;
CommandLineParser parser(argc, argv, keys);
if (parser.has("help")) {
cout << about << endl;
parser.printMessage();
return 0;
}
string modelName = parser.get<String>("@alias");
string zooFile = samples::findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
float scale = parser.get<float>("scale");
Scalar mean = parser.get<Scalar>("mean");
bool swapRB = parser.get<bool>("rgb");
String backend = parser.get<String>("backend");
String target = parser.get<String>("target");
String sha1 = parser.get<String>("sha1");
string model = findModel(parser.get<String>("model"), sha1);
string inputPath = findFile(parser.get<String>("input"));
if (model.empty()) {
cerr << "Model file not found\n";
return -1;
}
Net net;
try {
net = readNetFromONNX(model);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
} catch (const Exception &e) {
cerr << "Error loading model: " << e.what() << endl;
return -1;
}
Mat img = imread(inputPath, IMREAD_COLOR);
if (img.empty()) {
cerr << "Cannot load image: " << inputPath << endl;
return -1;
}
Mat blob;
blob = blobFromImage(img, scale, img.size(), mean, swapRB, /*crop=*/false,
/*type=*/CV_32F);
net.setInput(blob);
Mat out;
try {
out = net.forward();
} catch (const Exception &e) {
cerr << "Forward error: " << e.what() << endl;
return -1;
}
const float *p = out.ptr<float>(0);
CV_Assert(out.total() == 3);
Vec3f illum = Vec3f(p[0], p[1], p[2]);
Mat corrected = correct(img, illum);
Mat origVis = img.clone();
Mat corrVis = corrected.clone();
annotate(origVis, "Original");
annotate(corrVis, "FC4-corrected");
Mat stacked;
hconcat(origVis, corrVis, stacked);
imshow("Original and Corrected Images", stacked);
waitKey(0);
destroyAllWindows();
return 0;
}
+202
View File
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
# This file is part of OpenCV project.
# It is subject to the license terms in the LICENSE file found in the top-level
# directory of this distribution and at http://opencv.org/license.html.
'''
Auto white balance using FC4: https://github.com/yuanming-hu/fc4
Color constancy is a method to make colors of objects render correctly on a photo.
White balance aims to make white objects appear white on an image and not a shade of any
other color, independent of the actual light setting. White balance correction creates
a neutral looking coloring of the objects, and generally makes colors look more similar
to their 'true' colors under different light conditions.
Given an RGB image, the FC4 model predicts scene illuminant (R,G,B). We then apply
the illuminant to the image, applying the correction in the linear RGB space.
The transformation between linear and sRGB spaces is done as described in the sRGB standard,
which is a nonlinear Gamma correction with exponent 2.4 and extra handling of very small values.
This sample is written for 8bit images. The FC4 model accepts RGB images with applied Gamma scaling.
The training of the FC4 model was done on the Gehler-Shi dataset. The dataset includes
568 images and ground truth corrections, as well as ground truth illuminants. The linear
RGB images from the dataset were used with Gamma correction of 2.2 applied.
The model is a pretrained fold 0 of a training pipeline on the Gehler-Shi dataset, from the PyTorch
implementation of the FC4 algorithm by Mateo Rizzo. The model was converted from a .pth file to onnx
using torch.onnx.export. The model can be downloaded in the following link:
https://raw.githubusercontent.com/MykhailoTrushch/opencv/d6ab21353a87e4c527e38e464384c7ee78e96e22/samples/dnn/models/fc4_fold_0.onnx
Copyright (c) 2017 Yuanming Hu, Baoyuan Wang, Stephen Lin
Copyright (c) 2021 Matteo Rizzo
Licensed under the MIT license.
References:
Yuanming Hu, Baoyuan Wang, and Stephen Lin. “FC⁴: Fully Convolutional Color
Constancy with Confidence-Weighted Pooling.” CVPR, 2017, pp. 40854094.
Implementations of FC4:
https://github.com/yuanming-hu/fc4/
https://github.com/matteo-rizzo/fc4-pytorch
Lilong Shi and Brian Funt, "Re-processed Version of the Gehler Color
Constancy Dataset of 568 Images," accessed from http://www.cs.sfu.ca/~colour/data/
“IEC 61966-2-1:1999 Multimedia Systems and Equipment Colour Measurement and Management
Part 2-1: Colour Management Default RGB Colour Space sRGB.” IEC Standard, 1999.
'''
import argparse
import sys
import numpy as np
import cv2 as cv
from common import *
# Normalization constant for 8bit values
NORMALIZE_FACTOR = 1.0 / 255.0
# sRGB to linear conversion constants (or vice versa):
# SRGB_THRESHOLD / LINEAR_THRESHOLD: breakpoints between linear and gamma regions
# SRGB_SLOPE: slope of the linear segment near black
# SRGB_ALPHA: offset to ensure continuity at the threshold
# SRGB_EXP: gamma exponent
SRGB_THRESHOLD = 0.04045
SRGB_ALPHA = 0.055
SRGB_SLOPE = 12.92
SRGB_EXP = 2.4
LINEAR_THRESHOLD = 0.0031308
EPS = 1e-10
def srgb_to_linear(rgb: np.ndarray) -> np.ndarray:
low = rgb / SRGB_SLOPE
high = np.power((rgb + SRGB_ALPHA) / (1.0 + SRGB_ALPHA), SRGB_EXP, dtype=np.float32)
return np.where(rgb <= SRGB_THRESHOLD, low, high).astype(np.float32)
def linear_to_srgb(lin: np.ndarray) -> np.ndarray:
low = lin * SRGB_SLOPE
high = (1.0 + SRGB_ALPHA) * np.power(lin, 1.0 / SRGB_EXP, dtype=np.float32) - SRGB_ALPHA
return np.where(lin <= LINEAR_THRESHOLD, low, high).astype(np.float32)
def correct(bgr8u: np.ndarray, illum_rgb_linear: np.ndarray) -> np.ndarray:
assert bgr8u.dtype == np.uint8 and bgr8u.ndim == 3 and bgr8u.shape[2] == 3
bgr = bgr8u.astype(np.float32) * NORMALIZE_FACTOR
lin = srgb_to_linear(bgr)
e_r = max(float(illum_rgb_linear[0]), EPS)
e_g = max(float(illum_rgb_linear[1]), EPS)
e_b = max(float(illum_rgb_linear[2]), EPS)
s3 = np.float32(np.sqrt(3.0))
corr_bgr = np.array([e_b * s3 + EPS,
e_g * s3 + EPS,
e_r * s3 + EPS],
dtype=np.float32)
corrected = lin / corr_bgr.reshape(1, 1, 3)
max_val = float(corrected.max()) + EPS
corrected /= max_val
corrected = np.clip(corrected, 0.0, 1.0)
srgb = linear_to_srgb(corrected)
out_bgr8 = (srgb * 255.0 + 0.5).astype(np.uint8)
return out_bgr8
def annotate(img_bgr: np.ndarray, title: str) -> None:
fs = max(0.5, min(img_bgr.shape[1], img_bgr.shape[0]) / 800.0)
th = max(1, int(round(fs * 2)))
cv.putText(img_bgr, title, (10, 30), cv.FONT_HERSHEY_SIMPLEX, fs, (0,255,0), th)
def get_args_parser(func_args):
backends = ("default", "openvino", "opencv", "vkcom", "cuda", "webnn")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan",
"cuda", "cuda_fp16")
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--zoo',
default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
p.add_argument("--input", help="Path to input image", default="castle.png")
p.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
p.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = p.parse_known_args()
add_preproc_args(args.zoo, p, 'auto_white_balance', prefix="", alias="fc4")
p = argparse.ArgumentParser(
parents=[p],
description="FC4 Color Constancy (ONNX): " \
"predicts illuminant and applies white balance.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
return p.parse_args(func_args)
def main(func_args=None):
args = get_args_parser(func_args)
args.model = findModel(args.model, args.sha1)
try:
net = cv.dnn.readNetFromONNX(args.model)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
except cv.error as e:
print(f"Error loading model: {e}", file=sys.stderr)
sys.exit(1)
img = cv.imread(findFile(args.input), cv.IMREAD_COLOR)
if img is None:
print(f"Cannot load image: {args.input}", file=sys.stderr)
sys.exit(1)
blob = cv.dnn.blobFromImage(
img, scalefactor=args.scale, size=(img.shape[1], img.shape[0]),
mean=args.mean, swapRB=args.rgb, crop=False, ddepth=cv.CV_32F
)
net.setInput(blob)
try:
out = net.forward()
except cv.error as e:
print(f"Forward error: {e}", file=sys.stderr)
sys.exit(1)
illum = out.astype(np.float32).reshape(-1)
if out.size != 3:
print("Error: model output of size not equal to 3 (should output 3 illuminants in RGB order)")
sys.exit(-1)
corrected = correct(img, illum)
orig_vis = img.copy()
corr_vis = corrected.copy()
annotate(orig_vis, "Original")
annotate(corr_vis, "FC4-corrected")
stacked = np.hstack([orig_vis, corr_vis])
cv.imshow("Original and Corrected Images", stacked)
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == "__main__":
main()
+272
View File
@@ -0,0 +1,272 @@
#include <fstream>
#include <sstream>
#include <iostream>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>
#include "common.hpp"
using namespace cv;
using namespace std;
using namespace dnn;
const string about =
"Use this script to run a classification model on a camera stream, video, image or image list (i.e. .xml or .yaml containing image lists)\n\n"
"Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
"To run:\n"
"\t ./example_dnn_classification model_name --input=path/to/your/input/image/or/video (don't give --input flag if want to use device camera)\n"
"Sample command:\n"
"\t ./example_dnn_classification resnet --input=$OPENCV_SAMPLES_DATA_PATH/baboon.jpg\n"
"\t ./example_dnn_classification squeezenet\n"
"Model path can also be specified using --model argument. "
"Use imagelist_creator to create the xml or yaml list\n";
const string param_keys =
"{ help h | | Print help message. }"
"{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera.}"
"{ imglist | | Pass this flag if image list (i.e. .xml or .yaml) file is passed}"
"{ crop | false | Preprocess input image by center cropping.}"
//"{ labels | | Path to the text file with labels for detected objects.}"
"{ model | | Path to the model file.}";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
vector<string> classes;
static bool readStringList( const string& filename, vector<string>& l )
{
l.resize(0);
FileStorage fs(filename, FileStorage::READ);
if( !fs.isOpened() )
return false;
size_t dir_pos = filename.rfind('/');
if (dir_pos == string::npos)
dir_pos = filename.rfind('\\');
FileNode n = fs.getFirstTopLevelNode();
if( n.type() != FileNode::SEQ )
return false;
FileNodeIterator it = n.begin(), it_end = n.end();
for( ; it != it_end; ++it )
{
string fname = (string)*it;
if (dir_pos != string::npos)
{
string fpath = samples::findFile(filename.substr(0, dir_pos + 1) + fname, false);
if (fpath.empty())
{
fpath = samples::findFile(fname);
}
fname = fpath;
}
else
{
fname = samples::findFile(fname);
}
l.push_back(fname);
}
return true;
}
int main(int argc, char** argv)
{
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_INFO);
CommandLineParser parser(argc, argv, keys);
if (!parser.has("@alias") || parser.has("help"))
{
cout << about << endl;
parser.printMessage();
return -1;
}
const string modelName = parser.get<String>("@alias");
const string zooFile = findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
parser.about(about);
if (argc == 1 || parser.has("help"))
{
parser.printMessage();
return 0;
}
String sha1 = parser.get<String>("sha1");
float scale = parser.get<float>("scale");
Scalar mean = parser.get<Scalar>("mean");
Scalar std = parser.get<Scalar>("std");
bool swapRB = parser.get<bool>("rgb");
bool crop = parser.get<bool>("crop");
int inpWidth = parser.get<int>("width");
int inpHeight = parser.get<int>("height");
String model = findModel(parser.get<String>("model"), sha1);
String backend = parser.get<String>("backend");
String target = parser.get<String>("target");
bool isImgList = parser.has("imglist");
// Open file with labels.
string labels_filename = parser.get<String>("labels");
string file = findFile(labels_filename);
ifstream ifs(file.c_str());
if (!ifs.is_open()){
cout<<"File " << file << " not found";
exit(1);
}
string line;
while (getline(ifs, line))
{
classes.push_back(line);
}
if (!parser.check())
{
parser.printErrors();
return 1;
}
CV_Assert(!model.empty());
//! [Read and initialize network]
EngineType engine = ENGINE_AUTO;
if (backend != "default" || target != "cpu"){
engine = ENGINE_CLASSIC;
}
Net net = readNetFromONNX(model, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
net.setProfilingMode(DNN_PROFILE_SUMMARY);
//! [Read and initialize network]
// Create a window
static const std::string kWinName = "Deep learning image classification in OpenCV";
namedWindow(kWinName, WINDOW_NORMAL);
//Create FontFace for putText
FontFace sans("sans");
//! [Open a video file or an image file or a camera stream]
VideoCapture cap;
vector<string> imageList;
size_t currentImageIndex = 0;
if (parser.has("input")) {
string input = findFile(parser.get<String>("input"));
if (isImgList) {
bool check = readStringList(samples::findFile(input), imageList);
if (imageList.empty() || !check) {
cout << "Error: No images found or the provided file is not a valid .yaml or .xml file." << endl;
return -1;
}
} else {
// Input is not a directory, try to open as video or image
cap.open(input);
if (!cap.isOpened()) {
cout << "Failed to open the input." << endl;
return -1;
}
}
} else {
cap.open(0); // Open default camera
}
//! [Open a video file or an image file or a camera stream]
Mat frame, blob;
for(;;)
{
if (!imageList.empty()) {
// Handling directory of images
if (currentImageIndex >= imageList.size()) {
waitKey();
break; // Exit if all images are processed
}
frame = imread(imageList[currentImageIndex++]);
if(frame.empty()){
cout<<"Cannot open file"<<endl;
continue;
}
} else {
// Handling video or single image
cap >> frame;
}
if (frame.empty())
{
break;
}
//! [Create a 4D blob from a frame]
blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight), mean, swapRB, crop);
// Check std values.
if (std.val[0] != 0.0 && std.val[1] != 0.0 && std.val[2] != 0.0)
{
// Divide blob by std.
divide(blob, std, blob);
}
//! [Create a 4D blob from a frame]
//! [Set input blob]
net.setInput(blob);
//! [Set input blob]
TickMeter timeRecorder;
timeRecorder.reset();
Mat prob = net.forward();
double t1;
//! [Make forward pass]
timeRecorder.start();
prob = net.forward();
timeRecorder.stop();
net.printPerfProfile();
//! [Make forward pass]
//! [Get a class with a highest score]
int N = (int)prob.total(), K = std::min(5, N);
std::vector<std::pair<float, int> > prob_vec;
for (int i = 0; i < N; i++) {
prob_vec.push_back(std::make_pair(-prob.at<float>(i), i));
}
std::sort(prob_vec.begin(), prob_vec.end());
//! [Get a class with a highest score]
t1 = timeRecorder.getTimeMilli();
timeRecorder.reset();
string label = format("Inference time: %.1f ms", t1);
Mat subframe = frame(Rect(0, 0, std::min(1000, frame.cols), std::min(300, frame.rows)));
subframe *= 0.3f;
putText(frame, label, Point(20, 50), Scalar(0, 255, 0), sans, 25, 800);
// Print predicted class.
for (int i = 0; i < K; i++) {
int classId = prob_vec[i].second;
float confidence = -prob_vec[i].first;
label = format("%d. %s: %.2f", i+1, (classes.empty() ? format("Class #%d", classId).c_str() :
classes[classId].c_str()), confidence);
putText(frame, label, Point(20, 110 + i*35), Scalar(0, 255, 0), sans, 25, 500);
}
imshow(kWinName, frame);
int key = waitKey(isImgList ? 1000 : 100);
if (key == ' ')
key = waitKey();
if (key == 'q' || key == 27) // Check if 'q' or 'ESC' is pressed
return 0;
}
waitKey();
return 0;
}
+177
View File
@@ -0,0 +1,177 @@
import os
import glob
import argparse
import cv2 as cv
import numpy as np
import sys
from common import *
def help():
print(
'''
Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"\n
To run:
python classification.py model_name --input=path/to/your/input/image/or/video (don't give --input flag if want to use device camera)
Sample command:
python classification.py googlenet --input=path/to/image
Model path can also be specified using --model argument
'''
)
def get_args_parser(func_args):
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--input',
help='Path to input image or video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--crop', type=bool, default=False,
help='Center crop the image.')
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'classification')
parser = argparse.ArgumentParser(parents=[parser],
description='Use this script to run classification deep learning networks using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
return parser.parse_args(func_args)
def load_images(directory):
# List all common image file extensions, feel free to add more if needed
extensions = ['jpg', 'jpeg', 'png', 'bmp', 'tif', 'tiff']
files = []
for extension in extensions:
files.extend(glob.glob(os.path.join(directory, f'*.{extension}')))
return files
def main(func_args=None):
args = get_args_parser(func_args)
if args.alias is None or hasattr(args, 'help'):
help()
exit(1)
cv.utils.logging.setLogLevel(cv.utils.logging.LOG_LEVEL_INFO)
args.model = findModel(args.model, args.sha1)
args.labels = findFile(args.labels)
# Load names of classes
labels = None
if args.labels:
with open(args.labels, 'rt') as f:
labels = f.read().rstrip('\n').split('\n')
# Load a network
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
if hasattr(cv.dnn, 'DNN_PROFILE_SUMMARY'):
net.setProfilingMode(cv.dnn.DNN_PROFILE_SUMMARY)
winName = 'Deep learning image classification in OpenCV'
cv.namedWindow(winName, cv.WINDOW_NORMAL)
isdir = False
if args.input:
input_path = args.input
if os.path.isdir(input_path):
isdir = True
image_files = load_images(input_path)
if not image_files:
print("No images found in the directory.")
exit(-1)
current_image_index = 0
else:
input_path = findFile(input_path)
cap = cv.VideoCapture(input_path)
if not cap.isOpened():
print("Failed to open the input video")
exit(-1)
else:
cap = cv.VideoCapture(0)
while cv.waitKey(1) < 0:
if isdir:
if current_image_index >= len(image_files):
break
frame = cv.imread(image_files[current_image_index])
current_image_index += 1
else:
hasFrame, frame = cap.read()
if not hasFrame:
cv.waitKey()
break
# Create a 4D blob from a frame.
inpWidth = args.width if args.width else frame.shape[1]
inpHeight = args.height if args.height else frame.shape[0]
blob = cv.dnn.blobFromImage(frame, args.scale, (inpWidth, inpHeight), args.mean, args.rgb, crop=args.crop)
if args.std:
blob[0] /= np.asarray(args.std, dtype=np.float32).reshape(3, 1, 1)
# Run a model
net.setInput(blob)
t0 = cv.getTickCount()
out = net.forward()
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
net.printPerfProfile()
(h, w, _) = frame.shape
roi_rows = min(300, h)
roi_cols = min(1000, w)
frame[:roi_rows,:roi_cols,:] >>= 1
# Put efficiency information.
label = 'Inference time: %.1f ms' % (t * 1000.0)
cv.putText(frame, label, (15, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0))
# Print predicted classes.
out = out.flatten()
K = 5
topKidx = np.argpartition(out, -K)[-K:]
for i in range(K):
classId = topKidx[i]
confidence = out[classId]
label = '%s: %.2f' % (labels[classId] if labels else 'Class #%d' % classId, confidence)
cv.putText(frame, label, (15, 90 + i*30), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0))
cv.imshow(winName, frame)
key = cv.waitKey(1000 if isdir else 100)
if key >= 0:
key &= 255
if key == ord(' '):
key = cv.waitKey() & 255
if key == ord('q') or key == 27: # Wait for 1 second on each image, press 'q' to exit
sys.exit(0)
cv.waitKey()
if __name__ == "__main__":
main()
+121
View File
@@ -0,0 +1,121 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// To download the onnx model, see: https://storage.googleapis.com/ailia-models/colorization/colorizer.onnx
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include "common.hpp"
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
using namespace cv::dnn;
int main(int argc, char** argv) {
const string about =
"This sample demonstrates recoloring grayscale images with dnn.\n"
"This program is based on:\n"
" http://richzhang.github.io/colorization\n"
" https://github.com/richzhang/colorization\n"
"To download the onnx model:\n"
" https://storage.googleapis.com/ailia-models/colorization/colorizer.onnx\n";
const string param_keys =
"{ help h | | Print help message. }"
"{ input i | baboon.jpg | Path to the input image }"
"{ onnx_model_path | | Path to the ONNX model. Required. }";
const string backend_keys = format(
"{ backend | 0 | Choose one of computation backends: "
"%d: automatically (by default), "
"%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"%d: OpenCV implementation, "
"%d: VKCOM, "
"%d: CUDA, "
"%d: WebNN }",
cv::dnn::DNN_BACKEND_DEFAULT, cv::dnn::DNN_BACKEND_INFERENCE_ENGINE, cv::dnn::DNN_BACKEND_OPENCV,
cv::dnn::DNN_BACKEND_VKCOM, cv::dnn::DNN_BACKEND_CUDA, cv::dnn::DNN_BACKEND_WEBNN);
const string target_keys = format(
"{ target | 0 | Choose one of target computation devices: "
"%d: CPU target (by default), "
"%d: OpenCL, "
"%d: OpenCL fp16 (half-float precision), "
"%d: VPU, "
"%d: Vulkan, "
"%d: CUDA, "
"%d: CUDA fp16 (half-float preprocess) }",
cv::dnn::DNN_TARGET_CPU, cv::dnn::DNN_TARGET_OPENCL, cv::dnn::DNN_TARGET_OPENCL_FP16,
cv::dnn::DNN_TARGET_MYRIAD, cv::dnn::DNN_TARGET_VULKAN, cv::dnn::DNN_TARGET_CUDA,
cv::dnn::DNN_TARGET_CUDA_FP16);
const string keys = param_keys + backend_keys + target_keys;
CommandLineParser parser(argc, argv, keys);
parser.about(about);
if (parser.has("help")) {
parser.printMessage();
return 0;
}
string inputImagePath = parser.get<string>("input");
string onnxModelPath = parser.get<string>("onnx_model_path");
int backendId = parser.get<int>("backend");
int targetId = parser.get<int>("target");
if (onnxModelPath.empty()) {
cerr << "The path to the ONNX model is required!" << endl;
return -1;
}
Mat imgGray = imread(samples::findFile(inputImagePath), IMREAD_GRAYSCALE);
if (imgGray.empty()) {
cerr << "Could not read the image: " << inputImagePath << endl;
return -1;
}
Mat imgL = imgGray;
imgL.convertTo(imgL, CV_32F, 100.0/255.0);
Mat imgLResized;
resize(imgL, imgLResized, Size(256, 256), 0, 0, INTER_CUBIC);
// Prepare the model
EngineType engine = ENGINE_AUTO;
if (backendId != 0 || targetId != 0){
engine = ENGINE_CLASSIC;
}
dnn::Net net = dnn::readNetFromONNX(onnxModelPath, engine);
net.setPreferableBackend(backendId);
net.setPreferableTarget(targetId);
//! [Read and initialize network]
// Create blob from the image
Mat blob = dnn::blobFromImage(imgLResized, 1.0, Size(256, 256), Scalar(), false, false);
net.setInput(blob);
// Run inference
Mat result = net.forward();
Size siz(result.size[2], result.size[3]);
Mat a(siz, CV_32F, result.ptr(0,0));
Mat b(siz, CV_32F, result.ptr(0,1));
resize(a, a, imgGray.size());
resize(b, b, imgGray.size());
// merge, and convert back to BGR
Mat color, chn[] = {imgL, a, b};
// Proc
Mat lab;
merge(chn, 3, lab);
cvtColor(lab, color, COLOR_Lab2BGR);
imshow("input image", imgGray);
imshow("output image", color);
waitKey();
return 0;
}
+91
View File
@@ -0,0 +1,91 @@
# Script is based on https://github.com/richzhang/colorization/blob/master/colorization/colorize.py
# To download the onnx model, see: https://storage.googleapis.com/ailia-models/colorization/colorizer.onnx
# python colorization.py --onnx_model_path colorizer.onnx --input ansel_adams3.jpg
import numpy as np
import argparse
import cv2 as cv
import numpy as np
def parse_args():
backends = (cv.dnn.DNN_BACKEND_DEFAULT, cv.dnn.DNN_BACKEND_INFERENCE_ENGINE,
cv.dnn.DNN_BACKEND_OPENCV, cv.dnn.DNN_BACKEND_VKCOM, cv.dnn.DNN_BACKEND_CUDA)
targets = (cv.dnn.DNN_TARGET_CPU, cv.dnn.DNN_TARGET_OPENCL, cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD,
cv.dnn.DNN_TARGET_HDDL, cv.dnn.DNN_TARGET_VULKAN, cv.dnn.DNN_TARGET_CUDA, cv.dnn.DNN_TARGET_CUDA_FP16)
parser = argparse.ArgumentParser(description='iColor: deep interactive colorization')
parser.add_argument('--input', default='baboon.jpg',help='Path to image.')
parser.add_argument('--onnx_model_path', help='Path to onnx model', required=True)
parser.add_argument('--backend', choices=backends, default=cv.dnn.DNN_BACKEND_DEFAULT, type=int,
help="Choose one of computation backends: "
"%d: automatically (by default), "
"%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"%d: OpenCV implementation, "
"%d: VKCOM, "
"%d: CUDA" % backends)
parser.add_argument('--target', choices=targets, default=cv.dnn.DNN_TARGET_CPU, type=int,
help='Choose one of target computation devices: '
'%d: CPU target (by default), '
'%d: OpenCL, '
'%d: OpenCL fp16 (half-float precision), '
'%d: NCS2 VPU, '
'%d: HDDL VPU, '
'%d: Vulkan, '
'%d: CUDA, '
'%d: CUDA fp16 (half-float preprocess)'% targets)
args = parser.parse_args()
return args
if __name__ == '__main__':
args = parse_args()
img_gray=cv.imread(cv.samples.findFile(args.input),cv.IMREAD_GRAYSCALE)
img_gray_rs = cv.resize(img_gray, (256, 256), interpolation=cv.INTER_CUBIC)
img_gray_rs = img_gray_rs.astype(np.float32) # Convert to float to avoid data overflow
img_gray_rs *= (100.0 / 255.0) # Scale L channel to 0-100 range
onnx_model_path = args.onnx_model_path # Update this path to your ONNX model's path
engine = cv.dnn.ENGINE_AUTO
if args.backend != 0 or args.target != 0:
engine = cv.dnn.ENGINE_CLASSIC
session = cv.dnn.readNetFromONNX(onnx_model_path, engine)
session.setPreferableBackend(args.backend)
session.setPreferableTarget(args.target)
# Process each image in the batch (assuming batch processing is needed)
blob = cv.dnn.blobFromImage(img_gray_rs, swapRB=False) # Adjust swapRB according to your model's training
session.setInput(blob)
result_numpy = np.array(session.forward()[0])
if result_numpy.shape[0] == 2:
# Transpose result_numpy to shape (H, W, 2)
ab = result_numpy.transpose((1, 2, 0))
else:
# If it's already (H, W, 2), assign it directly
ab = result_numpy
# Resize ab to match img_gray's dimensions if they are not the same
h, w = img_gray.shape
if ab.shape[:2] != (h, w):
ab_resized = cv.resize(ab, (w, h), interpolation=cv.INTER_LINEAR)
else:
ab_resized = ab
# Expand dimensions of L to match ab's dimensions
img_l_expanded = np.expand_dims(img_gray, axis=-1)
# Concatenate L with AB to get the LAB image
lab_image = np.concatenate((img_l_expanded, ab_resized), axis=-1)
# Convert the Lab image to a 32-bit float format
lab_image = lab_image.astype(np.float32)
# Normalize L channel to the range [0, 100] and AB channels to the range [-127, 127]
lab_image[:, :, 0] *= (100.0 / 255.0) # Rescale L channel
#lab_image[:, :, 1:] -= 128 # Shift AB channels
# Convert the LAB image to BGR
image_bgr_out = cv.cvtColor(lab_image, cv.COLOR_Lab2BGR)
cv.imshow("input image",img_gray)
cv.imshow("output image",image_bgr_out)
cv.waitKey(0)
+218
View File
@@ -0,0 +1,218 @@
#include <opencv2/core/utils/filesystem.hpp>
#include<iostream>
using namespace cv;
std::string genArgument(const std::string& argName, const std::string& help,
const std::string& modelName, const std::string& zooFile,
char key = ' ', std::string defaultVal = "");
std::string genPreprocArguments(const std::string& modelName, const std::string& zooFile, const std::string& prefix);
std::string findFile(const std::string& filename);
std::string findModel(const std::string& filename, const std::string& sha1);
std::vector<std::string> findAliases(std::string& zooFile, const std::string& sampleType);
inline int getBackendID(const String& backend) {
std::map<String, int> backendIDs = {
{"default", cv::dnn::DNN_BACKEND_DEFAULT},
{"openvino", cv::dnn::DNN_BACKEND_INFERENCE_ENGINE},
{"opencv", cv::dnn::DNN_BACKEND_OPENCV},
{"vkcom", cv::dnn::DNN_BACKEND_VKCOM},
{"cuda", cv::dnn::DNN_BACKEND_CUDA},
{"webnn", cv::dnn::DNN_BACKEND_WEBNN}
};
if(backendIDs.find(backend) != backendIDs.end()){
return backendIDs[backend];
}else {
throw std::invalid_argument("Invalid backend name: " + backend);
}
}
inline int getTargetID(const String& target) {
std::map<String, int> targetIDs = {
{"cpu", cv::dnn::DNN_TARGET_CPU},
{"opencl", cv::dnn::DNN_TARGET_OPENCL},
{"opencl_fp16", cv::dnn::DNN_TARGET_OPENCL_FP16},
{"vpu", cv::dnn::DNN_TARGET_MYRIAD},
{"vulkan", cv::dnn::DNN_TARGET_VULKAN},
{"cuda", cv::dnn::DNN_TARGET_CUDA},
{"cuda_fp16", cv::dnn::DNN_TARGET_CUDA_FP16}
};
if(targetIDs.find(target) != targetIDs.end()){
return targetIDs[target];
}else {
throw std::invalid_argument("Invalid target name: " + target);
}
}
std::string genArgument(const std::string& argName, const std::string& help,
const std::string& modelName, const std::string& zooFile,
char key, std::string defaultVal)
{
if (!modelName.empty())
{
FileStorage fs(zooFile, FileStorage::READ);
if (fs.isOpened())
{
FileNode node = fs[modelName];
if (!node.empty())
{
FileNode value = node[argName];
if (argName.find("sha1") != std::string::npos) {
std::string prefix = argName.substr(0, argName.find("sha1"));
if (prefix == "config_"){
value = node[prefix+"load_info"]["sha1"];
}
else{
value = node[prefix+"load_info"][argName];
}
}
if (argName.find("download_sha") != std::string::npos) {
std::string prefix = argName.substr(0, argName.find("download_sha"));
value = node[prefix+"load_info"][argName];
}
if (!value.empty())
{
if (value.isReal())
defaultVal = format("%f", (float)value);
else if (value.isString())
defaultVal = (std::string)value;
else if (value.isInt())
defaultVal = format("%d", (int)value);
else if (value.isSeq())
{
for (size_t i = 0; i < value.size(); ++i)
{
FileNode v = value[(int)i];
if (v.isInt())
defaultVal += format("%d ", (int)v);
else if (v.isReal())
defaultVal += format("%f ", (float)v);
else
CV_Error(Error::StsNotImplemented, "Unexpected value format");
}
}
else
CV_Error(Error::StsNotImplemented, "Unexpected field format");
}
}
}
}
return "{ " + argName + " " + key + " | " + defaultVal + " | " + help + " }";
}
std::string findModel(const std::string& filename, const std::string& sha1)
{
if (filename.empty() || utils::fs::exists(filename))
return filename;
if(!getenv("OPENCV_DOWNLOAD_CACHE_DIR")){
std::cout<< "[WARN] Please specify a path to model download directory in OPENCV_DOWNLOAD_CACHE_DIR environment variable"<<std::endl;
return findFile(filename);
}
else{
std::string modelPath = utils::fs::join(getenv("OPENCV_DOWNLOAD_CACHE_DIR"), utils::fs::join(sha1, filename));
if (utils::fs::exists(modelPath))
return modelPath;
modelPath = utils::fs::join(getenv("OPENCV_DOWNLOAD_CACHE_DIR"),filename);
if (utils::fs::exists(modelPath))
return modelPath;
}
std::cout << "File " + filename + " not found! "
<< "Please specify a path to model download directory in OPENCV_DOWNLOAD_CACHE_DIR "
<< "environment variable or pass a full path to " + filename
<< std::endl;
std::exit(1);
}
std::string findFile(const std::string& filename)
{
if (filename.empty() || utils::fs::exists(filename))
return filename;
if(!getenv("OPENCV_SAMPLES_DATA_PATH")){
std::cout<< "[WARN] Please specify a path to opencv/samples/data in OPENCV_SAMPLES_DATA_PATH environment variable"<<std::endl;
}
else{
std::string samplePath = utils::fs::join(getenv("OPENCV_SAMPLES_DATA_PATH"), filename);
if (utils::fs::exists(samplePath))
return samplePath;
}
const char* extraPaths[] = {getenv("OPENCV_SAMPLES_DATA_PATH"),
getenv("OPENCV_DNN_TEST_DATA_PATH"),
getenv("OPENCV_TEST_DATA_PATH")};
for (int i = 0; i < 3; ++i)
{
if (extraPaths[i] == NULL)
continue;
std::string absPath = utils::fs::join(extraPaths[i], utils::fs::join("dnn", filename));
if (utils::fs::exists(absPath))
return absPath;
}
std::cout << "File " + filename + " not found! "
<< "Please specify the path to /opencv/samples/data in the OPENCV_SAMPLES_DATA_PATH environment variable, "
<< "or specify the path to opencv_extra/testdata in the OPENCV_DNN_TEST_DATA_PATH environment variable, "
<< "or specify the path to the model download cache directory in the OPENCV_DOWNLOAD_CACHE_DIR environment variable, "
<< "or pass the full path to " + filename + "."
<< std::endl;
std::exit(1);
}
std::string genPreprocArguments(const std::string& modelName, const std::string& zooFile, const std::string& prefix="")
{
return genArgument(prefix + "model", "Path to a binary file of model contains trained weights. "
"It could be a file with extensions .caffemodel (Caffe), "
".pb (TensorFlow), .bin (OpenVINO).",
modelName, zooFile, 'm') +
genArgument(prefix + "config", "Path to a text file of model contains network configuration. "
"It could be a file with extensions .prototxt (Caffe), .pbtxt (TensorFlow), .xml (OpenVINO).",
modelName, zooFile, 'c') +
genArgument(prefix + "mean", "Preprocess input image by subtracting mean values. Mean values should be in BGR order and delimited by spaces.",
modelName, zooFile) +
genArgument(prefix + "std", "Preprocess input image by dividing on a standard deviation.",
modelName, zooFile) +
genArgument(prefix + "scale", "Preprocess input image by multiplying on a scale factor.",
modelName, zooFile, ' ', "1.0") +
genArgument(prefix + "width", "Preprocess input image by resizing to a specific width.",
modelName, zooFile, ' ', "-1") +
genArgument(prefix + "height", "Preprocess input image by resizing to a specific height.",
modelName, zooFile, ' ', "-1") +
genArgument(prefix + "rgb", "Indicate that model works with RGB input images instead BGR ones.",
modelName, zooFile)+
genArgument(prefix + "labels", "Path to a text file with names of classes to label detected objects.",
modelName, zooFile)+
genArgument(prefix + "postprocessing", "Indicate the postprocessing type of model i.e. yolov8, yolonas, etc.",
modelName, zooFile)+
genArgument(prefix + "sha1", "Optional path to hashsum of downloaded model to be loaded from models.yml",
modelName, zooFile)+
genArgument(prefix + "config_sha1", "Optional path to hashsum of downloaded config to be loaded from models.yml",
modelName, zooFile)+
genArgument(prefix + "download_sha", "Optional path to hashsum of downloaded model to be loaded from models.yml",
modelName, zooFile);
}
std::vector<std::string> findAliases(std::string& zooFile, const std::string& sampleType) {
std::vector<std::string> aliases;
zooFile = findFile(zooFile);
cv::FileStorage fs(zooFile, cv::FileStorage::READ);
cv::FileNode root = fs.root();
for (const auto& node : root) {
std::string alias = node.name();
cv::FileNode sampleNode = node["sample"];
if (!sampleNode.empty() && sampleNode.isString()) {
std::string sampleValue = (std::string)sampleNode;
if (sampleValue == sampleType) {
aliases.push_back(alias);
}
}
}
return aliases;
}
+194
View File
@@ -0,0 +1,194 @@
import sys
import os
import cv2 as cv
def add_argument(zoo, parser, name, help, required=False, default=None, type=None, action=None, nargs=None, alias=None):
if alias is not None:
modelName = alias
elif len(sys.argv) > 1:
modelName = sys.argv[1]
else:
return
if os.path.isfile(zoo):
fs = cv.FileStorage(zoo, cv.FILE_STORAGE_READ)
node = fs.getNode(modelName)
if not node.empty():
value = node.getNode(name)
if "sha1" in name:
prefix = name.replace("sha1", "")
value = node.getNode(prefix + "load_info")
if prefix == "config_":
value = value.getNode("sha1")
else:
value = value.getNode(name)
if "download_sha" in name:
prefix = name.replace("download_sha", "")
value = node.getNode(prefix + "load_info")
value = value.getNode(name)
if not value.empty():
if value.isReal():
default = value.real()
elif value.isString():
default = value.string()
elif value.isInt():
default = int(value.real())
elif value.isSeq():
default = []
for i in range(value.size()):
v = value.at(i)
if v.isInt():
default.append(int(v.real()))
elif v.isReal():
default.append(v.real())
else:
print('Unexpected value format')
exit(0)
else:
print('Unexpected field format')
exit(0)
required = False
if action == 'store_true':
default = 1 if default == 'true' else (0 if default == 'false' else default)
assert(default is None or default == 0 or default == 1)
parser.add_argument('--' + name, required=required, help=help, default=bool(default),
action=action)
else:
parser.add_argument('--' + name, required=required, help=help, default=default,
action=action, nargs=nargs, type=type)
def add_preproc_args(zoo, parser, sample, alias=None, prefix=""):
aliases = []
if os.path.isfile(zoo) and prefix == "":
fs = cv.FileStorage(zoo, cv.FILE_STORAGE_READ)
root = fs.root()
for name in root.keys():
model = root.getNode(name)
if model.getNode('sample').string() == sample:
aliases.append(name)
if len(aliases):
parser.add_argument(prefix+'alias', nargs='?', choices=aliases,
help='An alias name of model to extract preprocessing parameters from models.yml file.')
add_argument(zoo, parser, prefix+'model',
help='Path to a binary file of model contains trained weights. '
'It could be a file with extensions .caffemodel (Caffe), '
'.pb (TensorFlow), .bin (OpenVINO)', alias=alias)
add_argument(zoo, parser, prefix+'config',
help='Path to a text file of model contains network configuration. '
'It could be a file with extensions .prototxt (Caffe), .pbtxt or .config (TensorFlow), .xml (OpenVINO)', alias=alias)
add_argument(zoo, parser, prefix+'mean', nargs='+', type=float, default=[0, 0, 0],
help='Preprocess input image by subtracting mean values. '
'Mean values should be in BGR order.', alias=alias)
add_argument(zoo, parser, prefix+'std', nargs='+', type=float, default=[0, 0, 0],
help='Preprocess input image by dividing on a standard deviation.', alias=alias)
add_argument(zoo, parser, prefix+'scale', type=float, default=1.0,
help='Preprocess input image by multiplying on a scale factor.', alias=alias)
add_argument(zoo, parser, prefix+'width', type=int,
help='Preprocess input image by resizing to a specific width.', alias=alias)
add_argument(zoo, parser, prefix+'height', type=int,
help='Preprocess input image by resizing to a specific height.', alias=alias)
add_argument(zoo, parser, prefix+'rgb', action='store_true',
help='Indicate that model works with RGB input images instead BGR ones.', alias=alias)
add_argument(zoo, parser, prefix+'labels',
help='Optional path to a text file with names of labels to label detected objects.', alias=alias)
add_argument(zoo, parser, prefix+'postprocessing', type=str,
help='Post-processing kind depends on model topology.', alias=alias)
add_argument(zoo, parser, prefix+'background_label_id', type=int, default=-1,
help='An index of background class in predictions. If not negative, exclude such class from list of classes.', alias=alias)
add_argument(zoo, parser, prefix+'sha1', type=str,
help='Optional path to hashsum of downloaded model to be loaded from models.yml', alias=alias)
add_argument(zoo, parser, prefix+'config_sha1', type=str,
help='Optional path to hashsum of downloaded config to be loaded from models.yml', alias=alias)
add_argument(zoo, parser, prefix+'download_sha', type=str,
help='Optional path to hashsum of downloaded model to be loaded from models.yml', alias=alias)
def findModel(filename, sha1):
if filename:
if os.path.exists(filename):
return filename
fpath = cv.samples.findFile(filename, False)
if fpath:
return fpath
if os.getenv('OPENCV_DOWNLOAD_CACHE_DIR') is None:
print('[WARN] Please specify a path to model download directory in OPENCV_DOWNLOAD_CACHE_DIR environment variable.')
return findFile(filename)
if os.path.exists(os.path.join(os.environ['OPENCV_DOWNLOAD_CACHE_DIR'], sha1, filename)):
return os.path.join(os.environ['OPENCV_DOWNLOAD_CACHE_DIR'], sha1, filename)
if os.path.exists(os.path.join(os.environ['OPENCV_DOWNLOAD_CACHE_DIR'], filename)):
return os.path.join(os.environ['OPENCV_DOWNLOAD_CACHE_DIR'], filename)
raise FileNotFoundError('File ' + filename + ' not found! Please specify a path to '
'model download directory in OPENCV_DOWNLOAD_CACHE_DIR '
'environment variable or pass a full path to ' + filename)
def findFile(filename):
if filename:
if os.path.exists(filename):
return filename
fpath = cv.samples.findFile(filename, False)
if fpath:
return fpath
if os.getenv('OPENCV_SAMPLES_DATA_PATH') is None:
print('[WARN] Please specify a path to `/samples/data` in OPENCV_SAMPLES_DATA_PATH environment variable.')
exit(0)
if os.path.exists(os.path.join(os.environ['OPENCV_SAMPLES_DATA_PATH'], filename)):
return os.path.join(os.environ['OPENCV_SAMPLES_DATA_PATH'], filename)
for path in ['OPENCV_DNN_TEST_DATA_PATH', 'OPENCV_TEST_DATA_PATH', 'OPENCV_SAMPLES_DATA_PATH']:
try:
extraPath = os.environ[path]
absPath = os.path.join(extraPath, 'dnn', filename)
if os.path.exists(absPath):
return absPath
except KeyError:
pass
raise FileNotFoundError(
'File ' + filename + ' not found! Please specify the path to '
'/opencv/samples/data in the OPENCV_SAMPLES_DATA_PATH environment variable, '
'or specify the path to opencv_extra/testdata in the OPENCV_DNN_TEST_DATA_PATH environment variable, '
'or specify the path to the model download cache directory in the OPENCV_DOWNLOAD_CACHE_DIR environment variable, '
'or pass the full path to ' + filename + '.'
)
def get_backend_id(backend_name):
backend_ids = {
"default": cv.dnn.DNN_BACKEND_DEFAULT,
"openvino": cv.dnn.DNN_BACKEND_INFERENCE_ENGINE,
"opencv": cv.dnn.DNN_BACKEND_OPENCV,
"vkcom": cv.dnn.DNN_BACKEND_VKCOM,
"cuda": cv.dnn.DNN_BACKEND_CUDA
}
if backend_name not in backend_ids:
raise ValueError(f"Invalid backend name: {backend_name}")
return backend_ids[backend_name]
def get_target_id(target_name):
target_ids = {
"cpu": cv.dnn.DNN_TARGET_CPU,
"opencl": cv.dnn.DNN_TARGET_OPENCL,
"opencl_fp16": cv.dnn.DNN_TARGET_OPENCL_FP16,
"ncs2_vpu": cv.dnn.DNN_TARGET_MYRIAD,
"hddl_vpu": cv.dnn.DNN_TARGET_HDDL,
"vulkan": cv.dnn.DNN_TARGET_VULKAN,
"cuda": cv.dnn.DNN_TARGET_CUDA,
"cuda_fp16": cv.dnn.DNN_TARGET_CUDA_FP16
}
if target_name not in target_ids:
raise ValueError(f"Invalid target name: {target_name}")
return target_ids[target_name]
+31
View File
@@ -0,0 +1,31 @@
import cv2 as cv
#! [CropLayer]
class CropLayer(object):
def __init__(self, params, blobs):
self.xstart = 0
self.xend = 0
self.ystart = 0
self.yend = 0
# Our layer receives two inputs. We need to crop the first input blob
# to match a shape of the second one (keeping batch size and number of channels)
def getMemoryShapes(self, inputs):
inputShape, targetShape = inputs[0], inputs[1]
batchSize, numChannels = inputShape[0], inputShape[1]
height, width = targetShape[2], targetShape[3]
self.ystart = (inputShape[2] - targetShape[2]) // 2
self.xstart = (inputShape[3] - targetShape[3]) // 2
self.yend = self.ystart + height
self.xend = self.xstart + width
return [[batchSize, numChannels, height, width]]
def forward(self, inputs):
return [inputs[0][:,:,self.ystart:self.yend,self.xstart:self.xend]]
#! [CropLayer]
#! [Register]
cv.dnn_registerLayer('Crop', CropLayer)
#! [Register]
+121
View File
@@ -0,0 +1,121 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
// Copyright (C) 2026, BigVision LLC, all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Companion sample for tutorial:
// doc/tutorials/dnn/dnn_custom_layers/dnn_custom_layers.md (ONNX section)
//
// Models used by this sample are generated by:
// opencv_extra/testdata/dnn/onnx/generate_custom_layer_models.py
// and live at:
// opencv_extra/testdata/dnn/onnx/models/custom_layer_default_domain.onnx
// opencv_extra/testdata/dnn/onnx/models/custom_layer_custom_domain.onnx
#include <opencv2/dnn.hpp>
#include <opencv2/core.hpp>
#include <iostream>
#include <vector>
#include <string>
using namespace cv;
using namespace cv::dnn;
using namespace std;
//! [CustomScaleBiasLayer]
// y = scale * x + bias, with scale/bias read from ONNX node attributes.
class CustomScaleBiasLayer CV_FINAL : public Layer
{
public:
CustomScaleBiasLayer(const LayerParams& params) : Layer(params)
{
scale = params.get<float>("scale", 1.f);
bias = params.get<float>("bias", 0.f);
}
static Ptr<Layer> create(LayerParams& params)
{
return makePtr<CustomScaleBiasLayer>(params);
}
bool getMemoryShapes(const vector<MatShape>& inpts,
const int /*requiredOutputs*/,
vector<MatShape>& outShapes,
vector<MatShape>& /*internals*/) const CV_OVERRIDE
{
outShapes.assign(1, inpts[0]);
return false;
}
void forward(InputArrayOfArrays inputs_arr, OutputArrayOfArrays outputs_arr,
OutputArrayOfArrays) CV_OVERRIDE
{
vector<Mat> inps, outs;
inputs_arr.getMatVector(inps);
outputs_arr.getMatVector(outs);
inps[0].convertTo(outs[0], outs[0].type(), scale, bias);
}
private:
float scale, bias;
};
//! [CustomScaleBiasLayer]
static void printShape(const string& tag, const Mat& m)
{
cout << tag;
for (int d = 0; d < m.dims; ++d)
cout << (d ? "x" : "") << m.size[d];
cout << "\n";
}
int main(int argc, char** argv)
{
const string keys =
"{ help h | | Print help message }"
"{ model m | | Path to ONNX model containing the custom op }"
"{ op o | MyCustomOp | Op key for registration. Default-domain: just the op_type "
"(e.g. MyCustomOp). Custom-domain: <domain>.<op_type> "
"(e.g. my.namespace.MyDomainOp) }";
CommandLineParser parser(argc, argv, keys);
parser.about("Demonstrates importing an ONNX model that contains a custom (non-standard) op "
"by registering a user-defined layer with cv::dnn::LayerFactory.");
if (parser.has("help") || !parser.has("model"))
{
parser.printMessage();
return 0;
}
const string modelPath = parser.get<string>("model");
const string opKey = parser.get<string>("op");
//! [Register CustomScaleBiasLayer]
// ONNX op-type lookup: layers in the default `ai.onnx` domain are registered
// under their op_type; layers in a non-default domain are registered under
// "<domain>.<op_type>" (e.g. "my.namespace.MyDomainOp").
LayerFactory::registerLayer(opKey, CustomScaleBiasLayer::create);
//! [Register CustomScaleBiasLayer]
Net net = readNetFromONNX(modelPath);
if (net.empty())
{
cerr << "Failed to load model: " << modelPath << "\n";
LayerFactory::unregisterLayer(opKey);
return 1;
}
// The companion ONNX models accept a 1x3x4x4 float input.
Mat input(vector<int>{1, 3, 4, 4}, CV_32F, Scalar(1.0f));
net.setInput(input);
Mat out = net.forward();
cout << "Loaded " << modelPath << " using custom op key '" << opKey << "'.\n";
printShape("Input shape: ", input);
printShape("Output shape: ", out);
cout << "Output[0,0,0,0] = " << out.ptr<float>()[0]
<< " (= scale * 1.0 + bias for the registered op)\n";
LayerFactory::unregisterLayer(opKey);
return 0;
}
+283
View File
@@ -0,0 +1,283 @@
#ifndef __OPENCV_SAMPLES_DNN_CUSTOM_LAYERS__
#define __OPENCV_SAMPLES_DNN_CUSTOM_LAYERS__
#include <opencv2/dnn.hpp>
#include <opencv2/dnn/shape_utils.hpp> // getPlane
//! [InterpLayer]
class InterpLayer : public cv::dnn::Layer
{
public:
InterpLayer(const cv::dnn::LayerParams &params) : Layer(params)
{
outWidth = params.get<int>("width", 0);
outHeight = params.get<int>("height", 0);
}
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params)
{
return cv::Ptr<cv::dnn::Layer>(new InterpLayer(params));
}
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
const int requiredOutputs,
std::vector<std::vector<int> > &outputs,
std::vector<std::vector<int> > &internals) const CV_OVERRIDE
{
CV_UNUSED(requiredOutputs); CV_UNUSED(internals);
std::vector<int> outShape(4);
outShape[0] = inputs[0][0]; // batch size
outShape[1] = inputs[0][1]; // number of channels
outShape[2] = outHeight;
outShape[3] = outWidth;
outputs.assign(1, outShape);
return false;
}
// Implementation of this custom layer is based on https://github.com/cdmh/deeplab-public/blob/master/src/caffe/layers/interp_layer.cpp
virtual void forward(cv::InputArrayOfArrays inputs_arr,
cv::OutputArrayOfArrays outputs_arr,
cv::OutputArrayOfArrays internals_arr) CV_OVERRIDE
{
if (inputs_arr.depth() == CV_16S)
{
// In case of DNN_TARGET_OPENCL_FP16 target the following method
// converts data from FP16 to FP32 and calls this forward again.
forward_fallback(inputs_arr, outputs_arr, internals_arr);
return;
}
std::vector<cv::Mat> inputs, outputs;
inputs_arr.getMatVector(inputs);
outputs_arr.getMatVector(outputs);
cv::Mat& inp = inputs[0];
cv::Mat& out = outputs[0];
const float* inpData = (float*)inp.data;
float* outData = (float*)out.data;
const int batchSize = inp.size[0];
const int numChannels = inp.size[1];
const int inpHeight = inp.size[2];
const int inpWidth = inp.size[3];
const float rheight = (outHeight > 1) ? static_cast<float>(inpHeight - 1) / (outHeight - 1) : 0.f;
const float rwidth = (outWidth > 1) ? static_cast<float>(inpWidth - 1) / (outWidth - 1) : 0.f;
for (int h2 = 0; h2 < outHeight; ++h2)
{
const float h1r = rheight * h2;
const int h1 = static_cast<int>(h1r);
const int h1p = (h1 < inpHeight - 1) ? 1 : 0;
const float h1lambda = h1r - h1;
const float h0lambda = 1.f - h1lambda;
for (int w2 = 0; w2 < outWidth; ++w2)
{
const float w1r = rwidth * w2;
const int w1 = static_cast<int>(w1r);
const int w1p = (w1 < inpWidth - 1) ? 1 : 0;
const float w1lambda = w1r - w1;
const float w0lambda = 1.f - w1lambda;
const float* pos1 = inpData + h1 * inpWidth + w1;
float* pos2 = outData + h2 * outWidth + w2;
for (int c = 0; c < batchSize * numChannels; ++c)
{
pos2[0] =
h0lambda * (w0lambda * pos1[0] + w1lambda * pos1[w1p]) +
h1lambda * (w0lambda * pos1[h1p * inpWidth] + w1lambda * pos1[h1p * inpWidth + w1p]);
pos1 += inpWidth * inpHeight;
pos2 += outWidth * outHeight;
}
}
}
}
private:
int outWidth, outHeight;
};
//! [InterpLayer]
//! [ResizeBilinearLayer]
class ResizeBilinearLayer CV_FINAL : public cv::dnn::Layer
{
public:
ResizeBilinearLayer(const cv::dnn::LayerParams &params) : Layer(params)
{
CV_Assert(!params.get<bool>("align_corners", false));
CV_Assert(!blobs.empty());
for (size_t i = 0; i < blobs.size(); ++i)
CV_Assert(blobs[i].type() == CV_32SC1);
// There are two cases of input blob: a single blob which contains output
// shape and two blobs with scaling factors.
if (blobs.size() == 1)
{
CV_Assert(blobs[0].total() == 2);
outHeight = blobs[0].at<int>(0, 0);
outWidth = blobs[0].at<int>(0, 1);
factorHeight = factorWidth = 0;
}
else
{
CV_Assert(blobs.size() == 2); CV_Assert(blobs[0].total() == 1); CV_Assert(blobs[1].total() == 1);
factorHeight = blobs[0].at<int>(0, 0);
factorWidth = blobs[1].at<int>(0, 0);
outHeight = outWidth = 0;
}
}
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params)
{
return cv::Ptr<cv::dnn::Layer>(new ResizeBilinearLayer(params));
}
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
const int,
std::vector<std::vector<int> > &outputs,
std::vector<std::vector<int> > &) const CV_OVERRIDE
{
std::vector<int> outShape(4);
outShape[0] = inputs[0][0]; // batch size
outShape[1] = inputs[0][1]; // number of channels
outShape[2] = outHeight != 0 ? outHeight : (inputs[0][2] * factorHeight);
outShape[3] = outWidth != 0 ? outWidth : (inputs[0][3] * factorWidth);
outputs.assign(1, outShape);
return false;
}
virtual void finalize(cv::InputArrayOfArrays, cv::OutputArrayOfArrays outputs_arr) CV_OVERRIDE
{
std::vector<cv::Mat> outputs;
outputs_arr.getMatVector(outputs);
if (!outWidth && !outHeight)
{
outHeight = outputs[0].size[2];
outWidth = outputs[0].size[3];
}
}
// This implementation is based on a reference implementation from
// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h
virtual void forward(cv::InputArrayOfArrays inputs_arr,
cv::OutputArrayOfArrays outputs_arr,
cv::OutputArrayOfArrays internals_arr) CV_OVERRIDE
{
if (inputs_arr.depth() == CV_16S)
{
// In case of DNN_TARGET_OPENCL_FP16 target the following method
// converts data from FP16 to FP32 and calls this forward again.
forward_fallback(inputs_arr, outputs_arr, internals_arr);
return;
}
std::vector<cv::Mat> inputs, outputs;
inputs_arr.getMatVector(inputs);
outputs_arr.getMatVector(outputs);
cv::Mat& inp = inputs[0];
cv::Mat& out = outputs[0];
const float* inpData = (float*)inp.data;
float* outData = (float*)out.data;
const int batchSize = inp.size[0];
const int numChannels = inp.size[1];
const int inpHeight = inp.size[2];
const int inpWidth = inp.size[3];
float heightScale = static_cast<float>(inpHeight) / outHeight;
float widthScale = static_cast<float>(inpWidth) / outWidth;
for (int b = 0; b < batchSize; ++b)
{
for (int y = 0; y < outHeight; ++y)
{
float input_y = y * heightScale;
int y0 = static_cast<int>(std::floor(input_y));
int y1 = std::min(y0 + 1, inpHeight - 1);
for (int x = 0; x < outWidth; ++x)
{
float input_x = x * widthScale;
int x0 = static_cast<int>(std::floor(input_x));
int x1 = std::min(x0 + 1, inpWidth - 1);
for (int c = 0; c < numChannels; ++c)
{
float interpolation =
inpData[offset(inp.size, c, x0, y0, b)] * (1 - (input_y - y0)) * (1 - (input_x - x0)) +
inpData[offset(inp.size, c, x0, y1, b)] * (input_y - y0) * (1 - (input_x - x0)) +
inpData[offset(inp.size, c, x1, y0, b)] * (1 - (input_y - y0)) * (input_x - x0) +
inpData[offset(inp.size, c, x1, y1, b)] * (input_y - y0) * (input_x - x0);
outData[offset(out.size, c, x, y, b)] = interpolation;
}
}
}
}
}
private:
static inline int offset(const cv::MatSize& size, int c, int x, int y, int b)
{
return x + size[3] * (y + size[2] * (c + size[1] * b));
}
int outWidth, outHeight, factorWidth, factorHeight;
};
//! [ResizeBilinearLayer]
//
// The following code is used only to generate tutorials documentation.
//
//! [A custom layer interface]
class MyLayer : public cv::dnn::Layer
{
public:
//! [MyLayer::MyLayer]
MyLayer(const cv::dnn::LayerParams &params);
//! [MyLayer::MyLayer]
//! [MyLayer::create]
static cv::Ptr<cv::dnn::Layer> create(cv::dnn::LayerParams& params);
//! [MyLayer::create]
//! [MyLayer::getMemoryShapes]
virtual bool getMemoryShapes(const std::vector<std::vector<int> > &inputs,
const int requiredOutputs,
std::vector<std::vector<int> > &outputs,
std::vector<std::vector<int> > &internals) const CV_OVERRIDE;
//! [MyLayer::getMemoryShapes]
//! [MyLayer::forward]
virtual void forward(cv::InputArrayOfArrays inputs,
cv::OutputArrayOfArrays outputs,
cv::OutputArrayOfArrays internals) CV_OVERRIDE;
//! [MyLayer::forward]
//! [MyLayer::finalize]
virtual void finalize(cv::InputArrayOfArrays inputs,
cv::OutputArrayOfArrays outputs) CV_OVERRIDE;
//! [MyLayer::finalize]
};
//! [A custom layer interface]
//! [Register a custom layer]
#include <opencv2/dnn/layer.details.hpp> // CV_DNN_REGISTER_LAYER_CLASS
static inline void loadNet()
{
CV_DNN_REGISTER_LAYER_CLASS(Interp, InterpLayer);
// ...
//! [Register a custom layer]
//! [Register InterpLayer]
CV_DNN_REGISTER_LAYER_CLASS(Interp, InterpLayer);
cv::dnn::Net caffeNet = cv::dnn::readNet("/path/to/config.prototxt", "/path/to/weights.caffemodel");
//! [Register InterpLayer]
//! [Register ResizeBilinearLayer]
CV_DNN_REGISTER_LAYER_CLASS(ResizeBilinear, ResizeBilinearLayer);
cv::dnn::Net tfNet = cv::dnn::readNet("/path/to/graph.pb");
//! [Register ResizeBilinearLayer]
if (false) loadNet(); // To prevent unused function warning.
}
#endif // __OPENCV_SAMPLES_DNN_CUSTOM_LAYERS__
+136
View File
@@ -0,0 +1,136 @@
/*
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory
of this distribution and at http://opencv.org/license.html.
This sample deblurs the given blurry image.
Copyright (C) 2025, Bigvision LLC.
How to use:
Sample command to run:
`./example_dnn_deblurring`
You can download NAFNet deblurring model using
`python download_models.py NAFNet`
References:
Github: https://github.com/megvii-research/NAFNet
PyTorch model: https://drive.google.com/file/d/14D4V4raNYIOhETfcuuLI3bGLB-OYIv6X/view
PyTorch model was converted to ONNX and then ONNX model was further quantized using block quantization from [opencv_zoo](https://github.com/opencv/opencv_zoo/blob/main/tools/quantize/block_quantize.py)
Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
*/
#include <iostream>
#include <fstream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/dnn.hpp>
#include "common.hpp"
using namespace cv;
using namespace dnn;
using namespace std;
const string about = "Use this script for image deblurring using OpenCV. \n\n"
"Firstly, download required models i.e. NAFNet using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
"To run:\n"
"\t Example: ./example_dnn_deblurring [--input=<image_name>] \n\n"
"Deblurring model path can also be specified using --model argument.\n\n";
const string param_keys =
"{ help h | | show help message}"
"{ @alias | NAFNet | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ input i | licenseplate_motion.jpg | image file path}";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
int main(int argc, char **argv)
{
CommandLineParser parser(argc, argv, keys);
if (!parser.has("@alias") || parser.has("help"))
{
cout<<about<<endl;
parser.printMessage();
return 0;
}
string modelName = parser.get<String>("@alias");
string zooFile = findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
parser.about("Use this script to run image deblurring using OpenCV.");
const string sha1 = parser.get<String>("sha1");
const string modelPath = findModel(parser.get<String>("model"), sha1);
string imgPath = parser.get<String>("input");
const string backend = parser.get<String>("backend");
const string target = parser.get<String>("target");
float scale = parser.get<float>("scale");
bool swapRB = parser.get<bool>("rgb");
Scalar mean_v = parser.get<Scalar>("mean");
EngineType engine = ENGINE_AUTO;
if (backend != "default" || target != "cpu"){
engine = ENGINE_CLASSIC;
}
Net net = readNetFromONNX(modelPath, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
Mat inputImage = imread(findFile(imgPath));
if (inputImage.empty()) {
cerr << "Error: Input image could not be loaded." << endl;
return -1;
}
Mat image = inputImage.clone();
Mat image_blob = blobFromImage(image, scale, Size(image.cols, image.rows), mean_v, swapRB, false);
net.setInput(image_blob);
Mat output = net.forward();
// Post Processing
Mat output_transposed(3, &output.size[1], CV_32F, output.ptr<float>());
vector<Mat> channels = {
Mat(output_transposed.size[1], output_transposed.size[2], CV_32F, output_transposed.ptr<float>(2)),
Mat(output_transposed.size[1], output_transposed.size[2], CV_32F, output_transposed.ptr<float>(1)),
Mat(output_transposed.size[1], output_transposed.size[2], CV_32F, output_transposed.ptr<float>(0))
};
Mat outputImage;
merge(channels, outputImage);
outputImage.convertTo(outputImage, CV_8UC3, 255.0);
imshow("Input Image", inputImage);
imshow("Output Image", outputImage);
waitKey(0);
return 0;
}
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python
'''
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory
of this distribution and at http://opencv.org/license.html.
This sample deblurs the given blurry image.
Copyright (C) 2025, Bigvision LLC.
How to use:
Sample command to run:
`python deblurring.py`
You can download NAFNet deblurring model using
`python download_models.py NAFNet`
References:
Github: https://github.com/megvii-research/NAFNet
PyTorch model: https://drive.google.com/file/d/14D4V4raNYIOhETfcuuLI3bGLB-OYIv6X/view
PyTorch model was converted to ONNX and then ONNX model was further quantized using block quantization from [opencv_zoo](https://github.com/opencv/opencv_zoo/blob/main/tools/quantize/block_quantize.py)
Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
'''
import argparse
import cv2 as cv
import numpy as np
from common import *
def help():
print(
'''
Use this script for image deblurring using OpenCV.
Firstly, download required models i.e. NAFNet using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
To run:
Example: python deblurring.py [--input=<image_name>]
Deblurring model path can also be specified using --model argument.
'''
)
def get_args_parser():
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--input', '-i', default="licenseplate_motion.jpg", help='Path to image file.', required=False)
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'deblurring', prefix="", alias="NAFNet")
parser = argparse.ArgumentParser(parents=[parser],
description='Image deblurring using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
return parser.parse_args()
def main():
if hasattr(args, 'help'):
help()
exit(1)
args.model = findModel(args.model, args.sha1)
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
input_image = cv.imread(findFile(args.input))
image = input_image.copy()
height, width = image.shape[:2]
image_blob = cv.dnn.blobFromImage(image, args.scale, (width, height), args.mean, args.rgb, False)
net.setInput(image_blob)
out = net.forward()
# Postprocessing
output = out[0]
output = np.transpose(output, (1, 2, 0))
output = np.clip(output * 255.0, 0, 255).astype(np.uint8)
out_image = cv.cvtColor(output, cv.COLOR_RGB2BGR)
cv.imshow("input image: ", input_image)
cv.imshow("output image: ", out_image)
cv.waitKey(0)
if __name__ == '__main__':
args = get_args_parser()
main()
@@ -0,0 +1,23 @@
from abc import ABC, ABCMeta, abstractmethod
class AbstractModel(ABC):
@abstractmethod
def get_prepared_models(self):
pass
class Framework(object):
in_blob_name = ''
out_blob_name = ''
__metaclass__ = ABCMeta
@abstractmethod
def get_name(self):
pass
@abstractmethod
def get_output(self, input_blob):
pass
@@ -0,0 +1,96 @@
import sys
import time
import numpy as np
from ...utils import get_final_summary_info
class ClsAccEvaluation:
log = sys.stdout
img_classes = {}
batch_size = 0
def __init__(self, log_path, img_classes_file, batch_size):
self.log = open(log_path, 'w')
self.img_classes = self.read_classes(img_classes_file)
self.batch_size = batch_size
# collect the accuracies for both models
self.general_quality_metric = []
self.general_inference_time = []
@staticmethod
def read_classes(img_classes_file):
result = {}
with open(img_classes_file) as file:
for l in file.readlines():
result[l.split()[0]] = int(l.split()[1])
return result
def get_correct_answers(self, img_list, net_output_blob):
correct_answers = 0
for i in range(len(img_list)):
indexes = np.argsort(net_output_blob[i])[-5:]
correct_index = self.img_classes[img_list[i]]
if correct_index in indexes:
correct_answers += 1
return correct_answers
def process(self, frameworks, data_fetcher):
sorted_imgs_names = sorted(self.img_classes.keys())
correct_answers = [0] * len(frameworks)
samples_handled = 0
blobs_l1_diff = [0] * len(frameworks)
blobs_l1_diff_count = [0] * len(frameworks)
blobs_l_inf_diff = [sys.float_info.min] * len(frameworks)
inference_time = [0.0] * len(frameworks)
for x in range(0, len(sorted_imgs_names), self.batch_size):
sublist = sorted_imgs_names[x:x + self.batch_size]
batch = data_fetcher.get_batch(sublist)
samples_handled += len(sublist)
fw_accuracy = []
fw_time = []
frameworks_out = []
for i in range(len(frameworks)):
start = time.time()
out = frameworks[i].get_output(batch)
end = time.time()
correct_answers[i] += self.get_correct_answers(sublist, out)
fw_accuracy.append(100 * correct_answers[i] / float(samples_handled))
frameworks_out.append(out)
inference_time[i] += end - start
fw_time.append(inference_time[i] / samples_handled * 1000)
print(samples_handled, 'Accuracy for', frameworks[i].get_name() + ':', fw_accuracy[i], file=self.log)
print("Inference time, ms ", frameworks[i].get_name(), fw_time[i], file=self.log)
self.general_quality_metric.append(fw_accuracy)
self.general_inference_time.append(fw_time)
for i in range(1, len(frameworks)):
log_str = frameworks[0].get_name() + " vs " + frameworks[i].get_name() + ':'
diff = np.abs(frameworks_out[0] - frameworks_out[i])
l1_diff = np.sum(diff) / diff.size
print(samples_handled, "L1 difference", log_str, l1_diff, file=self.log)
blobs_l1_diff[i] += l1_diff
blobs_l1_diff_count[i] += 1
if np.max(diff) > blobs_l_inf_diff[i]:
blobs_l_inf_diff[i] = np.max(diff)
print(samples_handled, "L_INF difference", log_str, blobs_l_inf_diff[i], file=self.log)
self.log.flush()
for i in range(1, len(blobs_l1_diff)):
log_str = frameworks[0].get_name() + " vs " + frameworks[i].get_name() + ':'
print('Final l1 diff', log_str, blobs_l1_diff[i] / blobs_l1_diff_count[i], file=self.log)
print(
get_final_summary_info(
self.general_quality_metric,
self.general_inference_time,
"accuracy"
),
file=self.log
)
@@ -0,0 +1,87 @@
import os
from abc import ABCMeta, abstractmethod
import cv2
import numpy as np
from ...img_utils import read_rgb_img, get_pytorch_preprocess
from ...test.configs.default_preprocess_config import PYTORCH_RSZ_HEIGHT, PYTORCH_RSZ_WIDTH
class DataFetch(object):
imgs_dir = ''
frame_size = 0
bgr_to_rgb = False
__metaclass__ = ABCMeta
@abstractmethod
def preprocess(self, img):
pass
@staticmethod
def reshape_img(img):
img = img[:, :, 0:3].transpose(2, 0, 1)
return np.expand_dims(img, 0)
def center_crop(self, img):
cols = img.shape[1]
rows = img.shape[0]
y1 = round((rows - self.frame_size) / 2)
y2 = round(y1 + self.frame_size)
x1 = round((cols - self.frame_size) / 2)
x2 = round(x1 + self.frame_size)
return img[y1:y2, x1:x2]
def initial_preprocess(self, img):
min_dim = min(img.shape[-3], img.shape[-2])
resize_ratio = self.frame_size / float(min_dim)
img = cv2.resize(img, (0, 0), fx=resize_ratio, fy=resize_ratio)
img = self.center_crop(img)
return img
def get_preprocessed_img(self, img_path):
image_data = read_rgb_img(img_path, self.bgr_to_rgb)
image_data = self.preprocess(image_data)
return self.reshape_img(image_data)
def get_batch(self, img_names):
assert type(img_names) is list
batch = np.zeros((len(img_names), 3, self.frame_size, self.frame_size)).astype(np.float32)
for i in range(len(img_names)):
img_name = img_names[i]
img_file = os.path.join(self.imgs_dir, img_name)
assert os.path.exists(img_file)
batch[i] = self.get_preprocessed_img(img_file)
return batch
class PyTorchPreprocessedFetch(DataFetch):
def __init__(self, pytorch_cls_config, preprocess_input=None):
self.imgs_dir = pytorch_cls_config.img_root_dir
self.frame_size = pytorch_cls_config.frame_size
self.bgr_to_rgb = pytorch_cls_config.bgr_to_rgb
self.preprocess_input = preprocess_input
def preprocess(self, img):
img = cv2.resize(img, (PYTORCH_RSZ_WIDTH, PYTORCH_RSZ_HEIGHT))
img = self.center_crop(img)
if self.preprocess_input:
return self.presprocess_input(img)
return get_pytorch_preprocess(img)
class TFPreprocessedFetch(DataFetch):
def __init__(self, tf_cls_config, preprocess_input):
self.imgs_dir = tf_cls_config.img_root_dir
self.frame_size = tf_cls_config.frame_size
self.bgr_to_rgb = tf_cls_config.bgr_to_rgb
self.preprocess_input = preprocess_input
def preprocess(self, img):
img = self.initial_preprocess(img)
return self.preprocess_input(img)
@@ -0,0 +1,19 @@
import cv2
import numpy as np
from .test.configs.default_preprocess_config import BASE_IMG_SCALE_FACTOR
def read_rgb_img(img_file, is_bgr_to_rgb=True):
img = cv2.imread(img_file, cv2.IMREAD_COLOR)
if is_bgr_to_rgb:
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img
def get_pytorch_preprocess(img):
img = img.astype(np.float32)
img *= BASE_IMG_SCALE_FACTOR
img -= [0.485, 0.456, 0.406]
img /= [0.229, 0.224, 0.225]
return img
@@ -0,0 +1,60 @@
from .configs.test_config import TestClsConfig, TestClsModuleConfig
from .model_test_pipeline import ModelTestPipeline
from ..evaluation.classification.cls_accuracy_evaluator import ClsAccEvaluation
from ..utils import get_test_module
class ClsModelTestPipeline(ModelTestPipeline):
def __init__(
self,
network_model,
model_processor,
dnn_model_processor,
data_fetcher,
img_processor=None,
cls_args_parser=None,
default_input_blob_preproc=None
):
super(ClsModelTestPipeline, self).__init__(
network_model,
model_processor,
dnn_model_processor
)
if cls_args_parser:
self._parser = cls_args_parser
self.test_config = TestClsConfig()
parser_args = self._parser.parse_args()
if parser_args.test:
self._test_module_config = TestClsModuleConfig()
self._test_module = get_test_module(
self._test_module_config.test_module_name,
self._test_module_config.test_module_path
)
if parser_args.default_img_preprocess:
self._default_input_blob_preproc = default_input_blob_preproc
if parser_args.evaluate:
self._data_fetcher = data_fetcher(self.test_config, img_processor)
def _configure_test_module_params(self):
self._test_module_param_list.extend((
'--crop', self._test_module_config.crop,
'--std', *self._test_module_config.std
))
if self._test_module_config.rsz_height and self._test_module_config.rsz_width:
self._test_module_param_list.extend((
'--initial_height', self._test_module_config.rsz_height,
'--initial_width', self._test_module_config.rsz_width,
))
def _configure_acc_eval(self, log_path):
self._accuracy_evaluator = ClsAccEvaluation(
log_path,
self.test_config.img_cls_file,
self.test_config.batch_size
)
@@ -0,0 +1,37 @@
BASE_IMG_SCALE_FACTOR = 1 / 255.0
PYTORCH_RSZ_HEIGHT = 256
PYTORCH_RSZ_WIDTH = 256
pytorch_resize_input_blob = {
"mean": ["123.675", "116.28", "103.53"],
"scale": str(BASE_IMG_SCALE_FACTOR),
"std": ["0.229", "0.224", "0.225"],
"crop": "True",
"rgb": True,
"rsz_height": str(PYTORCH_RSZ_HEIGHT),
"rsz_width": str(PYTORCH_RSZ_WIDTH)
}
pytorch_input_blob = {
"mean": ["123.675", "116.28", "103.53"],
"scale": str(BASE_IMG_SCALE_FACTOR),
"std": ["0.229", "0.224", "0.225"],
"crop": "True",
"rgb": True
}
tf_input_blob = {
"scale": str(1 / 127.5),
"mean": ["127.5", "127.5", "127.5"],
"std": [],
"crop": "True",
"rgb": True
}
tf_model_blob_caffe_mode = {
"mean": ["103.939", "116.779", "123.68"],
"scale": "1.0",
"std": [],
"crop": "True",
"rgb": False
}
@@ -0,0 +1,40 @@
import os
from dataclasses import dataclass, field
from typing import List
@dataclass
class CommonConfig:
output_data_root_dir: str = "dnn_model_runner/dnn_conversion"
logs_dir: str = os.path.join(output_data_root_dir, "logs")
log_file_path: str = os.path.join(logs_dir, "{}_log.txt")
@dataclass
class TestClsConfig:
batch_size: int = 1
frame_size: int = 224
img_root_dir: str = "./ILSVRC2012_img_val"
# location of image-class matching
img_cls_file: str = "./val.txt"
bgr_to_rgb: bool = True
@dataclass
class TestClsModuleConfig:
cls_test_data_dir: str = "../data"
test_module_name: str = "classification"
test_module_path: str = "classification.py"
input_img: str = os.path.join(cls_test_data_dir, "squirrel_cls.jpg")
model: str = ""
frame_height: str = str(TestClsConfig.frame_size)
frame_width: str = str(TestClsConfig.frame_size)
scale: str = "1.0"
mean: List[str] = field(default_factory=lambda: ["0.0", "0.0", "0.0"])
std: List[str] = field(default_factory=list)
crop: str = "False"
rgb: str = "True"
rsz_height: str = ""
rsz_width: str = ""
classes: str = os.path.join(cls_test_data_dir, "dnn", "classification_classes_ILSVRC2012.txt")
@@ -0,0 +1,126 @@
import os
import numpy as np
from .configs.test_config import CommonConfig
from ..utils import create_parser, plot_acc
class ModelTestPipeline:
def __init__(
self,
network_model,
model_processor,
dnn_model_processor
):
self._net_model = network_model
self._model_processor = model_processor
self._dnn_model_processor = dnn_model_processor
self._parser = create_parser()
self._test_module = None
self._test_module_config = None
self._test_module_param_list = None
self.test_config = None
self._data_fetcher = None
self._default_input_blob_preproc = None
self._accuracy_evaluator = None
def init_test_pipeline(self):
cmd_args = self._parser.parse_args()
model_dict = self._net_model.get_prepared_models()
model_names = list(model_dict.keys())
print(
"The model {} was successfully obtained and converted to OpenCV {}".format(model_names[0], model_names[1])
)
if cmd_args.test:
if not self._test_module_config.model:
self._test_module_config.model = self._net_model.model_path["full_path"]
if cmd_args.default_img_preprocess:
self._test_module_config.scale = self._default_input_blob_preproc["scale"]
self._test_module_config.mean = self._default_input_blob_preproc["mean"]
self._test_module_config.std = self._default_input_blob_preproc["std"]
self._test_module_config.crop = self._default_input_blob_preproc["crop"]
if "rsz_height" in self._default_input_blob_preproc and "rsz_width" in self._default_input_blob_preproc:
self._test_module_config.rsz_height = self._default_input_blob_preproc["rsz_height"]
self._test_module_config.rsz_width = self._default_input_blob_preproc["rsz_width"]
self._test_module_param_list = [
'--model', self._test_module_config.model,
'--input', self._test_module_config.input_img,
'--width', self._test_module_config.frame_width,
'--height', self._test_module_config.frame_height,
'--scale', self._test_module_config.scale,
'--mean', *self._test_module_config.mean,
'--std', *self._test_module_config.std,
'--classes', self._test_module_config.classes,
]
if self._default_input_blob_preproc["rgb"]:
self._test_module_param_list.append('--rgb')
self._configure_test_module_params()
self._test_module.main(
self._test_module_param_list
)
if cmd_args.evaluate:
original_model_name = model_names[0]
dnn_model_name = model_names[1]
self.run_test_pipeline(
[
self._model_processor(model_dict[original_model_name], original_model_name),
self._dnn_model_processor(model_dict[dnn_model_name], dnn_model_name)
],
original_model_name.replace(" ", "_")
)
def run_test_pipeline(
self,
models_list,
formatted_exp_name,
is_plot_acc=True
):
log_path, logs_dir = self._configure_eval_log(formatted_exp_name)
print(
"===== Running evaluation of the model with the following params:\n"
"\t* val data location: {}\n"
"\t* log file location: {}\n".format(
self.test_config.img_root_dir,
log_path
)
)
os.makedirs(logs_dir, exist_ok=True)
self._configure_acc_eval(log_path)
self._accuracy_evaluator.process(models_list, self._data_fetcher)
if is_plot_acc:
plot_acc(
np.array(self._accuracy_evaluator.general_inference_time),
formatted_exp_name
)
print("===== End of the evaluation pipeline =====")
def _configure_acc_eval(self, log_path):
pass
def _configure_test_module_params(self):
pass
@staticmethod
def _configure_eval_log(formatted_exp_name):
common_test_config = CommonConfig()
return common_test_config.log_file_path.format(formatted_exp_name), common_test_config.logs_dir
@@ -0,0 +1,153 @@
import argparse
import importlib.util
import os
import random
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import torch
from .test.configs.test_config import CommonConfig
SEED_VAL = 42
DNN_LIB = "DNN"
# common path for model savings
MODEL_PATH_ROOT = os.path.join(CommonConfig().output_data_root_dir, "{}/models")
def get_full_model_path(lib_name, model_full_name):
model_path = MODEL_PATH_ROOT.format(lib_name)
return {
"path": model_path,
"full_path": os.path.join(model_path, model_full_name)
}
def plot_acc(data_list, experiment_name):
plt.figure(figsize=[8, 6])
plt.plot(data_list[:, 0], "r", linewidth=2.5, label="Original Model")
plt.plot(data_list[:, 1], "b", linewidth=2.5, label="Converted DNN Model")
plt.xlabel("Iterations ", fontsize=15)
plt.ylabel("Time (ms)", fontsize=15)
plt.title(experiment_name, fontsize=15)
plt.legend()
full_path_to_fig = os.path.join(CommonConfig().output_data_root_dir, experiment_name + ".png")
plt.savefig(full_path_to_fig, bbox_inches="tight")
def get_final_summary_info(general_quality_metric, general_inference_time, metric_name):
general_quality_metric = np.array(general_quality_metric)
general_inference_time = np.array(general_inference_time)
summary_line = "===== End of processing. General results:\n"
"\t* mean {} for the original model: {}\t"
"\t* mean time (min) for the original model inferences: {}\n"
"\t* mean {} for the DNN model: {}\t"
"\t* mean time (min) for the DNN model inferences: {}\n".format(
metric_name, np.mean(general_quality_metric[:, 0]),
np.mean(general_inference_time[:, 0]) / 60000,
metric_name, np.mean(general_quality_metric[:, 1]),
np.mean(general_inference_time[:, 1]) / 60000,
)
return summary_line
def set_common_reproducibility():
random.seed(SEED_VAL)
np.random.seed(SEED_VAL)
def set_pytorch_env():
set_common_reproducibility()
torch.manual_seed(SEED_VAL)
torch.set_printoptions(precision=10)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(SEED_VAL)
torch.backends.cudnn_benchmark_enabled = False
torch.backends.cudnn.deterministic = True
def set_tf_env(is_use_gpu=True):
set_common_reproducibility()
tf.random.set_seed(SEED_VAL)
os.environ["TF_DETERMINISTIC_OPS"] = "1"
if tf.config.list_physical_devices("GPU") and is_use_gpu:
gpu_devices = tf.config.list_physical_devices("GPU")
tf.config.experimental.set_visible_devices(gpu_devices[0], "GPU")
tf.config.experimental.set_memory_growth(gpu_devices[0], True)
os.environ["TF_USE_CUDNN"] = "1"
else:
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
def str_bool(input_val):
if input_val.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif input_val.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value was expected')
def get_formatted_model_list(model_list):
note_line = 'Please, choose the model from the below list:\n'
spaces_to_set = ' ' * (len(note_line) - 2)
return note_line + ''.join([spaces_to_set, '{} \n'] * len(model_list)).format(*model_list)
def model_str(model_list):
def type_model_list(input_val):
if input_val.lower() in model_list:
return input_val.lower()
else:
raise argparse.ArgumentTypeError(
'The model is currently unavailable for test.\n' +
get_formatted_model_list(model_list)
)
return type_model_list
def get_test_module(test_module_name, test_module_path):
module_spec = importlib.util.spec_from_file_location(test_module_name, test_module_path)
test_module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(test_module)
module_spec.loader.exec_module(test_module)
return test_module
def create_parser():
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument(
"--test",
type=str_bool,
help="Define whether you'd like to run the model with OpenCV for testing.",
default=False
),
parser.add_argument(
"--default_img_preprocess",
type=str_bool,
help="Define whether you'd like to preprocess the input image with defined"
" PyTorch or TF functions for model test with OpenCV.",
default=False
),
parser.add_argument(
"--evaluate",
type=str_bool,
help="Define whether you'd like to run evaluation of the models (ex.: TF vs OpenCV networks).",
default=True
)
return parser
def create_extended_parser(model_list):
parser = create_parser()
parser.add_argument(
"--model_name",
type=model_str(model_list=model_list),
help="\nDefine the model name to test.\n" +
get_formatted_model_list(model_list),
required=True
)
return parser
@@ -0,0 +1,78 @@
# Run PaddlePaddle model using OpenCV
These two demonstrations show how to inference PaddlePaddle model using OpenCV.
## Environment Setup
```shell
pip install paddlepaddle-gpu
pip install paddlehub
pip install paddle2onnx
```
## 1. Run PaddlePaddle ResNet50 using OpenCV
### Run PaddlePaddle model demo
Run the code sample as follows:
```shell
python paddle_resnet50.py
```
There are three parts to the process:
1. Export PaddlePaddle ResNet50 model to onnx format.
2. Use `cv2.dnn.readNetFromONNX` to load the model file.
3. Preprocess image file and do the inference.
## 2. Run PaddleSeg Portrait Segmentation using OpenCV
### Convert to ONNX Model
#### 1. Get Paddle Inference model
For more details, please refer to [PaddleSeg](https://github.com/PaddlePaddle/PaddleSeg/blob/release/2.1/contrib/HumanSeg/README.md).
```shell
wget https://x2paddle.bj.bcebos.com/inference/models/humanseg_hrnet18_small_v1.zip
unzip humanseg_hrnet18_small_v1.zip
```
Notes:
* The exported model must have a fixed input shape, as dynamic is not supported at this moment.
#### 2. Convert to ONNX model using paddle2onnx
To convert the model, use the following command:
```
paddle2onnx --model_dir humanseg_hrnet18_small_v1 \
--model_filename model.pdmodel \
--params_filename model.pdiparams \
--opset_version 11 \
--save_file humanseg_hrnet18_tiny.onnx
```
The converted model can be found in the current directory by the name `humanseg_hrnet18_tiny.onnx` .
### Run PaddleSeg Portrait Segmentation demo
Run the code sample as follows:
```shell
python paddle_humanseg.py
```
There are three parts to the process:
1. Use `cv2.dnn.readNetFromONNX` to load the model file.
2. Preprocess image file and do inference.
3. Postprocess image file and visualize.
The resulting file can be found at `data/result_test_human.jpg` .
### Portrait segmentation visualization
<img src="../../../../data/messi5.jpg" width="50%" height="50%"><img src="./data/result_test_human.jpg" width="50%" height="50%">
Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

@@ -0,0 +1,112 @@
import os
import paddlehub.vision.transforms as T
import numpy as np
import cv2 as cv
def get_color_map_list(num_classes):
"""
Returns the color map for visualizing the segmentation mask,
which can support arbitrary number of classes.
Args:
num_classes (int): Number of classes.
Returns:
(list). The color map.
"""
num_classes += 1
color_map = num_classes * [0, 0, 0]
for i in range(0, num_classes):
j = 0
lab = i
while lab:
color_map[i * 3] |= (((lab >> 0) & 1) << (7 - j))
color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j))
color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j))
j += 1
lab >>= 3
color_map = color_map[3:]
return color_map
def visualize(image, result, save_dir=None, weight=0.6):
"""
Convert predict result to color image, and save added image.
Args:
image (str): The path of origin image.
result (np.ndarray): The predict result of image.
save_dir (str): The directory for saving visual image. Default: None.
weight (float): The image weight of visual image, and the result weight is (1 - weight). Default: 0.6
Returns:
vis_result (np.ndarray): If `save_dir` is None, return the visualized result.
"""
color_map = get_color_map_list(256)
color_map = [color_map[i:i + 3] for i in range(0, len(color_map), 3)]
color_map = np.array(color_map).astype("uint8")
# Use OpenCV LUT for color mapping
c1 = cv.LUT(result, color_map[:, 0])
c2 = cv.LUT(result, color_map[:, 1])
c3 = cv.LUT(result, color_map[:, 2])
pseudo_img = np.dstack((c1, c2, c3))
im = cv.imread(image)
vis_result = cv.addWeighted(im, weight, pseudo_img, 1 - weight, 0)
if save_dir is not None:
if not os.path.exists(save_dir):
os.makedirs(save_dir)
image_name = os.path.split(image)[-1]
out_path = os.path.join(save_dir, image_name)
cv.imwrite(out_path, vis_result)
else:
return vis_result
def preprocess(image_path):
''' preprocess input image file to np.ndarray
Args:
image_path(str): Path of input image file
Returns:
ProcessedImage(numpy.ndarray): A numpy.ndarray
variable which shape is (1, 3, 192, 192)
'''
transforms = T.Compose([
T.Resize((192, 192)),
T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
],
to_rgb=True)
return np.expand_dims(transforms(image_path), axis=0)
if __name__ == '__main__':
img_path = "../../../../data/messi5.jpg"
# load PPSeg Model use cv.dnn
net = cv.dnn.readNetFromONNX('humanseg_hrnet18_tiny.onnx')
# read and preprocess image file
im = preprocess(img_path)
# inference
net.setInput(im)
result = net.forward(['save_infer_model/scale_0.tmp_1'])
# post process
image = cv.imread(img_path)
r, c, _ = image.shape
result = np.argmax(result[0], axis=1).astype(np.uint8)
result = cv.resize(result[0, :, :],
dsize=(c, r),
interpolation=cv.INTER_NEAREST)
print("grid_image.shape is: ", result.shape)
folder_path = "data"
if not os.path.exists(folder_path):
os.makedirs(folder_path)
file_path = os.path.join(folder_path, '%s.jpg' % "result_test_human")
result_color = visualize(img_path, result)
cv.imwrite(file_path, result_color)
print('%s saved' % file_path)
@@ -0,0 +1,61 @@
import paddle
import paddlehub as hub
import paddlehub.vision.transforms as T
import cv2 as cv
import numpy as np
def preprocess(image_path):
''' preprocess input image file to np.ndarray
Args:
image_path(str): Path of input image file
Returns:
ProcessedImage(numpy.ndarray): A numpy.ndarray
variable which shape is (1, 3, 224, 224)
'''
transforms = T.Compose([
T.Resize((256, 256)),
T.CenterCrop(224),
T.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])],
to_rgb=True)
return np.expand_dims(transforms(image_path), axis=0)
def export_onnx_resnet50(save_path):
''' export PaddlePaddle model to ONNX format
Args:
save_path(str): Path to save exported ONNX model
Returns:
None
'''
model = hub.Module(name="resnet50_vd_imagenet_ssld")
input_spec = paddle.static.InputSpec(
[1, 3, 224, 224], "float32", "image")
paddle.onnx.export(model, save_path,
input_spec=[input_spec],
opset_version=10)
if __name__ == '__main__':
save_path = './resnet50'
image_file = './data/cat.jpg'
labels = open('./data/labels.txt').read().strip().split('\n')
model = export_onnx_resnet50(save_path)
# load resnet50 use cv.dnn
net = cv.dnn.readNetFromONNX(save_path + '.onnx')
# read and preprocess image file
im = preprocess(image_file)
# inference
net.setInput(im)
result = net.forward(['save_infer_model/scale_0.tmp_0'])
# post process
class_id = np.argmax(result[0])
label = labels[class_id]
print("Image: {}".format(image_file))
print("Predict Category: {}".format(label))
@@ -0,0 +1,71 @@
from torchvision import models
from ..pytorch_model import (
PyTorchModelPreparer,
PyTorchModelProcessor,
PyTorchDnnModelProcessor
)
from ...common.evaluation.classification.cls_data_fetcher import PyTorchPreprocessedFetch
from ...common.test.cls_model_test_pipeline import ClsModelTestPipeline
from ...common.test.configs.default_preprocess_config import pytorch_resize_input_blob
from ...common.test.configs.test_config import TestClsConfig
from ...common.utils import set_pytorch_env, create_extended_parser
model_dict = {
"alexnet": models.alexnet,
"vgg11": models.vgg11,
"vgg13": models.vgg13,
"vgg16": models.vgg16,
"vgg19": models.vgg19,
"resnet18": models.resnet18,
"resnet34": models.resnet34,
"resnet50": models.resnet50,
"resnet101": models.resnet101,
"resnet152": models.resnet152,
"squeezenet1_0": models.squeezenet1_0,
"squeezenet1_1": models.squeezenet1_1,
"resnext50_32x4d": models.resnext50_32x4d,
"resnext101_32x8d": models.resnext101_32x8d,
"wide_resnet50_2": models.wide_resnet50_2,
"wide_resnet101_2": models.wide_resnet101_2
}
class PyTorchClsModel(PyTorchModelPreparer):
def __init__(self, height, width, model_name, original_model):
super(PyTorchClsModel, self).__init__(height, width, model_name, original_model)
def main():
set_pytorch_env()
parser = create_extended_parser(list(model_dict.keys()))
cmd_args = parser.parse_args()
model_name = cmd_args.model_name
cls_model = PyTorchClsModel(
height=TestClsConfig().frame_size,
width=TestClsConfig().frame_size,
model_name=model_name,
original_model=model_dict[model_name](pretrained=True)
)
pytorch_cls_pipeline = ClsModelTestPipeline(
network_model=cls_model,
model_processor=PyTorchModelProcessor,
dnn_model_processor=PyTorchDnnModelProcessor,
data_fetcher=PyTorchPreprocessedFetch,
cls_args_parser=parser,
default_input_blob_preproc=pytorch_resize_input_blob
)
pytorch_cls_pipeline.init_test_pipeline()
if __name__ == "__main__":
main()
@@ -0,0 +1,139 @@
import os
import cv2
import numpy as np
import torch
import torch.onnx
from torch.autograd import Variable
from torchvision import models
def get_pytorch_onnx_model(original_model):
# define the directory for further converted model save
onnx_model_path = "models"
# define the name of further converted model
onnx_model_name = "resnet50.onnx"
# create directory for further converted model
os.makedirs(onnx_model_path, exist_ok=True)
# get full path to the converted model
full_model_path = os.path.join(onnx_model_path, onnx_model_name)
# generate model input
generated_input = Variable(
torch.randn(1, 3, 224, 224)
)
# model export into ONNX format
torch.onnx.export(
original_model,
generated_input,
full_model_path,
verbose=True,
input_names=["input"],
output_names=["output"],
opset_version=11
)
return full_model_path
def get_preprocessed_img(img_path):
# read the image
input_img = cv2.imread(img_path, cv2.IMREAD_COLOR)
input_img = input_img.astype(np.float32)
input_img = cv2.resize(input_img, (256, 256))
# define preprocess parameters
mean = np.array([0.485, 0.456, 0.406]) * 255.0
scale = 1 / 255.0
std = [0.229, 0.224, 0.225]
# prepare input blob to fit the model input:
# 1. subtract mean
# 2. scale to set pixel values from 0 to 1
input_blob = cv2.dnn.blobFromImage(
image=input_img,
scalefactor=scale,
size=(224, 224), # img target size
mean=mean,
swapRB=True, # BGR -> RGB
crop=True # center crop
)
# 3. divide by std
input_blob[0] /= np.asarray(std, dtype=np.float32).reshape(3, 1, 1)
return input_blob
def get_imagenet_labels(labels_path):
with open(labels_path) as f:
imagenet_labels = [line.strip() for line in f.readlines()]
return imagenet_labels
def get_opencv_dnn_prediction(opencv_net, preproc_img, imagenet_labels):
# set OpenCV DNN input
opencv_net.setInput(preproc_img)
# OpenCV DNN inference
out = opencv_net.forward()
print("OpenCV DNN prediction: \n")
print("* shape: ", out.shape)
# get the predicted class ID
imagenet_class_id = np.argmax(out)
# get confidence
confidence = out[0][imagenet_class_id]
print("* class ID: {}, label: {}".format(imagenet_class_id, imagenet_labels[imagenet_class_id]))
print("* confidence: {:.4f}".format(confidence))
def get_pytorch_dnn_prediction(original_net, preproc_img, imagenet_labels):
original_net.eval()
preproc_img = torch.FloatTensor(preproc_img)
# inference
with torch.no_grad():
out = original_net(preproc_img)
print("\nPyTorch model prediction: \n")
print("* shape: ", out.shape)
# get the predicted class ID
imagenet_class_id = torch.argmax(out, axis=1).item()
print("* class ID: {}, label: {}".format(imagenet_class_id, imagenet_labels[imagenet_class_id]))
# get confidence
confidence = out[0][imagenet_class_id]
print("* confidence: {:.4f}".format(confidence.item()))
def main():
# initialize PyTorch ResNet-50 model
original_model = models.resnet50(pretrained=True)
# get the path to the converted into ONNX PyTorch model
full_model_path = get_pytorch_onnx_model(original_model)
# read converted .onnx model with OpenCV API
opencv_net = cv2.dnn.readNetFromONNX(full_model_path)
print("OpenCV model was successfully read. Layer IDs: \n", opencv_net.getLayerNames())
# get preprocessed image
input_img = get_preprocessed_img("../data/squirrel_cls.jpg")
# get ImageNet labels
imagenet_labels = get_imagenet_labels("../data/dnn/classification_classes_ILSVRC2012.txt")
# obtain OpenCV DNN predictions
get_opencv_dnn_prediction(opencv_net, input_img, imagenet_labels)
# obtain original PyTorch ResNet50 predictions
get_pytorch_dnn_prediction(original_model, input_img, imagenet_labels)
if __name__ == "__main__":
main()
@@ -0,0 +1,50 @@
import os
import torch
import torch.onnx
from torch.autograd import Variable
from torchvision import models
def get_pytorch_onnx_model(original_model):
# define the directory for further converted model save
onnx_model_path = "models"
# define the name of further converted model
onnx_model_name = "resnet50.onnx"
# create directory for further converted model
os.makedirs(onnx_model_path, exist_ok=True)
# get full path to the converted model
full_model_path = os.path.join(onnx_model_path, onnx_model_name)
# generate model input
generated_input = Variable(
torch.randn(1, 3, 224, 224)
)
# model export into ONNX format
torch.onnx.export(
original_model,
generated_input,
full_model_path,
verbose=True,
input_names=["input"],
output_names=["output"],
opset_version=11
)
return full_model_path
def main():
# initialize PyTorch ResNet-50 model
original_model = models.resnet50(pretrained=True)
# get the path to the converted into ONNX PyTorch model
full_model_path = get_pytorch_onnx_model(original_model)
print("PyTorch ResNet-50 model was successfully converted: ", full_model_path)
if __name__ == "__main__":
main()
@@ -0,0 +1,98 @@
import os
import cv2
import torch.onnx
from torch.autograd import Variable
from ..common.abstract_model import AbstractModel, Framework
from ..common.utils import DNN_LIB, get_full_model_path
CURRENT_LIB = "PyTorch"
MODEL_FORMAT = ".onnx"
class PyTorchModelPreparer(AbstractModel):
def __init__(
self,
height,
width,
model_name="default",
original_model=object,
batch_size=1,
default_input_name="input",
default_output_name="output"
):
self._height = height
self._width = width
self._model_name = model_name
self._original_model = original_model
self._batch_size = batch_size
self._default_input_name = default_input_name
self._default_output_name = default_output_name
self.model_path = self._set_model_path()
self._dnn_model = self._set_dnn_model()
def _set_dnn_model(self):
generated_input = Variable(torch.randn(
self._batch_size, 3, self._height, self._width)
)
os.makedirs(self.model_path["path"], exist_ok=True)
torch.onnx.export(
self._original_model,
generated_input,
self.model_path["full_path"],
verbose=True,
input_names=[self._default_input_name],
output_names=[self._default_output_name],
opset_version=11
)
return cv2.dnn.readNetFromONNX(self.model_path["full_path"])
def _set_model_path(self):
model_to_save = self._model_name + MODEL_FORMAT
return get_full_model_path(CURRENT_LIB.lower(), model_to_save)
def get_prepared_models(self):
return {
CURRENT_LIB + " " + self._model_name: self._original_model,
DNN_LIB + " " + self._model_name: self._dnn_model
}
class PyTorchModelProcessor(Framework):
def __init__(self, prepared_model, model_name):
self._prepared_model = prepared_model
self._name = model_name
def get_output(self, input_blob):
tensor = torch.FloatTensor(input_blob)
self._prepared_model.eval()
with torch.no_grad():
model_out = self._prepared_model(tensor)
# segmentation case
if len(model_out) == 2:
model_out = model_out['out']
out = model_out.detach().numpy()
return out
def get_name(self):
return self._name
class PyTorchDnnModelProcessor(Framework):
def __init__(self, prepared_dnn_model, model_name):
self._prepared_dnn_model = prepared_dnn_model
self._name = model_name
def get_output(self, input_blob):
self._prepared_dnn_model.setInput(input_blob, '')
return self._prepared_dnn_model.forward()
def get_name(self):
return self._name
@@ -0,0 +1,15 @@
# Python 3.7.5
onnx>=1.7.0
numpy>=1.19.1
torch>=1.5.1
torchvision>=0.6.1
tensorflow>=2.1.0
tensorflow-gpu>=2.1.0
paddlepaddle>=2.0.0
paddlepaddle-gpu>=2.0.0
paddlehub>=2.1.0
paddle2onnx>=0.5.1
paddleseg>=2.0.0
@@ -0,0 +1,104 @@
from tensorflow.keras.applications import (
VGG16, vgg16,
VGG19, vgg19,
ResNet50, resnet,
ResNet101,
ResNet152,
DenseNet121, densenet,
DenseNet169,
DenseNet201,
InceptionResNetV2, inception_resnet_v2,
InceptionV3, inception_v3,
MobileNet, mobilenet,
MobileNetV2, mobilenet_v2,
NASNetLarge, nasnet,
NASNetMobile,
Xception, xception
)
from ..tf_model import TFModelPreparer
from ..tf_model import (
TFModelProcessor,
TFDnnModelProcessor
)
from ...common.evaluation.classification.cls_data_fetcher import TFPreprocessedFetch
from ...common.test.cls_model_test_pipeline import ClsModelTestPipeline
from ...common.test.configs.default_preprocess_config import (
tf_input_blob,
pytorch_input_blob,
tf_model_blob_caffe_mode
)
from ...common.utils import set_tf_env, create_extended_parser
model_dict = {
"vgg16": [VGG16, vgg16, tf_model_blob_caffe_mode],
"vgg19": [VGG19, vgg19, tf_model_blob_caffe_mode],
"resnet50": [ResNet50, resnet, tf_model_blob_caffe_mode],
"resnet101": [ResNet101, resnet, tf_model_blob_caffe_mode],
"resnet152": [ResNet152, resnet, tf_model_blob_caffe_mode],
"densenet121": [DenseNet121, densenet, pytorch_input_blob],
"densenet169": [DenseNet169, densenet, pytorch_input_blob],
"densenet201": [DenseNet201, densenet, pytorch_input_blob],
"inceptionresnetv2": [InceptionResNetV2, inception_resnet_v2, tf_input_blob],
"inceptionv3": [InceptionV3, inception_v3, tf_input_blob],
"mobilenet": [MobileNet, mobilenet, tf_input_blob],
"mobilenetv2": [MobileNetV2, mobilenet_v2, tf_input_blob],
"nasnetlarge": [NASNetLarge, nasnet, tf_input_blob],
"nasnetmobile": [NASNetMobile, nasnet, tf_input_blob],
"xception": [Xception, xception, tf_input_blob]
}
CNN_CLASS_ID = 0
CNN_UTILS_ID = 1
DEFAULT_BLOB_PARAMS_ID = 2
class TFClsModel(TFModelPreparer):
def __init__(self, model_name, original_model):
super(TFClsModel, self).__init__(model_name, original_model)
def main():
set_tf_env()
parser = create_extended_parser(list(model_dict.keys()))
cmd_args = parser.parse_args()
model_name = cmd_args.model_name
model_name_val = model_dict[model_name]
cls_model = TFClsModel(
model_name=model_name,
original_model=model_name_val[CNN_CLASS_ID](
include_top=True,
weights="imagenet"
)
)
tf_cls_pipeline = ClsModelTestPipeline(
network_model=cls_model,
model_processor=TFModelProcessor,
dnn_model_processor=TFDnnModelProcessor,
data_fetcher=TFPreprocessedFetch,
img_processor=model_name_val[CNN_UTILS_ID].preprocess_input,
cls_args_parser=parser,
default_input_blob_preproc=model_name_val[DEFAULT_BLOB_PARAMS_ID]
)
tf_cls_pipeline.init_test_pipeline()
if __name__ == "__main__":
main()
@@ -0,0 +1,142 @@
import os
import cv2
import numpy as np
import tensorflow as tf
from tensorflow.keras.applications import MobileNet
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
from ...common.utils import set_tf_env
def get_tf_model_proto(tf_model):
# define the directory for .pb model
pb_model_path = "models"
# define the name of .pb model
pb_model_name = "mobilenet.pb"
# create directory for further converted model
os.makedirs(pb_model_path, exist_ok=True)
# get model TF graph
tf_model_graph = tf.function(lambda x: tf_model(x))
# get concrete function
tf_model_graph = tf_model_graph.get_concrete_function(
tf.TensorSpec(tf_model.inputs[0].shape, tf_model.inputs[0].dtype))
# obtain frozen concrete function
frozen_tf_func = convert_variables_to_constants_v2(tf_model_graph)
# get frozen graph
frozen_tf_func.graph.as_graph_def()
# save full tf model
tf.io.write_graph(graph_or_graph_def=frozen_tf_func.graph,
logdir=pb_model_path,
name=pb_model_name,
as_text=False)
return os.path.join(pb_model_path, pb_model_name)
def get_preprocessed_img(img_path):
# read the image
input_img = cv2.imread(img_path, cv2.IMREAD_COLOR)
input_img = input_img.astype(np.float32)
# define preprocess parameters
mean = np.array([1.0, 1.0, 1.0]) * 127.5
scale = 1 / 127.5
# prepare input blob to fit the model input:
# 1. subtract mean
# 2. scale to set pixel values from 0 to 1
input_blob = cv2.dnn.blobFromImage(
image=input_img,
scalefactor=scale,
size=(224, 224), # img target size
mean=mean,
swapRB=True, # BGR -> RGB
crop=True # center crop
)
print("Input blob shape: {}\n".format(input_blob.shape))
return input_blob
def get_imagenet_labels(labels_path):
with open(labels_path) as f:
imagenet_labels = [line.strip() for line in f.readlines()]
return imagenet_labels
def get_opencv_dnn_prediction(opencv_net, preproc_img, imagenet_labels):
# set OpenCV DNN input
opencv_net.setInput(preproc_img)
# OpenCV DNN inference
out = opencv_net.forward()
print("OpenCV DNN prediction: \n")
print("* shape: ", out.shape)
# get the predicted class ID
imagenet_class_id = np.argmax(out)
# get confidence
confidence = out[0][imagenet_class_id]
print("* class ID: {}, label: {}".format(imagenet_class_id, imagenet_labels[imagenet_class_id]))
print("* confidence: {:.4f}\n".format(confidence))
def get_tf_dnn_prediction(original_net, preproc_img, imagenet_labels):
# inference
preproc_img = preproc_img.transpose(0, 2, 3, 1)
print("TF input blob shape: {}\n".format(preproc_img.shape))
out = original_net(preproc_img)
print("\nTensorFlow model prediction: \n")
print("* shape: ", out.shape)
# get the predicted class ID
imagenet_class_id = np.argmax(out)
print("* class ID: {}, label: {}".format(imagenet_class_id, imagenet_labels[imagenet_class_id]))
# get confidence
confidence = out[0][imagenet_class_id]
print("* confidence: {:.4f}".format(confidence))
def main():
# configure TF launching
set_tf_env()
# initialize TF MobileNet model
original_tf_model = MobileNet(
include_top=True,
weights="imagenet"
)
# get TF frozen graph path
full_pb_path = get_tf_model_proto(original_tf_model)
# read frozen graph with OpenCV API
opencv_net = cv2.dnn.readNetFromTensorflow(full_pb_path)
print("OpenCV model was successfully read. Model layers: \n", opencv_net.getLayerNames())
# get preprocessed image
input_img = get_preprocessed_img("../data/squirrel_cls.jpg")
# get ImageNet labels
imagenet_labels = get_imagenet_labels("../data/dnn/classification_classes_ILSVRC2012.txt")
# obtain OpenCV DNN predictions
get_opencv_dnn_prediction(opencv_net, input_img, imagenet_labels)
# obtain TF model predictions
get_tf_dnn_prediction(original_tf_model, input_img, imagenet_labels)
if __name__ == "__main__":
main()
@@ -0,0 +1,45 @@
import os
import tarfile
import urllib
DETECTION_MODELS_URL = 'http://download.tensorflow.org/models/object_detection/'
def extract_tf_frozen_graph(model_name, extracted_model_path):
# define model archive name
tf_model_tar = model_name + '.tar.gz'
# define link to retrieve model archive
model_link = DETECTION_MODELS_URL + tf_model_tar
tf_frozen_graph_name = 'frozen_inference_graph'
try:
urllib.request.urlretrieve(model_link, tf_model_tar)
except Exception:
print("TF {} was not retrieved: {}".format(model_name, model_link))
return
print("TF {} was retrieved.".format(model_name))
tf_model_tar = tarfile.open(tf_model_tar)
frozen_graph_path = ""
for model_tar_elem in tf_model_tar.getmembers():
if tf_frozen_graph_name in os.path.basename(model_tar_elem.name):
tf_model_tar.extract(model_tar_elem, extracted_model_path)
frozen_graph_path = os.path.join(extracted_model_path, model_tar_elem.name)
break
tf_model_tar.close()
return frozen_graph_path
def main():
tf_model_name = 'ssd_mobilenet_v1_coco_2017_11_17'
graph_extraction_dir = "./"
frozen_graph_path = extract_tf_frozen_graph(tf_model_name, graph_extraction_dir)
print("Frozen graph path for {}: {}".format(tf_model_name, frozen_graph_path))
if __name__ == "__main__":
main()
@@ -0,0 +1,112 @@
import cv2
import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
from ..common.abstract_model import AbstractModel, Framework
from ..common.utils import DNN_LIB, get_full_model_path
CURRENT_LIB = "TF"
MODEL_FORMAT = ".pb"
class TFModelPreparer(AbstractModel):
""" Class for the preparation of the TF models: original and converted OpenCV Net.
Args:
model_name: TF model name
original_model: TF configured model object or session
is_ready_graph: indicates whether ready .pb file already exists
tf_model_graph_path: path to the existing frozen TF graph
"""
def __init__(
self,
model_name="default",
original_model=None,
is_ready_graph=False,
tf_model_graph_path=""
):
self._model_name = model_name
self._original_model = original_model
self._model_to_save = ""
self._is_ready_to_transfer_graph = is_ready_graph
self.model_path = self._set_model_path(tf_model_graph_path)
self._dnn_model = self._set_dnn_model()
def _set_dnn_model(self):
if not self._is_ready_to_transfer_graph:
# get model TF graph
tf_model_graph = tf.function(lambda x: self._original_model(x))
tf_model_graph = tf_model_graph.get_concrete_function(
tf.TensorSpec(self._original_model.inputs[0].shape, self._original_model.inputs[0].dtype))
# obtain frozen concrete function
frozen_tf_func = convert_variables_to_constants_v2(tf_model_graph)
frozen_tf_func.graph.as_graph_def()
# save full TF model
tf.io.write_graph(graph_or_graph_def=frozen_tf_func.graph,
logdir=self.model_path["path"],
name=self._model_to_save,
as_text=False)
return cv2.dnn.readNetFromTensorflow(self.model_path["full_path"])
def _set_model_path(self, tf_pb_file_path):
""" Method for setting model paths.
Args:
tf_pb_file_path: path to the existing TF .pb
Returns:
dictionary, where full_path key means saved model path and its full name.
"""
model_paths_dict = {
"path": "",
"full_path": tf_pb_file_path
}
if not self._is_ready_to_transfer_graph:
self._model_to_save = self._model_name + MODEL_FORMAT
model_paths_dict = get_full_model_path(CURRENT_LIB.lower(), self._model_to_save)
return model_paths_dict
def get_prepared_models(self):
original_lib_name = CURRENT_LIB + " " + self._model_name
configured_model_dict = {
original_lib_name: self._original_model,
DNN_LIB + " " + self._model_name: self._dnn_model
}
return configured_model_dict
class TFModelProcessor(Framework):
def __init__(self, prepared_model, model_name):
self._prepared_model = prepared_model
self._name = model_name
def get_output(self, input_blob):
assert len(input_blob.shape) == 4
batch_tf = input_blob.transpose(0, 2, 3, 1)
out = self._prepared_model(batch_tf)
return out
def get_name(self):
return CURRENT_LIB
class TFDnnModelProcessor(Framework):
def __init__(self, prepared_dnn_model, model_name):
self._prepared_dnn_model = prepared_dnn_model
self._name = model_name
def get_output(self, input_blob):
self._prepared_dnn_model.setInput(input_blob)
ret_val = self._prepared_dnn_model.forward()
return ret_val
def get_name(self):
return DNN_LIB
+383
View File
@@ -0,0 +1,383 @@
'''
Helper module to download extra data from Internet
'''
from __future__ import print_function
import os
import sys
import yaml
import argparse
import tarfile
import platform
import tempfile
import hashlib
import requests
import shutil
from pathlib import Path
from datetime import datetime
from urllib.request import Request, urlopen
import xml.etree.ElementTree as ET
__all__ = ["downloadFile"]
class HashMismatchException(Exception):
def __init__(self, expected, actual):
Exception.__init__(self)
self.expected = expected
self.actual = actual
def __str__(self):
return 'Hash mismatch: expected {} vs actual of {}'.format(self.expected, self.actual)
def getHashsumFromFile(filepath):
sha = hashlib.sha1()
if os.path.exists(filepath):
print(' there is already a file with the same name')
with open(filepath, 'rb') as f:
while True:
buf = f.read(10*1024*1024)
if not buf:
break
sha.update(buf)
hashsum = sha.hexdigest()
return hashsum
def checkHashsum(expected_sha, filepath, silent=True):
if not os.path.exists(filepath):
print(f"{filepath} does not exist. Skipping hashsum matching")
return False
print(' expected SHA1: {}'.format(expected_sha))
actual_sha = getHashsumFromFile(filepath)
print(' actual SHA1:{}'.format(actual_sha))
hashes_matched = expected_sha == actual_sha
if not hashes_matched and not silent:
raise HashMismatchException(expected_sha, actual_sha)
return hashes_matched
def isArchive(filepath):
return tarfile.is_tarfile(filepath)
class DownloadInstance:
def __init__(self, **kwargs):
self.name = kwargs.pop('name')
self.filename = kwargs.pop('filename')
self.loader = kwargs.pop('loader', None)
self.save_dir = kwargs.pop('save_dir')
self.sha = kwargs.pop('sha', None)
def __str__(self):
return 'DownloadInstance <{}>'.format(self.name)
def get(self):
print(" Working on " + self.name)
print(" Getting file " + self.filename)
if self.sha is None:
print(' No expected hashsum provided, loading file')
else:
filepath = os.path.join(self.save_dir, self.sha, self.filename)
if checkHashsum(self.sha, filepath):
print(' hash match - file already exists, skipping')
return filepath
else:
print(' hash didn\'t match, loading file')
if not os.path.exists(self.save_dir):
print(' creating directory: ' + self.save_dir)
os.makedirs(self.save_dir)
print(' hash check failed - loading')
assert self.loader
try:
self.loader.load(self.filename, self.sha, self.save_dir)
print(' done')
print(' file {}'.format(self.filename))
if self.sha is None:
download_path = os.path.join(self.save_dir, self.filename)
self.sha = getHashsumFromFile(download_path)
new_dir = os.path.join(self.save_dir, self.sha)
if not os.path.exists(new_dir):
os.makedirs(new_dir)
filepath = os.path.join(new_dir, self.filename)
if not (os.path.exists(filepath)):
shutil.move(download_path, new_dir)
print(' No expected hashsum provided, actual SHA is {}'.format(self.sha))
else:
checkHashsum(self.sha, filepath, silent=False)
except Exception as e:
print(" There was some problem with loading file {} for {}".format(self.filename, self.name))
print(" Exception: {}".format(e))
return
print(" Finished " + self.name)
return filepath
class Loader(object):
MB = 1024*1024
BUFSIZE = 10*MB
def __init__(self, download_name, download_sha, archive_member = None):
self.download_name = download_name
self.download_sha = download_sha
self.archive_member = archive_member
def load(self, requested_file, sha, save_dir):
if self.download_sha is None:
download_dir = save_dir
else:
# create a new folder in save_dir to avoid possible name conflicts
download_dir = os.path.join(save_dir, self.download_sha)
if not os.path.exists(download_dir):
os.makedirs(download_dir)
download_path = os.path.join(download_dir, self.download_name)
print(" Preparing to download file " + self.download_name)
if checkHashsum(self.download_sha, download_path):
print(' hash match - file already exists, no need to download')
else:
filesize = self.download(download_path)
print(' Downloaded {} with size {} Mb'.format(self.download_name, filesize/self.MB))
if self.download_sha is not None:
checkHashsum(self.download_sha, download_path, silent=False)
if self.download_name == requested_file:
return
else:
if isArchive(download_path):
if sha is not None:
extract_dir = os.path.join(save_dir, sha)
else:
extract_dir = save_dir
if not os.path.exists(extract_dir):
os.makedirs(extract_dir)
self.extract(requested_file, download_path, extract_dir)
else:
raise Exception("Downloaded file has different name")
def download(self, filepath):
print("Warning: download is not implemented, this is a base class")
return 0
def extract(self, requested_file, archive_path, save_dir):
filepath = os.path.join(save_dir, requested_file)
try:
with tarfile.open(archive_path) as f:
if self.archive_member is None:
pathDict = dict((os.path.split(elem)[1], os.path.split(elem)[0]) for elem in f.getnames())
self.archive_member = pathDict[requested_file]
if self.archive_member == "":
self.archive_member = requested_file
assert self.archive_member in f.getnames()
self.save(filepath, f.extractfile(self.archive_member))
except Exception as e:
print(' catch {}'.format(e))
def save(self, filepath, r):
with open(filepath, 'wb') as f:
print(' progress ', end="")
sys.stdout.flush()
while True:
buf = r.read(self.BUFSIZE)
if not buf:
break
f.write(buf)
print('>', end="")
sys.stdout.flush()
class URLLoader(Loader):
def __init__(self, download_name, download_sha, url, archive_member = None):
super(URLLoader, self).__init__(download_name, download_sha, archive_member)
self.download_name = download_name
self.download_sha = download_sha
self.url = url
def download(self, filepath):
headers = {'User-Agent': 'Wget/1.20.3'}
req = Request(self.url, headers=headers)
with urlopen(req, timeout=60) as r:
self.printRequest(r)
self.save(filepath, r)
return os.path.getsize(filepath)
def printRequest(self, r):
def getMB(r):
d = dict(r.info())
for c in ['content-length', 'Content-Length']:
if c in d:
return int(d[c]) / self.MB
return '<unknown>'
print(' {} {} [{} Mb]'.format(r.getcode(), r.msg, getMB(r)))
class GDriveLoader(Loader):
BUFSIZE = 1024 * 1024
PROGRESS_SIZE = 10 * 1024 * 1024
def __init__(self, download_name, download_sha, gid, archive_member = None):
super(GDriveLoader, self).__init__(download_name, download_sha, archive_member)
self.download_name = download_name
self.download_sha = download_sha
self.gid = gid
def download(self, filepath):
session = requests.Session() # re-use cookies
URL = "https://docs.google.com/uc?export=download"
response = session.get(URL, params = { 'id' : self.gid }, stream = True)
def get_confirm_token(response): # in case of large files
for key, value in response.cookies.items():
if key.startswith('download_warning'):
return value
return None
token = get_confirm_token(response)
if token:
params = { 'id' : self.gid, 'confirm' : token }
response = session.get(URL, params = params, stream = True)
sz = 0
progress_sz = self.PROGRESS_SIZE
with open(filepath, "wb") as f:
for chunk in response.iter_content(self.BUFSIZE):
if not chunk:
continue # keep-alive
f.write(chunk)
sz += len(chunk)
if sz >= progress_sz:
progress_sz += self.PROGRESS_SIZE
print('>', end='')
sys.stdout.flush()
print('')
return sz
def produceDownloadInstance(instance_name, filename, sha, url, save_dir, download_name=None, download_sha=None, archive_member=None):
spec_param = url
loader = URLLoader
if download_name is None:
download_name = filename
if download_sha is None:
download_sha = sha
if "drive.google.com" in url:
token = ""
token_part = url.rsplit('/', 1)[-1]
if "&id=" not in token_part:
token_part = url.rsplit('/', 1)[-2]
for param in token_part.split("&"):
if param.startswith("id="):
token = param[3:]
if token:
loader = GDriveLoader
spec_param = token
else:
print("Warning: possibly wrong Google Drive link")
return DownloadInstance(
name=instance_name,
filename=filename,
sha=sha,
save_dir=save_dir,
loader=loader(download_name, download_sha, spec_param, archive_member)
)
def getSaveDir():
env_path = os.environ.get("OPENCV_DOWNLOAD_DATA_PATH", None)
if env_path:
save_dir = env_path
else:
# TODO reuse binding function cv2.utils.fs.getCacheDirectory when issue #19011 is fixed
if platform.system() == "Darwin":
#On Apple devices
temp_env = os.environ.get("TMPDIR", None)
if temp_env is None or not os.path.isdir(temp_env):
temp_dir = Path("/tmp")
print("Using world accessible cache directory. This may be not secure: ", temp_dir)
else:
temp_dir = temp_env
elif platform.system() == "Windows":
temp_dir = tempfile.gettempdir()
else:
xdg_cache_env = os.environ.get("XDG_CACHE_HOME", None)
if (xdg_cache_env and xdg_cache_env[0] and os.path.isdir(xdg_cache_env)):
temp_dir = xdg_cache_env
else:
home_env = os.environ.get("HOME", None)
if (home_env and home_env[0] and os.path.isdir(home_env)):
home_path = os.path.join(home_env, ".cache/")
if os.path.isdir(home_path):
temp_dir = home_path
else:
temp_dir = tempfile.gettempdir()
print("Using world accessible cache directory. This may be not secure: ", temp_dir)
save_dir = os.path.join(temp_dir, "downloads")
if not os.path.exists(save_dir):
os.makedirs(save_dir)
return save_dir
def downloadFile(url, sha=None, save_dir=None, filename=None):
if save_dir is None:
save_dir = getSaveDir()
if filename is None:
filename = "download_" + datetime.now().__str__()
name = filename
return produceDownloadInstance(name, filename, sha, url, save_dir).get()
def parseMetalinkFile(metalink_filepath, save_dir):
NS = {'ml': 'urn:ietf:params:xml:ns:metalink'}
models = []
for file_elem in ET.parse(metalink_filepath).getroot().findall('ml:file', NS):
url = file_elem.find('ml:url', NS).text
fname = file_elem.attrib['name']
name = file_elem.find('ml:identity', NS).text
hash_sum = file_elem.find('ml:hash', NS).text
models.append(produceDownloadInstance(name, fname, hash_sum, url, save_dir))
return models
def parseYAMLFile(yaml_filepath, save_dir, model_name):
models = []
with open(yaml_filepath, 'r') as stream:
data_loaded = yaml.safe_load(stream)
for name, params in data_loaded.items():
if model_name != "" and name != model_name:
continue
for key in params.keys():
if key.endswith("load_info"):
prefix = key[:-len('load_info')]
load_info = params.get(prefix+"load_info", None)
if load_info:
print(prefix)
if prefix == "config_":
fname = os.path.basename(params.get("config"))
hash_sum = load_info.get("sha1")
url = load_info.get("url")
download_sha = load_info.get("download_sha")
download_name = load_info.get("download_name")
archive_member = load_info.get("member")
models.append(produceDownloadInstance(name, fname, hash_sum, url, save_dir,
download_name=download_name, download_sha=download_sha, archive_member=archive_member))
else:
fname = os.path.basename(params.get(prefix+"model"))
hash_sum = load_info.get(prefix+"sha1")
url = load_info.get(prefix+"url")
download_sha = load_info.get(prefix+"download_sha")
download_name = load_info.get(prefix+"download_name")
archive_member = load_info.get(prefix+"member")
models.append(produceDownloadInstance(name, fname, hash_sum, url, save_dir,
download_name=download_name, download_sha=download_sha, archive_member=archive_member))
return models
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='This is a utility script for downloading DNN models for samples.')
parser.add_argument('--save_dir', action="store", default=os.getcwd(),
help='Path to the directory to store downloaded files')
parser.add_argument('model_name', type=str, default="", nargs='?', action="store",
help='name of the model to download')
args = parser.parse_args()
models = []
save_dir = args.save_dir
selected_model_name = args.model_name
models.extend(parseMetalinkFile('face_detector/weights.meta4', save_dir))
models.extend(parseYAMLFile('models.yml', save_dir, selected_model_name))
for m in models:
print(m)
if selected_model_name and not m.name.startswith(selected_model_name):
continue
print('Model: ' + selected_model_name)
m.get()
+251
View File
@@ -0,0 +1,251 @@
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <string>
#include <cmath>
#include <vector>
#include "common.hpp"
// Define namespace to simplify code
using namespace cv;
using namespace cv::dnn;
using namespace std;
int threshold1 = 0;
int threshold2 = 50;
int blurAmount = 5;
// Function to apply sigmoid activation
static void sigmoid(Mat& input) {
exp(-input, input); // e^-input
input = 1.0 / (1.0 + input); // 1 / (1 + e^-input)
}
static void applyCanny(const Mat& image, Mat& result) {
Mat gray;
cvtColor(image, gray, COLOR_BGR2GRAY);
Canny(gray, result, threshold1, threshold2);
}
// Load Model
static void loadModel(const string modelPath, String backend, String target, Net &net, EngineType engine){
net = readNetFromONNX(modelPath, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
}
static void setupCannyWindow(){
destroyWindow("Output");
namedWindow("Output", WINDOW_AUTOSIZE);
moveWindow("Output", 200, 50);
createTrackbar("thrs1", "Output", &threshold1, 255, nullptr);
createTrackbar("thrs2", "Output", &threshold2, 255, nullptr);
createTrackbar("blur", "Output", &blurAmount, 20, nullptr);
}
// Function to process the neural network output to generate edge maps
static pair<Mat, Mat> postProcess(const vector<Mat>& output, int height, int width) {
vector<Mat> preds;
preds.reserve(output.size());
for (const Mat &p : output) {
Mat img;
// Correctly handle 4D tensor assuming it's always in the format [1, 1, height, width]
Mat processed;
if (p.dims == 4 && p.size[0] == 1 && p.size[1] == 1) {
// Use only the spatial dimensions
processed = p.reshape(0, {p.size[2], p.size[3]});
} else {
processed = p.clone();
}
sigmoid(processed);
normalize(processed, img, 0, 255, NORM_MINMAX, CV_8U);
resize(img, img, Size(width, height)); // Resize to the original size
preds.push_back(img);
}
Mat fuse = preds.back(); // Last element as the fused result
// Calculate the average of the predictions
Mat ave = Mat::zeros(height, width, CV_32F);
for (Mat &pred : preds) {
Mat temp;
pred.convertTo(temp, CV_32F);
ave += temp;
}
ave /= static_cast<float>(preds.size());
ave.convertTo(ave, CV_8U);
return {fuse, ave}; // Return both fused and average edge maps
}
static void applyDexined(Net &net, const Mat &image, Mat &result) {
int originalWidth = image.cols;
int originalHeight = image.rows;
vector<Mat> outputs;
net.forward(outputs);
pair<Mat, Mat> res = postProcess(outputs, originalHeight, originalWidth);
result = res.first; // or res.second for average edge map
}
int main(int argc, char** argv) {
const string about =
"This sample demonstrates edge detection with dexined and canny edge detection techniques.\n\n"
"To run with canny:\n"
"\t ./example_dnn_edge_detection --input=path/to/your/input/image/or/video (don't give --input flag if want to use device camera)\n"
"With Dexined:\n"
"\t ./example_dnn_edge_detection dexined --input=path/to/your/input/image/or/video\n\n"
"For switching between deep learning based model(dexined) and canny edge detector, press space bar in case of video. In case of image, pass the argument --method for switching between dexined and canny.\n"
"Model path can also be specified using --model argument. Download it using python download_models.py dexined from dnn samples directory\n\n";
const string param_keys =
"{ help h | | Print help message. }"
"{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera.}"
"{ method | dexined | Choose method: dexined or canny. }"
"{ model | | Path to the model file for using dexined. }";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
CommandLineParser parser(argc, argv, keys);
if (parser.has("help"))
{
cout << about << endl;
parser.printMessage();
return -1;
}
string modelName = parser.get<String>("@alias");
string zooFile = parser.get<String>("zoo");
const char* path = getenv("OPENCV_SAMPLES_DATA_PATH");
if ((path != NULL) || parser.has("@alias") || (parser.get<String>("model") != "")) {
modelName = "dexined";
zooFile = findFile(zooFile);
}
else{
cout<<"[WARN] set the environment variables or pass path to dexined.onnx model file using --model and models.yml file using --zoo for using dexined based edge detector. Continuing with canny edge detector\n\n";
}
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
int width = parser.get<int>("width");
int height = parser.get<int>("height");
float scale = parser.get<float>("scale");
Scalar mean = parser.get<Scalar>("mean");
bool swapRB = parser.get<bool>("rgb");
String backend = parser.get<String>("backend");
String target = parser.get<String>("target");
string method = parser.get<String>("method");
String sha1 = parser.get<String>("sha1");
string model = findModel(parser.get<String>("model"), sha1);
EngineType engine = ENGINE_AUTO;
if (backend != "default" || target != "cpu"){
engine = ENGINE_CLASSIC;
}
parser.about(about);
VideoCapture cap;
if (parser.has("input"))
cap.open(samples::findFile(parser.get<String>("input")));
else
cap.open(0);
if (!cap.isOpened()) {
cerr << "Error: Video could not be opened." << endl;
return -1;
}
namedWindow("Input", WINDOW_AUTOSIZE);
namedWindow("Output", WINDOW_AUTOSIZE);
moveWindow("Output", 200, 0);
Net net;
Mat image;
if (model.empty()) {
cout << "[WARN] Model file not provided, using canny instead. Pass model using --model=/path/to/dexined.onnx to use dexined model." << endl;
method = "canny";
}
if (method == "dexined") {
loadModel(model, backend, target, net, engine);
}
else{
Mat dummy = Mat::zeros(512, 512, CV_8UC3);
setupCannyWindow();
}
cout<<"To switch between canny and dexined press space bar."<<endl;
for (;;){
cap >> image;
if (image.empty())
{
cout << "Press any key to exit" << endl;
waitKey();
break;
}
Mat result;
int kernelSize = 2 * blurAmount + 1;
Mat blurred;
GaussianBlur(image, blurred, Size(kernelSize, kernelSize), 0);
if (method == "dexined")
{
Mat blob = blobFromImage(blurred, scale, Size(width, height), mean, swapRB, false, CV_32F);
net.setInput(blob);
applyDexined(net, image, result);
}
else if (method == "canny")
{
applyCanny(blurred, result);
}
imshow("Input", image);
imshow("Output", result);
int key = waitKey(30);
if (key == ' ' && method == "canny")
{
if (!model.empty()){
method = "dexined";
if (net.empty())
loadModel(model, backend, target, net, engine);
destroyWindow("Output");
namedWindow("Input", WINDOW_AUTOSIZE);
namedWindow("Output", WINDOW_AUTOSIZE);
moveWindow("Output", 200, 0);
} else {
cout << "[ERROR] Provide model file using --model to use dexined. Download model using python download_models.py dexined from dnn samples directory" << endl;
}
}
else if (key == ' ' && method == "dexined")
{
method = "canny";
setupCannyWindow();
}
else if (key == 27 || key == 'q')
{ // Escape key to exit
break;
}
}
destroyAllWindows();
return 0;
}
+181
View File
@@ -0,0 +1,181 @@
'''
This sample demonstrates edge detection with dexined and canny edge detection techniques.
For switching between deep learning based model(dexined) and canny edge detector, press space bar in case of video. In case of image, pass the argument --method for switching between dexined and canny.
'''
import cv2 as cv
import argparse
import numpy as np
from common import *
def get_args_parser(func_args):
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
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)
parser.add_argument('--method', help='choose method: dexined or canny', default='canny', required=False)
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'edge_detection', 'dexined')
parser = argparse.ArgumentParser(parents=[parser],
description='''
To run:
Canny:
python edge_detection.py --input=path/to/your/input/image/or/video (don't give --input flag if want to use device camera)
Dexined:
python edge_detection.py dexined --input=path/to/your/input/image/or/video
"In case of video input, for switching between deep learning based model (Dexined) and Canny edge detector, press space bar. Pass as argument in case of image input."
Model path can also be specified using --model argument
''', formatter_class=argparse.RawTextHelpFormatter)
return parser.parse_args(func_args)
threshold1 = 0
threshold2 = 50
blur_amount = 5
gray = None
def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))
def post_processing(output, shape):
h, w = shape
preds = []
for p in output:
img = sigmoid(p)
img = np.squeeze(img)
img = cv.normalize(img, None, 0, 255, cv.NORM_MINMAX, cv.CV_8U)
img = cv.resize(img, (w, h))
preds.append(img)
fuse = preds[-1]
ave = np.array(preds, dtype=np.float32)
ave = np.uint8(np.mean(ave, axis=0))
return fuse, ave
def apply_canny(image):
global threshold1, threshold2, blur_amount
kernel_size = 2 * blur_amount + 1
blurred = cv.GaussianBlur(image, (kernel_size, kernel_size), 0)
result = cv.Canny(blurred, threshold1, threshold2)
cv.imshow('Output', result)
def setupCannyWindow(image):
global gray
cv.destroyWindow('Output')
cv.namedWindow('Output', cv.WINDOW_AUTOSIZE)
cv.moveWindow('Output', 200, 50)
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
cv.createTrackbar('thrs1', 'Output', threshold1, 255, lambda value: [globals().__setitem__('threshold1', value), apply_canny(gray)])
cv.createTrackbar('thrs2', 'Output', threshold2, 255, lambda value: [globals().__setitem__('threshold2', value), apply_canny(gray)])
cv.createTrackbar('blur', 'Output', blur_amount, 20, lambda value: [globals().__setitem__('blur_amount', value), apply_canny(gray)])
def loadModel(args, engine):
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
return net
def apply_dexined(model, image):
t0 = cv.getTickCount()
out = model.forward()
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
result,_ = post_processing(out, image.shape[:2])
label = 'Inference time: %.2f ms' % (t * 1000.0)
cv.putText(image, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255))
cv.putText(result, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))
cv.imshow("Output", result)
def main(func_args=None):
args = get_args_parser(func_args)
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
cap = cv.VideoCapture(cv.samples.findFile(args.input) if args.input else 0)
if not cap.isOpened():
print("Failed to open the input video")
exit(-1)
cv.namedWindow('Input', cv.WINDOW_AUTOSIZE)
cv.namedWindow('Output', cv.WINDOW_AUTOSIZE)
cv.moveWindow('Output', 200, 50)
method = args.method
if os.getenv('OPENCV_SAMPLES_DATA_PATH') is not None or hasattr(args, 'model'):
try:
args.model = findModel(args.model, args.sha1)
method = 'dexined'
except:
print("[WARN] Model file not provided, using canny instead. Pass model using --model=/path/to/dexined.onnx to use dexined model.")
method = 'canny'
args.model = None
else:
print("[WARN] Model file not provided, using canny instead. Pass model using --model=/path/to/dexined.onnx to use dexined model.")
method = 'canny'
if method == 'canny':
dummy = np.zeros((512, 512, 3), dtype="uint8")
setupCannyWindow(dummy)
net = None
if method == "dexined":
net = loadModel(args, engine)
while cv.waitKey(1) < 0:
hasFrame, image = cap.read()
if not hasFrame:
print("Press any key to exit")
cv.waitKey(0)
break
if method == "canny":
global gray
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
apply_canny(gray)
elif method == "dexined":
inp = cv.dnn.blobFromImage(image, args.scale, (args.width, args.height), args.mean, swapRB=args.rgb, crop=False)
net.setInput(inp)
apply_dexined(net, image)
cv.imshow("Input", image)
key = cv.waitKey(30)
if key == ord(' ') and method == 'canny':
if hasattr(args, 'model') and args.model is not None:
print("model: ", args.model)
method = "dexined"
if net is None:
net = loadModel(args, engine)
cv.destroyWindow('Output')
cv.namedWindow('Output', cv.WINDOW_AUTOSIZE)
cv.moveWindow('Output', 200, 50)
else:
print("[ERROR] Provide model file using --model to use dexined. Download model using python download_models.py dexined from dnn samples directory")
elif key == ord(' ') and method=='dexined':
method = "canny"
setupCannyWindow(image)
elif key == 27 or key == ord('q'):
break
cv.destroyAllWindows()
if __name__ == '__main__':
main()
+282
View File
@@ -0,0 +1,282 @@
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/objdetect.hpp>
#include <iostream>
using namespace cv;
using namespace std;
static
void visualize(Mat& input, int frame, Mat& faces, double fps, int thickness = 2)
{
std::string fpsString = cv::format("FPS : %.2f", (float)fps);
if (frame >= 0)
cout << "Frame " << frame << ", ";
cout << "FPS: " << fpsString << endl;
for (int i = 0; i < faces.rows; i++)
{
// Print results
cout << "Face " << i
<< ", top-left coordinates: (" << faces.at<float>(i, 0) << ", " << faces.at<float>(i, 1) << "), "
<< "box width: " << faces.at<float>(i, 2) << ", box height: " << faces.at<float>(i, 3) << ", "
<< "score: " << cv::format("%.2f", faces.at<float>(i, 14))
<< endl;
// Draw bounding box
rectangle(input, Rect2i(int(faces.at<float>(i, 0)), int(faces.at<float>(i, 1)), int(faces.at<float>(i, 2)), int(faces.at<float>(i, 3))), Scalar(0, 255, 0), thickness);
// Draw landmarks
circle(input, Point2i(int(faces.at<float>(i, 4)), int(faces.at<float>(i, 5))), 2, Scalar(255, 0, 0), thickness);
circle(input, Point2i(int(faces.at<float>(i, 6)), int(faces.at<float>(i, 7))), 2, Scalar(0, 0, 255), thickness);
circle(input, Point2i(int(faces.at<float>(i, 8)), int(faces.at<float>(i, 9))), 2, Scalar(0, 255, 0), thickness);
circle(input, Point2i(int(faces.at<float>(i, 10)), int(faces.at<float>(i, 11))), 2, Scalar(255, 0, 255), thickness);
circle(input, Point2i(int(faces.at<float>(i, 12)), int(faces.at<float>(i, 13))), 2, Scalar(0, 255, 255), thickness);
}
putText(input, fpsString, Point(0, 15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(0, 255, 0), 2);
}
int main(int argc, char** argv)
{
CommandLineParser parser(argc, argv,
"{help h | | Print this message}"
"{image1 i1 | | Path to the input image1. Omit for detecting through VideoCapture}"
"{image2 i2 | | Path to the input image2. When image1 and image2 parameters given then the program try to find a face on both images and runs face recognition algorithm}"
"{video v | 0 | Path to the input video}"
"{scale sc | 1.0 | Scale factor used to resize input video frames}"
"{fd_model fd | face_detection_yunet_2026may.onnx| Path to the model. Download yunet.onnx in https://github.com/opencv/opencv_zoo/tree/master/models/face_detection_yunet}"
"{fr_model fr | face_recognition_sface_2021dec.onnx | Path to the face recognition model. Download the model at https://github.com/opencv/opencv_zoo/tree/master/models/face_recognition_sface}"
"{score_threshold | 0.85 | Filter out faces of score < score_threshold}"
"{nms_threshold | 0.3 | Suppress bounding boxes of iou >= nms_threshold}"
"{top_k | 5000 | Keep top_k bounding boxes before NMS}"
"{save s | false | Set true to save results. This flag is invalid when using camera}"
);
if (parser.has("help"))
{
parser.printMessage();
return 0;
}
String fd_modelPath = parser.get<String>("fd_model");
String fr_modelPath = parser.get<String>("fr_model");
float scoreThreshold = parser.get<float>("score_threshold");
float nmsThreshold = parser.get<float>("nms_threshold");
int topK = parser.get<int>("top_k");
bool save = parser.get<bool>("save");
float scale = parser.get<float>("scale");
double cosine_similar_thresh = 0.363;
double l2norm_similar_thresh = 1.128;
//! [initialize_FaceDetectorYN]
// Initialize FaceDetectorYN
Ptr<FaceDetectorYN> detector = FaceDetectorYN::create(fd_modelPath, "", Size(320, 320), scoreThreshold, nmsThreshold, topK);
//! [initialize_FaceDetectorYN]
TickMeter tm;
// If input is an image
if (parser.has("image1"))
{
String input1 = parser.get<String>("image1");
Mat image1 = imread(samples::findFile(input1));
if (image1.empty())
{
std::cerr << "Cannot read image: " << input1 << std::endl;
return 2;
}
int imageWidth = int(image1.cols * scale);
int imageHeight = int(image1.rows * scale);
resize(image1, image1, Size(imageWidth, imageHeight));
tm.start();
//! [inference]
// Set input size before inference
detector->setInputSize(image1.size());
Mat faces1;
detector->detect(image1, faces1);
if (faces1.rows < 1)
{
std::cerr << "Cannot find a face in " << input1 << std::endl;
return 1;
}
//! [inference]
tm.stop();
// Draw results on the input image
visualize(image1, -1, faces1, tm.getFPS());
// Save results if save is true
if (save)
{
cout << "Saving result.jpg...\n";
imwrite("result.jpg", image1);
}
// Visualize results
imshow("image1", image1);
pollKey(); // handle UI events to show content
if (parser.has("image2"))
{
String input2 = parser.get<String>("image2");
Mat image2 = imread(samples::findFile(input2));
if (image2.empty())
{
std::cerr << "Cannot read image2: " << input2 << std::endl;
return 2;
}
tm.reset();
tm.start();
detector->setInputSize(image2.size());
Mat faces2;
detector->detect(image2, faces2);
if (faces2.rows < 1)
{
std::cerr << "Cannot find a face in " << input2 << std::endl;
return 1;
}
tm.stop();
visualize(image2, -1, faces2, tm.getFPS());
if (save)
{
cout << "Saving result2.jpg...\n";
imwrite("result2.jpg", image2);
}
imshow("image2", image2);
pollKey();
//! [initialize_FaceRecognizerSF]
// Initialize FaceRecognizerSF
Ptr<FaceRecognizerSF> faceRecognizer = FaceRecognizerSF::create(fr_modelPath, "");
//! [initialize_FaceRecognizerSF]
//! [facerecognizer]
// Aligning and cropping facial image through the first face of faces detected.
Mat aligned_face1, aligned_face2;
faceRecognizer->alignCrop(image1, faces1.row(0), aligned_face1);
faceRecognizer->alignCrop(image2, faces2.row(0), aligned_face2);
// Run feature extraction with given aligned_face
Mat feature1, feature2;
faceRecognizer->feature(aligned_face1, feature1);
feature1 = feature1.clone();
faceRecognizer->feature(aligned_face2, feature2);
feature2 = feature2.clone();
//! [facerecognizer]
//! [match]
double cos_score = faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_COSINE);
double L2_score = faceRecognizer->match(feature1, feature2, FaceRecognizerSF::DisType::FR_NORM_L2);
//! [match]
if (cos_score >= cosine_similar_thresh)
{
std::cout << "They have the same identity;";
}
else
{
std::cout << "They have different identities;";
}
std::cout << " Cosine Similarity: " << cos_score << ", threshold: " << cosine_similar_thresh << ". (higher value means higher similarity, max 1.0)\n";
if (L2_score <= l2norm_similar_thresh)
{
std::cout << "They have the same identity;";
}
else
{
std::cout << "They have different identities.";
}
std::cout << " NormL2 Distance: " << L2_score << ", threshold: " << l2norm_similar_thresh << ". (lower value means higher similarity, min 0.0)\n";
}
cout << "Press any key to exit..." << endl;
waitKey(0);
}
else
{
int frameWidth, frameHeight;
VideoCapture capture;
std::string video = parser.get<string>("video");
if (video.size() == 1 && isdigit(video[0]))
capture.open(parser.get<int>("video"));
else
capture.open(samples::findFileOrKeep(video)); // keep GStreamer pipelines
if (capture.isOpened())
{
frameWidth = int(capture.get(CAP_PROP_FRAME_WIDTH) * scale);
frameHeight = int(capture.get(CAP_PROP_FRAME_HEIGHT) * scale);
cout << "Video " << video
<< ": width=" << frameWidth
<< ", height=" << frameHeight
<< endl;
}
else
{
cout << "Could not initialize video capturing: " << video << "\n";
return 1;
}
detector->setInputSize(Size(frameWidth, frameHeight));
cout << "Press 'SPACE' to save frame, any other key to exit..." << endl;
int nFrame = 0;
for (;;)
{
// Get frame
Mat frame;
if (!capture.read(frame))
{
cerr << "Can't grab frame! Stop\n";
break;
}
resize(frame, frame, Size(frameWidth, frameHeight));
// Inference
Mat faces;
tm.start();
detector->detect(frame, faces);
tm.stop();
Mat result = frame.clone();
// Draw results on the input image
visualize(result, nFrame, faces, tm.getFPS());
// Visualize results
imshow("Live", result);
int key = waitKey(1);
bool saveFrame = save;
if (key == ' ')
{
saveFrame = true;
key = 0; // handled
}
if (saveFrame)
{
std::string frame_name = cv::format("frame_%05d.png", nFrame);
std::string result_name = cv::format("result_%05d.jpg", nFrame);
cout << "Saving '" << frame_name << "' and '" << result_name << "' ...\n";
imwrite(frame_name, frame);
imwrite(result_name, result);
}
++nFrame;
if (key > 0)
break;
}
cout << "Processed " << nFrame << " frames" << endl;
}
cout << "Done." << endl;
return 0;
}
+159
View File
@@ -0,0 +1,159 @@
import argparse
import numpy as np
import cv2 as cv
def str2bool(v):
if v.lower() in ['on', 'yes', 'true', 'y', 't']:
return True
elif v.lower() in ['off', 'no', 'false', 'n', 'f']:
return False
else:
raise NotImplementedError
parser = argparse.ArgumentParser()
parser.add_argument('--image1', '-i1', type=str, help='Path to the input image1. Omit for detecting on default camera.')
parser.add_argument('--image2', '-i2', type=str, help='Path to the input image2. When image1 and image2 parameters given then the program try to find a face on both images and runs face recognition algorithm.')
parser.add_argument('--video', '-v', type=str, help='Path to the input video.')
parser.add_argument('--scale', '-sc', type=float, default=1.0, help='Scale factor used to resize input video frames.')
parser.add_argument('--face_detection_model', '-fd', type=str, default='face_detection_yunet_2026may.onnx', help='Path to the face detection model. Download the model at https://github.com/opencv/opencv_zoo/tree/master/models/face_detection_yunet')
parser.add_argument('--face_recognition_model', '-fr', type=str, default='face_recognition_sface_2021dec.onnx', help='Path to the face recognition model. Download the model at https://github.com/opencv/opencv_zoo/tree/master/models/face_recognition_sface')
parser.add_argument('--score_threshold', type=float, default=0.85, help='Filtering out faces of score < score_threshold.')
parser.add_argument('--nms_threshold', type=float, default=0.3, help='Suppress bounding boxes of iou >= nms_threshold.')
parser.add_argument('--top_k', type=int, default=5000, help='Keep top_k bounding boxes before NMS.')
parser.add_argument('--save', '-s', type=str2bool, default=False, help='Set true to save results. This flag is invalid when using camera.')
args = parser.parse_args()
def visualize(input, faces, fps, thickness=2):
if faces[1] is not None:
for idx, face in enumerate(faces[1]):
print('Face {}, top-left coordinates: ({:.0f}, {:.0f}), box width: {:.0f}, box height {:.0f}, score: {:.2f}'.format(idx, face[0], face[1], face[2], face[3], face[-1]))
coords = face[:-1].astype(np.int32)
cv.rectangle(input, (coords[0], coords[1]), (coords[0]+coords[2], coords[1]+coords[3]), (0, 255, 0), thickness)
cv.circle(input, (coords[4], coords[5]), 2, (255, 0, 0), thickness)
cv.circle(input, (coords[6], coords[7]), 2, (0, 0, 255), thickness)
cv.circle(input, (coords[8], coords[9]), 2, (0, 255, 0), thickness)
cv.circle(input, (coords[10], coords[11]), 2, (255, 0, 255), thickness)
cv.circle(input, (coords[12], coords[13]), 2, (0, 255, 255), thickness)
cv.putText(input, 'FPS: {:.2f}'.format(fps), (1, 16), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
if __name__ == '__main__':
## [initialize_FaceDetectorYN]
detector = cv.FaceDetectorYN.create(
args.face_detection_model,
"",
(320, 320),
args.score_threshold,
args.nms_threshold,
args.top_k
)
## [initialize_FaceDetectorYN]
tm = cv.TickMeter()
# If input is an image
if args.image1 is not None:
img1 = cv.imread(cv.samples.findFile(args.image1))
img1Width = int(img1.shape[1]*args.scale)
img1Height = int(img1.shape[0]*args.scale)
img1 = cv.resize(img1, (img1Width, img1Height))
tm.start()
## [inference]
# Set input size before inference
detector.setInputSize((img1Width, img1Height))
faces1 = detector.detect(img1)
## [inference]
tm.stop()
assert faces1[1] is not None, 'Cannot find a face in {}'.format(args.image1)
# Draw results on the input image
visualize(img1, faces1, tm.getFPS())
# Save results if save is true
if args.save:
print('Results saved to result.jpg\n')
cv.imwrite('result.jpg', img1)
# Visualize results in a new window
cv.imshow("image1", img1)
if args.image2 is not None:
img2 = cv.imread(cv.samples.findFile(args.image2))
tm.reset()
tm.start()
detector.setInputSize((img2.shape[1], img2.shape[0]))
faces2 = detector.detect(img2)
tm.stop()
assert faces2[1] is not None, 'Cannot find a face in {}'.format(args.image2)
visualize(img2, faces2, tm.getFPS())
cv.imshow("image2", img2)
## [initialize_FaceRecognizerSF]
recognizer = cv.FaceRecognizerSF.create(
args.face_recognition_model,"")
## [initialize_FaceRecognizerSF]
## [facerecognizer]
# Align faces
face1_align = recognizer.alignCrop(img1, faces1[1][0])
face2_align = recognizer.alignCrop(img2, faces2[1][0])
# Extract features
face1_feature = recognizer.feature(face1_align)
face2_feature = recognizer.feature(face2_align)
## [facerecognizer]
cosine_similarity_threshold = 0.363
l2_similarity_threshold = 1.128
## [match]
cosine_score = recognizer.match(face1_feature, face2_feature, cv.FaceRecognizerSF_FR_COSINE)
l2_score = recognizer.match(face1_feature, face2_feature, cv.FaceRecognizerSF_FR_NORM_L2)
## [match]
msg = 'different identities'
if cosine_score >= cosine_similarity_threshold:
msg = 'the same identity'
print('They have {}. Cosine Similarity: {}, threshold: {} (higher value means higher similarity, max 1.0).'.format(msg, cosine_score, cosine_similarity_threshold))
msg = 'different identities'
if l2_score <= l2_similarity_threshold:
msg = 'the same identity'
print('They have {}. NormL2 Distance: {}, threshold: {} (lower value means higher similarity, min 0.0).'.format(msg, l2_score, l2_similarity_threshold))
cv.waitKey(0)
else: # Omit input to call default camera
if args.video is not None:
deviceId = args.video
else:
deviceId = 0
cap = cv.VideoCapture(deviceId)
frameWidth = int(cap.get(cv.CAP_PROP_FRAME_WIDTH)*args.scale)
frameHeight = int(cap.get(cv.CAP_PROP_FRAME_HEIGHT)*args.scale)
detector.setInputSize([frameWidth, frameHeight])
while cv.waitKey(1) < 0:
hasFrame, frame = cap.read()
if not hasFrame:
print('No frames grabbed!')
break
frame = cv.resize(frame, (frameWidth, frameHeight))
# Inference
tm.start()
faces = detector.detect(frame) # faces is a tuple
tm.stop()
# Draw results on the input image
visualize(frame, faces, tm.getFPS())
# Visualize results
cv.imshow('Live', frame)
cv.destroyAllWindows()
+2
View File
@@ -0,0 +1,2 @@
*.caffemodel
*.pb
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,79 @@
This is a brief description of training process which has been used to get res10_300x300_ssd_iter_140000.caffemodel.
The model was created with SSD framework using ResNet-10 like architecture as a backbone. Channels count in ResNet-10 convolution layers was significantly dropped (2x- or 4x- fewer channels).
The model was trained in Caffe framework on some huge and available online dataset.
1. Prepare training tools
You need to use "ssd" branch from this repository https://github.com/weiliu89/caffe/tree/ssd . Checkout this branch and built it (see instructions in repo's README)
2. Prepare training data.
The data preparation pipeline can be represented as:
(a)Download original face detection dataset -> (b)Convert annotation to the PASCAL VOC format -> (c)Create LMDB database with images + annotations for training
a) Find some datasets with face bounding boxes annotation. For some reasons I can't provide links here, but you easily find them on your own. Also study the data. It may contain small or low quality faces which can spoil training process. Often there are special flags about object quality in annotation. Remove such faces from annotation (smaller when 16 along at least one side, or blurred, of highly-occluded, or something else).
b) The downloaded dataset will have some format of annotation. It may be one single file for all images, or separate file for each image or something else. But to train SSD in Caffe you need to convert annotation to PASCAL VOC format.
PASCAL VOC annotation consist of .xml file for each image. In this xml file all face bounding boxes should be listed as:
<annotation>
<size>
<width>300</width>
<height>300</height>
</size>
<object>
<name>face</name>
<difficult>0</difficult>
<bndbox>
<xmin>100</xmin>
<ymin>100</ymin>
<xmax>200</xmax>
<ymax>200</ymax>
</bndbox>
</object>
<object>
<name>face</name>
<difficult>0</difficult>
<bndbox>
<xmin>0</xmin>
<ymin>0</ymin>
<xmax>100</xmax>
<ymax>100</ymax>
</bndbox>
</object>
</annotation>
So, convert your dataset's annotation to the format above.
Also, you should create labelmap.prototxt file with the following content:
item {
name: "none_of_the_above"
label: 0
display_name: "background"
}
item {
name: "face"
label: 1
display_name: "face"
}
You need this file to establish correspondence between name of class and digital label of class.
For next step we also need file there all our image-annotation file names pairs are listed. This file should contain similar lines:
images_val/0.jpg annotations_val/0.jpg.xml
c) To create LMDB you need to use create_data.sh tool from caffe/data/VOC0712 Caffe's source code directory.
This script calls create_annoset.py inside, so check out what you need to pass as script's arguments
You need to prepare 2 LMDB databases: one for training images, one for validation images.
3. Train your detector
For training you need to have 3 files: train.prototxt, test.prototxt and solver.prototxt. You can find these files in the same directory as for this readme.
Also you need to edit train.prototxt and test.prototxt to replace paths for your LMDB databases to actual databases you've created in step 2.
Now all is done for launch training process.
Execute next lines in Terminal:
mkdir -p snapshot
mkdir -p log
/path_for_caffe_build_dir/tools/caffe train -solver="solver.prototxt" -gpu 0 2>&1 | tee -a log/log.log
And wait. It will take about 8 hours to finish the process.
After it you can use your .caffemodel from snapshot/ subdirectory in resnet_face_ssd_python.py sample.
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
train_net: "train.prototxt"
test_net: "test.prototxt"
test_iter: 2312
test_interval: 5000
test_initialization: true
base_lr: 0.01
display: 10
lr_policy: "multistep"
max_iter: 140000
stepvalue: 80000
stepvalue: 120000
gamma: 0.1
momentum: 0.9
weight_decay: 0.0005
average_loss: 500
iter_size: 1
type: "SGD"
solver_mode: GPU
random_seed: 0
debug_info: false
snapshot: 1000
snapshot_prefix: "snapshot/res10_300x300_ssd"
eval_type: "detection"
ap_version: "11point"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<metalink xmlns="urn:ietf:params:xml:ns:metalink">
<file name="res10_300x300_ssd_iter_140000_fp16.caffemodel">
<identity>opencv_face_detector_fp16</identity>
<hash type="sha-1">31fc22bfdd907567a04bb45b7cfad29966caddc1</hash>
<url>https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20180205_fp16/res10_300x300_ssd_iter_140000_fp16.caffemodel</url>
</file>
<file name="opencv_face_detector_uint8.pb">
<identity>opencv_face_detector_uint8</identity>
<hash type="sha-1">4f2fdf6f231d759d7bbdb94353c5a68690f3d2ae</hash>
<url>https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20180220_uint8/opencv_face_detector_uint8.pb</url>
</file>
</metalink>
+52
View File
@@ -0,0 +1,52 @@
from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
parser = argparse.ArgumentParser(
description='This script is used to run style transfer models from '
'https://github.com/onnx/models/tree/main/vision/style_transfer/fast_neural_style using OpenCV')
parser.add_argument('--input', help='Path to image or video. Skip to capture frames from camera')
parser.add_argument('--model', help='Path to .onnx model')
parser.add_argument('--width', default=-1, type=int, help='Resize input to specific width.')
parser.add_argument('--height', default=-1, type=int, help='Resize input to specific height.')
parser.add_argument('--median_filter', default=0, type=int, help='Kernel size of postprocessing blurring.')
args = parser.parse_args()
net = cv.dnn.readNet(cv.samples.findFile(args.model))
net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
if args.input:
cap = cv.VideoCapture(args.input)
else:
cap = cv.VideoCapture(0)
cv.namedWindow('Styled image', cv.WINDOW_NORMAL)
while cv.waitKey(1) < 0:
hasFrame, frame = cap.read()
if not hasFrame:
cv.waitKey()
break
inWidth = args.width if args.width != -1 else frame.shape[1]
inHeight = args.height if args.height != -1 else frame.shape[0]
inp = cv.dnn.blobFromImage(frame, 1.0, (inWidth, inHeight),
swapRB=True, crop=False)
net.setInput(inp)
t0 = cv.getTickCount()
out = net.forward()
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
out = out.reshape(3, out.shape[2], out.shape[3])
out = out.transpose(1, 2, 0)
print('%.2f ms' % (t * 1000.0))
if args.median_filter:
out = cv.medianBlur(out, args.median_filter)
out = np.clip(out, 0, 255)
out = out.astype(np.uint8)
cv.imshow('Styled image', out)
+140
View File
@@ -0,0 +1,140 @@
# This file is part of OpenCV project.
# It is subject to the license terms in the LICENSE file found in the top-level directory
# of this distribution and at http://opencv.org/license.html.
# Copyright (C) 2026, BigVision LLC, all rights reserved.
# Third party copyrights are property of their respective owners.
'''
This is a sample script to run Gemma3 inference in OpenCV using ONNX model.
The script loads the Gemma3 model and runs inference on a given prompt using
the Gemma3 chat format (<start_of_turn> / <end_of_turn> special tokens).
Model: https://huggingface.co/google/gemma-3-1b-it
Exporting Gemma3 model to ONNX:
1. Install the required dependencies:
pip install optimum[exporters] optimum-onnx[onnxruntime] torch transformers
2. Export the model to ONNX:
Without KV-cache:
optimum-cli export onnx --model google/gemma-3-1b-it --task causal-lm gemma3_instruct_onnx/
With KV-cache (recommended, faster autoregressive inference):
optimum-cli export onnx --model google/gemma-3-1b-it --task causal-lm-with-past gemma3_instruct_onnx_with_past/
Run the script:
1. Install the required dependencies:
pip install numpy
2. Run the script:
Without KV-cache (causal-lm export):
python gemma3_inference.py --model=<path-to-onnx-model> \
--tokenizer_path=<path-to-opencv-tokenizer-config.json> \
--prompt="What is OpenCV?"
With KV-cache (causal-lm-with-past export):
python gemma3_inference.py --model=<path-to-onnx-model> \
--tokenizer_path=<path-to-opencv-tokenizer-config.json> \
--prompt="What is OpenCV?" \
--use_kv_cache
The tokenizer_path should point to an OpenCV-format config.json (e.g., from
opencv_extra/testdata/dnn/llm/gemma3/config.json), NOT the HuggingFace tokenizer_config.json.
'''
import numpy as np
import argparse
import cv2 as cv
def parse_args():
parser = argparse.ArgumentParser(description='Use this script to run Gemma3 inference in OpenCV',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--model', type=str, required=True, help='Path to Gemma3 ONNX model file.')
parser.add_argument('--tokenizer_path', type=str, required=True, help='Path to Gemma3 tokenizer config.json.')
parser.add_argument('--prompt', type=str, default='What is OpenCV?', help='User prompt.')
parser.add_argument('--max_new_tokens', type=int, default=64, help='Maximum number of new tokens to generate.')
parser.add_argument('--use_kv_cache', action='store_true', default=False, help='Enable KV-cache for faster inference (requires causal-lm-with-past export).')
parser.add_argument('--seed', type=int, default=0, help='Random seed.')
return parser.parse_args()
def build_gemma3_prompt(user_prompt):
'''Wrap user prompt in Gemma3 chat format.'''
return '<start_of_turn>user\n' + user_prompt + '<end_of_turn>\n<start_of_turn>model\n'
def gemma3_inference(net, prompt, max_new_tokens, tokenizer, use_kv_cache=True):
print("Inferencing Gemma3 model...")
tokens = tokenizer.encode(prompt)
# Prepend BOS token (id=2) as required by Gemma3
tokens = [2] + list(tokens)
input_ids = np.array(tokens, dtype=np.int64).reshape(1, -1)
# Gemma3 special token IDs
eos_id = 1 # <eos>
eot_id = 106 # <end_of_turn>
stop_ids = (eos_id, eot_id)
generated = []
if use_kv_cache:
net.enableKVCache()
prompt_len = input_ids.shape[1]
# Prefill: process full prompt once to populate KV-cache
net.setInput(input_ids, 'input_ids')
net.setInput(np.ones((1, prompt_len), dtype=np.int64), 'attention_mask')
logits = net.forward()
new_id = int(np.argmax(logits[:, -1, :].reshape(-1)))
generated = [new_id]
# Generate: feed one new token per step; OpenCV routes present.* -> past_key_values.*
for _ in range(max_new_tokens - 1):
if new_id in stop_ids:
break
net.setInput(np.array([[new_id]], dtype=np.int64), 'input_ids')
net.setInput(np.ones((1, prompt_len + len(generated)), dtype=np.int64), 'attention_mask')
logits = net.forward()
new_id = int(np.argmax(logits[:, -1, :].reshape(-1)))
generated.append(new_id)
else:
# Without KV-cache: feed full growing sequence each step
for _ in range(max_new_tokens):
net.setInput(input_ids, 'input_ids')
net.setInput(np.ones((1, input_ids.shape[1]), dtype=np.int64), 'attention_mask')
logits = net.forward()
new_id = int(np.argmax(logits[:, -1, :].reshape(-1)))
if new_id in stop_ids:
break
generated.append(new_id)
input_ids = np.concatenate([input_ids, [[new_id]]], axis=1)
return np.array([tokens + generated], dtype=np.int64)
if __name__ == '__main__':
args = parse_args()
np.random.seed(args.seed)
print("Preparing Gemma3 model...")
tokenizer = cv.dnn.Tokenizer.load(args.tokenizer_path)
net = cv.dnn.readNetFromONNX(args.model, cv.dnn.ENGINE_NEW)
gemma3_prompt = build_gemma3_prompt(args.prompt)
print(f"Prompt:\n{gemma3_prompt}")
prompt_len = len(tokenizer.encode(gemma3_prompt)) + 1 # +1 for BOS token
tokens = gemma3_inference(net, gemma3_prompt, args.max_new_tokens, tokenizer, args.use_kv_cache)
response = tokenizer.decode(tokens[0][prompt_len:].tolist())
print(f"Response:\n{response}")
+89
View File
@@ -0,0 +1,89 @@
'''
This is a sample script to run GPT-2 inference in OpenCV using ONNX model.
The script loads the GPT-2 model and runs inference on a given prompt.
Currently script only works with fixed size window, that means
you will have to specify prompt of the same length as when model was exported to ONNX.
Exporting GPT-2 model to ONNX.
To export GPT-2 model to ONNX, you can use the following procedure:
1. Clone fork of Andrej Karpathy's GPT-2 repository:
git clone -b fix-dynamic-axis-export https://github.com/nklskyoy/build-nanogpt
2. Install the required dependencies:
pip install -r requirements.txt
3 Export the model to ONNX:
python export2onnx.py --promt=<Any-promt-you-want>
Run the script:
1. Install the required dependencies:
pip install tiktoken==0.7.0 numpy tqdm
2. Run the script:
python gpt2_inference.py --model=<path-to-onnx-model> --tokenizer_path=<path-to-tokenizer-config> --prompt=<use-promt-of-the-same-length-used-while-exporting>
'''
import numpy as np
import argparse
import cv2 as cv
def parse_args():
parser = argparse.ArgumentParser(description='Use this script to run GPT-2 inference in OpenCV',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--model', type=str, required=True, help='Path to GPT-2 model ONNX model file.')
parser.add_argument('--tokenizer_path', type=str, required=True, help='Path to GPT-2 tokenizer config file.')
parser.add_argument("--prompt", type=str, default="Hello, I'm a language model,", help="Prompt to start with.")
parser.add_argument("--max_seq_len", type=int, default=32, help="Number of tokens to continue.")
parser.add_argument("--seed", type=int, default=0, help="Random seed")
return parser.parse_args()
def stable_softmax(logits):
exp_logits = np.exp(logits - np.max(logits, axis=-1, keepdims=True))
return exp_logits / np.sum(exp_logits, axis=-1, keepdims=True)
def gpt2_inference(net, prompt, max_length, tokenizer):
print("Inferencing GPT-2 model...")
tokens = tokenizer.encode(prompt).reshape(1,-1)
stop_tokens = (50256, ) ## could be extended to include more stop tokens
while 0 < max_length and tokens[:, -1] not in stop_tokens:
net.setInputsNames(['idx'])
net.setInput(tokens, 'idx')
logits = net.forward()
logits = logits[:, -1, :] # (B, vocab_size)
# use hard sampling
new_idx = np.argmax(logits.reshape(-1)).reshape(1,1)
tokens = np.concatenate((tokens, new_idx), axis=1)
max_length -= 1
return tokens
if __name__ == '__main__':
args = parse_args()
print("Preparing GPT-2 model...")
max_length = args.max_seq_len
prompt = args.prompt
tokenizer_path = args.tokenizer_path
net = cv.dnn.readNetFromONNX(args.model, cv.dnn.ENGINE_NEW)
tokenizer = cv.dnn.Tokenizer.load(tokenizer_path)
tokens = gpt2_inference(net, prompt, max_length, tokenizer)
print(tokenizer.decode(tokens[0]))
+111
View File
@@ -0,0 +1,111 @@
//
// this sample demonstrates parsing (segmenting) human body parts from an image using opencv's dnn,
// based on https://github.com/Engineering-Course/LIP_JPPNet
//
// get the pretrained model from: https://www.dropbox.com/s/qag9vzambhhkvxr/lip_jppnet_384.pb?dl=0
//
#include <opencv2/dnn.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
static Mat parse_human(const Mat &image, const std::string &model, int backend=dnn::DNN_BACKEND_DEFAULT, int target=dnn::DNN_TARGET_CPU) {
// this network expects an image and a flipped copy as input
Mat flipped;
flip(image, flipped, 1);
std::vector<Mat> batch;
batch.push_back(image);
batch.push_back(flipped);
Mat blob = dnn::blobFromImages(batch, 1.0, Size(), Scalar(104.00698793, 116.66876762, 122.67891434));
dnn::Net net = dnn::readNet(model);
net.setPreferableBackend(backend);
net.setPreferableTarget(target);
net.setInput(blob);
Mat out = net.forward();
// expected output: [2, 20, 384, 384], (2 lists(orig, flipped) of 20 body part heatmaps 384x384)
// LIP classes:
// 0 Background, 1 Hat, 2 Hair, 3 Glove, 4 Sunglasses, 5 UpperClothes, 6 Dress, 7 Coat, 8 Socks, 9 Pants
// 10 Jumpsuits, 11 Scarf, 12 Skirt, 13 Face, 14 LeftArm, 15 RightArm, 16 LeftLeg, 17 RightLeg, 18 LeftShoe. 19 RightShoe
Vec3b colors[] = {
Vec3b(0, 0, 0), Vec3b(128, 0, 0), Vec3b(255, 0, 0), Vec3b(0, 85, 0), Vec3b(170, 0, 51), Vec3b(255, 85, 0),
Vec3b(0, 0, 85), Vec3b(0, 119, 221), Vec3b(85, 85, 0), Vec3b(0, 85, 85), Vec3b(85, 51, 0), Vec3b(52, 86, 128),
Vec3b(0, 128, 0), Vec3b(0, 0, 255), Vec3b(51, 170, 221), Vec3b(0, 255, 255), Vec3b(85, 255, 170),
Vec3b(170, 255, 85), Vec3b(255, 255, 0), Vec3b(255, 170, 0)
};
Mat segm(image.size(), CV_8UC3, Scalar(0,0,0));
Mat maxval(image.size(), CV_32F, Scalar(0));
// iterate over body part heatmaps (LIP classes)
for (int i=0; i<out.size[1]; i++) {
// resize heatmaps to original image size
// "head" is the original image result, "tail" the flipped copy
Mat head, h(out.size[2], out.size[3], CV_32F, out.ptr<float>(0,i));
resize(h, head, image.size());
// we have to swap the last 3 pairs in the "tail" list
static int tail_order[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,15,14,17,16,19,18};
Mat tail, t(out.size[2], out.size[3], CV_32F, out.ptr<float>(1,tail_order[i]));
resize(t, tail, image.size());
flip(tail, tail, 1);
// mix original and flipped result
Mat avg = (head + tail) * 0.5;
// write color if prob value > maxval
Mat cmask;
compare(avg, maxval, cmask, CMP_GT);
segm.setTo(colors[i], cmask);
// keep largest values for next iteration
max(avg, maxval, maxval);
}
cvtColor(segm, segm, COLOR_RGB2BGR);
return segm;
}
int main(int argc, char**argv)
{
std::string param_keys =
"{help h | | show help screen / args}"
"{image i | | person image to process }"
"{model m |lip_jppnet_384.pb| network model}";
std::string backend_keys = cv::format(
"{ backend | 0 | Choose one of computation backends: "
"%d: automatically (by default), "
"%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"%d: OpenCV implementation, "
"%d: VKCOM, "
"%d: CUDA }", cv::dnn::DNN_BACKEND_DEFAULT, cv::dnn::DNN_BACKEND_INFERENCE_ENGINE, cv::dnn::DNN_BACKEND_OPENCV, cv::dnn::DNN_BACKEND_VKCOM, cv::dnn::DNN_BACKEND_CUDA);
std::string target_keys = cv::format(
"{ target | 0 | Choose one of target computation devices: "
"%d: CPU target (by default), "
"%d: OpenCL, "
"%d: OpenCL fp16 (half-float precision), "
"%d: VPU, "
"%d: Vulkan, "
"%d: CUDA, "
"%d: CUDA fp16 (half-float preprocess) }", cv::dnn::DNN_TARGET_CPU, cv::dnn::DNN_TARGET_OPENCL, cv::dnn::DNN_TARGET_OPENCL_FP16, cv::dnn::DNN_TARGET_MYRIAD, cv::dnn::DNN_TARGET_VULKAN, cv::dnn::DNN_TARGET_CUDA, cv::dnn::DNN_TARGET_CUDA_FP16);
std::string keys = param_keys + backend_keys + target_keys;
CommandLineParser parser(argc, argv, keys);
if (argc == 1 || parser.has("help"))
{
parser.printMessage();
return 0;
}
std::string model = parser.get<std::string>("model");
std::string image = parser.get<std::string>("image");
int backend = parser.get<int>("backend");
int target = parser.get<int>("target");
Mat input = imread(image);
Mat segm = parse_human(input, model, backend, target);
imshow("human parsing", segm);
waitKey();
return 0;
}
+190
View File
@@ -0,0 +1,190 @@
#!/usr/bin/env python
'''
You can download the converted pb model from https://www.dropbox.com/s/qag9vzambhhkvxr/lip_jppnet_384.pb?dl=0
or convert the model yourself.
Follow these steps if you want to convert the original model yourself:
To get original .meta pre-trained model download https://drive.google.com/file/d/1BFVXgeln-bek8TCbRjN6utPAgRE0LJZg/view
For correct convert .meta to .pb model download original repository https://github.com/Engineering-Course/LIP_JPPNet
Change script evaluate_parsing_JPPNet-s2.py for human parsing
1. Remove preprocessing to create image_batch_origin:
with tf.name_scope("create_inputs"):
...
Add
image_batch_origin = tf.placeholder(tf.float32, shape=(2, None, None, 3), name='input')
2. Create input
image = cv2.imread(path/to/image)
image_rev = np.flip(image, axis=1)
input = np.stack([image, image_rev], axis=0)
3. Hardcode image_h and image_w shapes to determine output shapes.
We use default INPUT_SIZE = (384, 384) from evaluate_parsing_JPPNet-s2.py.
parsing_out1 = tf.reduce_mean(tf.stack([tf.image.resize_images(parsing_out1_100, INPUT_SIZE),
tf.image.resize_images(parsing_out1_075, INPUT_SIZE),
tf.image.resize_images(parsing_out1_125, INPUT_SIZE)]), axis=0)
Do similarly with parsing_out2, parsing_out3
4. Remove postprocessing. Last net operation:
raw_output = tf.reduce_mean(tf.stack([parsing_out1, parsing_out2, parsing_out3]), axis=0)
Change:
parsing_ = sess.run(raw_output, feed_dict={'input:0': input})
5. To save model after sess.run(...) add:
input_graph_def = tf.get_default_graph().as_graph_def()
output_node = "Mean_3"
output_graph_def = tf.graph_util.convert_variables_to_constants(sess, input_graph_def, output_node)
output_graph = "LIP_JPPNet.pb"
with tf.gfile.GFile(output_graph, "wb") as f:
f.write(output_graph_def.SerializeToString())'
'''
import argparse
import os.path
import numpy as np
import cv2 as cv
backends = (cv.dnn.DNN_BACKEND_DEFAULT, cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_BACKEND_OPENCV,
cv.dnn.DNN_BACKEND_VKCOM, cv.dnn.DNN_BACKEND_CUDA)
targets = (cv.dnn.DNN_TARGET_CPU, cv.dnn.DNN_TARGET_OPENCL, cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD,
cv.dnn.DNN_TARGET_HDDL, cv.dnn.DNN_TARGET_VULKAN, cv.dnn.DNN_TARGET_CUDA, cv.dnn.DNN_TARGET_CUDA_FP16)
def preprocess(image):
"""
Create 4-dimensional blob from image and flip image
:param image: input image
"""
image_rev = np.flip(image, axis=1)
input = cv.dnn.blobFromImages([image, image_rev], mean=(104.00698793, 116.66876762, 122.67891434))
return input
def run_net(input, model_path, backend, target):
"""
Read network and infer model
:param model_path: path to JPPNet model
:param backend: computation backend
:param target: computation device
"""
net = cv.dnn.readNet(model_path)
net.setPreferableBackend(backend)
net.setPreferableTarget(target)
net.setInput(input)
out = net.forward()
return out
def postprocess(out, input_shape):
"""
Create a grayscale human segmentation
:param out: network output
:param input_shape: input image width and height
"""
# LIP classes
# 0 Background
# 1 Hat
# 2 Hair
# 3 Glove
# 4 Sunglasses
# 5 UpperClothes
# 6 Dress
# 7 Coat
# 8 Socks
# 9 Pants
# 10 Jumpsuits
# 11 Scarf
# 12 Skirt
# 13 Face
# 14 LeftArm
# 15 RightArm
# 16 LeftLeg
# 17 RightLeg
# 18 LeftShoe
# 19 RightShoe
head_output, tail_output = np.split(out, indices_or_sections=[1], axis=0)
head_output = head_output.squeeze(0)
tail_output = tail_output.squeeze(0)
head_output = np.stack([cv.resize(img, dsize=input_shape) for img in head_output[:, ...]])
tail_output = np.stack([cv.resize(img, dsize=input_shape) for img in tail_output[:, ...]])
tail_list = np.split(tail_output, indices_or_sections=list(range(1, 20)), axis=0)
tail_list = [arr.squeeze(0) for arr in tail_list]
tail_list_rev = [tail_list[i] for i in range(14)]
tail_list_rev.extend([tail_list[15], tail_list[14], tail_list[17], tail_list[16], tail_list[19], tail_list[18]])
tail_output_rev = np.stack(tail_list_rev, axis=0)
tail_output_rev = np.flip(tail_output_rev, axis=2)
raw_output_all = np.mean(np.stack([head_output, tail_output_rev], axis=0), axis=0, keepdims=True)
raw_output_all = np.argmax(raw_output_all, axis=1)
raw_output_all = raw_output_all.transpose(1, 2, 0)
return raw_output_all
def decode_labels(gray_image):
"""
Colorize image according to labels
:param gray_image: grayscale human segmentation result
"""
height, width, _ = gray_image.shape
colors = [(0, 0, 0), (128, 0, 0), (255, 0, 0), (0, 85, 0), (170, 0, 51), (255, 85, 0),
(0, 0, 85), (0, 119, 221), (85, 85, 0), (0, 85, 85), (85, 51, 0), (52, 86, 128),
(0, 128, 0), (0, 0, 255), (51, 170, 221), (0, 255, 255),(85, 255, 170),
(170, 255, 85), (255, 255, 0), (255, 170, 0)]
segm = np.stack([colors[idx] for idx in gray_image.flatten()])
segm = segm.reshape(height, width, 3).astype(np.uint8)
segm = cv.cvtColor(segm, cv.COLOR_BGR2RGB)
return segm
def parse_human(image, model_path, backend=cv.dnn.DNN_BACKEND_OPENCV, target=cv.dnn.DNN_TARGET_CPU):
"""
Prepare input for execution, run net and postprocess output to parse human.
:param image: input image
:param model_path: path to JPPNet model
:param backend: name of computation backend
:param target: name of computation target
"""
input = preprocess(image)
input_h, input_w = input.shape[2:]
output = run_net(input, model_path, backend, target)
grayscale_out = postprocess(output, (input_w, input_h))
segmentation = decode_labels(grayscale_out)
return segmentation
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Use this script to run human parsing using JPPNet',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--input', '-i', required=True, help='Path to input image.')
parser.add_argument('--model', '-m', default='lip_jppnet_384.pb', help='Path to pb model.')
parser.add_argument('--backend', choices=backends, default=cv.dnn.DNN_BACKEND_DEFAULT, type=int,
help="Choose one of computation backends: "
"%d: automatically (by default), "
"%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"%d: OpenCV implementation, "
"%d: VKCOM, "
"%d: CUDA"% backends)
parser.add_argument('--target', choices=targets, default=cv.dnn.DNN_TARGET_CPU, type=int,
help='Choose one of target computation devices: '
'%d: CPU target (by default), '
'%d: OpenCL, '
'%d: OpenCL fp16 (half-float precision), '
'%d: NCS2 VPU, '
'%d: HDDL VPU, '
'%d: Vulkan, '
'%d: CUDA, '
'%d: CUDA fp16 (half-float preprocess)' % targets)
args, _ = parser.parse_known_args()
if not os.path.isfile(args.model):
raise OSError("Model not exist")
image = cv.imread(args.input)
output = parse_human(image, args.model, args.backend, args.target)
winName = 'Deep learning human parsing in OpenCV'
cv.namedWindow(winName, cv.WINDOW_AUTOSIZE)
cv.imshow(winName, output)
cv.waitKey()
+230
View File
@@ -0,0 +1,230 @@
/*
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory
of this distribution and at http://opencv.org/license.html.
This sample inpaints the masked area in the given image.
Copyright (C) 2025, Bigvision LLC.
How to use:
Sample command to run:
./example_dnn_inpainting
The system will ask you to draw the mask on area to be inpainted
You can download lama inpainting model using:
`python download_models.py lama`
References:
Github: https://github.com/advimman/lama
ONNX model: https://huggingface.co/Carve/LaMa-ONNX/blob/main/lama_fp32.onnx
ONNX model was further quantized using block quantization from [opencv_zoo](https://github.com/opencv/opencv_zoo)
Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
*/
#include <iostream>
#include <fstream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/dnn.hpp>
#include "common.hpp"
using namespace cv;
using namespace dnn;
using namespace std;
const string about = "Use this script for image inpainting using OpenCV. \n\n"
"Firstly, download required models i.e. lama using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
"To run:\n"
"\t Example: ./example_dnn_inpainting [--input=<image_name>] \n\n"
"Inpainting model path can also be specified using --model argument.\n\n";
const string keyboard_shorcuts = "Keyboard Shorcuts: \n\n"
"Press 'i' to increase brush size.\n"
"Press 'd' to decrease brush size.\n"
"Press 'r' to reset mask.\n"
"Press ' ' (space bar) after selecting area to be inpainted.\n"
"Press ESC to terminate the program.\n\n";
const string param_keys =
"{ help h | | show help message}"
"{ @alias | lama | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ input i | rubberwhale1.png | image file path}";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
bool drawing = false;
Mat maskGray;
int brush_size = 15;
static void drawMask(int event, int x, int y, int, void*) {
if (event == EVENT_LBUTTONDOWN) {
drawing = true;
} else if (event == EVENT_MOUSEMOVE) {
if (drawing) {
circle(maskGray, Point(x, y), brush_size, Scalar(255), -1);
}
} else if (event == EVENT_LBUTTONUP) {
drawing = false;
}
}
int main(int argc, char **argv)
{
CommandLineParser parser(argc, argv, keys);
if (!parser.has("@alias") || parser.has("help"))
{
cout<<about<<endl;
parser.printMessage();
return 0;
}
string modelName = parser.get<String>("@alias");
string zooFile = findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
parser.about("Use this script to run image inpainting using OpenCV.");
const string sha1 = parser.get<String>("sha1");
const string modelPath = findModel(parser.get<String>("model"), sha1);
string imgPath = parser.get<String>("input");
const string backend = parser.get<String>("backend");
const string target = parser.get<String>("target");
int height = parser.get<int>("height");
int width = parser.get<int>("width");
float scale = parser.get<float>("scale");
bool swapRB = parser.get<bool>("rgb");
Scalar mean_v = parser.get<Scalar>("mean");
int stdSize = 20;
int stdWeight = 400;
int stdImgSize = 512;
int imgWidth = -1; // Initialization
int fontSize = 60;
int fontWeight = 500;
cout<<"Model loading..."<<endl;
EngineType engine = ENGINE_AUTO;
if (backend != "default" || target != "cpu"){
engine = ENGINE_CLASSIC;
}
Net net = readNetFromONNX(modelPath, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
FontFace fontFace("sans");
Mat input_image = imread(findFile(imgPath));
if (input_image.empty()) {
cerr << "Error: Input image could not be loaded." << endl;
return -1;
}
double aspectRatio = static_cast<double>(input_image.rows) / static_cast<double>(input_image.cols);
int h = static_cast<int>(width * aspectRatio);
resize(input_image, input_image, Size(width, h));
Mat image = input_image.clone();
imgWidth = min(input_image.rows, input_image.cols);
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize);
fontWeight = min(fontWeight, (stdWeight*imgWidth)/stdImgSize);
cout<<keyboard_shorcuts<<endl;
const string label = "Press 'i' to increase, 'd' to decrease brush size. And 'r' to reset mask. ";
double alpha = 0.5;
Rect r = getTextSize(Size(), label, Point(), fontFace, fontSize, fontWeight);
r.height += 2 * fontSize; // padding
r.width += 10; // padding
// Setting up window
namedWindow("Draw Mask");
setMouseCallback("Draw Mask", drawMask);
Mat tempImage = input_image.clone();
Mat overlay = input_image.clone();
rectangle(overlay, r, Scalar::all(255), FILLED);
addWeighted(overlay, alpha, tempImage, 1 - alpha, 0, tempImage);
putText(tempImage, "Draw the mask on the image. Press space bar when done", Point(10, fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
putText(tempImage, label, Point(10, 2*fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
Mat displayImage = tempImage.clone();
for (;;) {
maskGray = Mat::zeros(input_image.size(), CV_8U);
displayImage = tempImage.clone();
for(;;) {
displayImage.setTo(Scalar(255, 255, 255), maskGray > 0); // Highlight mask area
imshow("Draw Mask", displayImage);
int key = waitKey(30) & 255;
if (key == 'i') {
brush_size += 1;
cout << "Brush size increased to " << brush_size << endl;
} else if (key == 'd') {
brush_size = max(1, brush_size - 1);
cout << "Brush size decreased to " << brush_size << endl;
} else if (key == 'r') {
maskGray = Mat::zeros(image.size(), CV_8U);
displayImage = tempImage.clone();
cout << "Mask cleared." << endl;
} else if (key == ' ') {
break;
} else if (key == 27){
return -1;
}
}
cout<<"Processing image..."<<endl;
// Inference block
Mat image_blob = blobFromImage(image, scale, Size(width, height), mean_v, swapRB, false);
Mat mask_blob;
mask_blob = blobFromImage(maskGray, 1.0, Size(width, height), Scalar(0), false, false);
mask_blob = (mask_blob > 0);
mask_blob.convertTo(mask_blob, CV_32F);
mask_blob = mask_blob/255.0;
net.setInput(image_blob, "image");
net.setInput(mask_blob, "mask");
Mat output = net.forward();
// Post Processing
Mat output_transposed(3, &output.size[1], CV_32F, output.ptr<float>());
vector<Mat> channels;
for (int i = 0; i < 3; ++i) {
channels.push_back(Mat(output_transposed.size[1], output_transposed.size[2], CV_32F,
output_transposed.ptr<float>(i)));
}
Mat output_image;
merge(channels, output_image);
output_image.convertTo(output_image, CV_8U);
resize(output_image, output_image, Size(width, h));
image = output_image;
imshow("Inpainted Output", output_image);
}
return 0;
}
+199
View File
@@ -0,0 +1,199 @@
#!/usr/bin/env python
'''
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory
of this distribution and at http://opencv.org/license.html.
This sample inpaints the masked area in the given image.
Copyright (C) 2025, Bigvision LLC.
How to use:
Sample command to run:
`python inpainting.py`
The system will ask you to draw the mask to be inpainted
You can download lama inpainting model using
`python download_models.py lama`
References:
Github: https://github.com/advimman/lama
ONNX model: https://huggingface.co/Carve/LaMa-ONNX/blob/main/lama_fp32.onnx
ONNX model was further quantized using block quantization from [opencv_zoo](https://github.com/opencv/opencv_zoo)
Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
'''
import argparse
import os.path
import numpy as np
import cv2 as cv
from common import *
def help():
print(
'''
Use this script for image inpainting using OpenCV.
Firstly, download required models i.e. lama using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
To run:
Example: python inpainting.py [--input=<image_name>]
Inpainting model path can also be specified using --model argument.
'''
)
def keyboard_shorcuts():
print('''
Keyboard Shorcuts:
Press 'i' to increase brush size.
Press 'd' to decrease brush size.
Press 'r' to reset mask.
Press ' ' (space bar) after selecting area to be inpainted.
Press ESC to terminate the program.
'''
)
def get_args_parser():
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--input', '-i', default="rubberwhale1.png", help='Path to image file.', required=False)
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'inpainting', prefix="", alias="lama")
parser = argparse.ArgumentParser(parents=[parser],
description='Image inpainting using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
return parser.parse_args()
drawing = False
mask_gray = None
brush_size = 15
def draw_mask(event, x, y, flags, param):
global drawing, mask_gray, brush_size
if event == cv.EVENT_LBUTTONDOWN:
drawing = True
elif event == cv.EVENT_MOUSEMOVE:
if drawing:
cv.circle(mask_gray, (x, y), brush_size, (255), thickness=-1)
elif event == cv.EVENT_LBUTTONUP:
drawing = False
def main():
global mask_gray, brush_size
print("Model loading...")
if hasattr(args, 'help'):
help()
exit(1)
args.model = findModel(args.model, args.sha1)
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
input_image = cv.imread(findFile(args.input))
aspect_ratio = input_image.shape[0]/input_image.shape[1]
height = int(args.width*aspect_ratio)
input_image = cv.resize(input_image, (args.width, height))
image = input_image.copy()
keyboard_shorcuts()
stdSize = 0.7
stdWeight = 2
stdImgSize = 512
imgWidth = min(input_image.shape[:2])
fontSize = min(1.5, (stdSize*imgWidth)/stdImgSize)
fontThickness = max(1,(stdWeight*imgWidth)//stdImgSize)
label = "Press 'i' to increase, 'd' to decrease brush size. And 'r' to reset mask. "
labelSize, _ = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
alpha = 0.5
# Setting up the window
cv.namedWindow("Draw Mask")
cv.setMouseCallback("Draw Mask", draw_mask)
temp_image = input_image.copy()
overlay = input_image.copy()
cv.rectangle(overlay, (0, 0), (labelSize[0]+10, labelSize[1]+int(30*fontSize)), (255, 255, 255), cv.FILLED)
cv.addWeighted(overlay, alpha, temp_image, 1 - alpha, 0, temp_image)
cv.putText(temp_image, "Draw the mask on the image. Press space bar when done.", (10, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.putText(temp_image, label, (10, int(50*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
display_image = temp_image.copy()
while True:
mask_gray = np.zeros((input_image.shape[0], input_image.shape[1]), dtype=np.uint8)
display_image = temp_image.copy()
while True:
display_image[mask_gray > 0] = [255, 255, 255]
cv.imshow("Draw Mask", display_image)
key = cv.waitKey(30) & 0xFF
if key == ord('i'): # Increase brush size
brush_size += 1
print(f"Brush size increased to {brush_size}")
elif key == ord('d'): # Decrease brush size
brush_size = max(1, brush_size - 1)
print(f"Brush size decreased to {brush_size}")
elif key == ord('r'): # clear the mask
mask_gray = np.zeros((input_image.shape[0], input_image.shape[1]), dtype=np.uint8)
display_image = temp_image.copy()
print(f"Mask cleared")
elif key == ord(' '): # Press space bar to finish drawing
break
elif key == 27:
exit()
print("Processing image...")
# Inference block
image_blob = cv.dnn.blobFromImage(image, args.scale, (args.width, args.height), args.mean, args.rgb, False)
mask_blob = cv.dnn.blobFromImage(mask_gray, scalefactor=1.0, size=(args.width, args.height), mean=(0,), swapRB=False, crop=False)
mask_blob = (mask_blob > 0).astype(np.float32)
net.setInput(image_blob, "image")
net.setInput(mask_blob, "mask")
output = net.forward()
# Postprocessing
output_image = output[0]
output_image = np.transpose(output_image, (1, 2, 0))
output_image = (output_image).astype(np.uint8)
output_image = cv.resize(output_image, (args.width, height))
image = output_image
cv.imshow("Inpainted Output", output_image)
if __name__ == '__main__':
args = get_args_parser()
main()
+229
View File
@@ -0,0 +1,229 @@
<!DOCTYPE html>
<html>
<head>
<script async src="../../opencv.js" type="text/javascript"></script>
<script src="../../utils.js" type="text/javascript"></script>
<script type='text/javascript'>
var netDet = undefined, netRecogn = undefined;
var persons = {};
//! [Run face detection model]
function detectFaces(img) {
netDet.setInputSize(new cv.Size(img.cols, img.rows));
var out = new cv.Mat();
netDet.detect(img, out);
var faces = [];
for (var i = 0, n = out.data32F.length; i < n; i += 15) {
var left = out.data32F[i];
var top = out.data32F[i + 1];
var right = (out.data32F[i] + out.data32F[i + 2]);
var bottom = (out.data32F[i + 1] + out.data32F[i + 3]);
left = Math.min(Math.max(0, left), img.cols - 1);
top = Math.min(Math.max(0, top), img.rows - 1);
right = Math.min(Math.max(0, right), img.cols - 1);
bottom = Math.min(Math.max(0, bottom), img.rows - 1);
if (left < right && top < bottom) {
faces.push({
x: left,
y: top,
width: right - left,
height: bottom - top,
x1: out.data32F[i + 4] < 0 || out.data32F[i + 4] > img.cols - 1 ? -1 : out.data32F[i + 4],
y1: out.data32F[i + 5] < 0 || out.data32F[i + 5] > img.rows - 1 ? -1 : out.data32F[i + 5],
x2: out.data32F[i + 6] < 0 || out.data32F[i + 6] > img.cols - 1 ? -1 : out.data32F[i + 6],
y2: out.data32F[i + 7] < 0 || out.data32F[i + 7] > img.rows - 1 ? -1 : out.data32F[i + 7],
x3: out.data32F[i + 8] < 0 || out.data32F[i + 8] > img.cols - 1 ? -1 : out.data32F[i + 8],
y3: out.data32F[i + 9] < 0 || out.data32F[i + 9] > img.rows - 1 ? -1 : out.data32F[i + 9],
x4: out.data32F[i + 10] < 0 || out.data32F[i + 10] > img.cols - 1 ? -1 : out.data32F[i + 10],
y4: out.data32F[i + 11] < 0 || out.data32F[i + 11] > img.rows - 1 ? -1 : out.data32F[i + 11],
x5: out.data32F[i + 12] < 0 || out.data32F[i + 12] > img.cols - 1 ? -1 : out.data32F[i + 12],
y5: out.data32F[i + 13] < 0 || out.data32F[i + 13] > img.rows - 1 ? -1 : out.data32F[i + 13],
confidence: out.data32F[i + 14]
})
}
}
out.delete();
return faces;
};
//! [Run face detection model]
//! [Get 128 floating points feature vector]
function face2vec(face) {
var blob = cv.blobFromImage(face, 1.0, {width: 112, height: 112}, [0, 0, 0, 0], true, false)
netRecogn.setInput(blob);
var vec = netRecogn.forward();
blob.delete();
return vec;
};
//! [Get 128 floating points feature vector]
//! [Recognize]
function recognize(face) {
var vec = face2vec(face);
var bestMatchName = 'unknown';
var bestMatchScore = 30; // Threshold for face recognition.
for (name in persons) {
var personVec = persons[name];
var score = vec.dot(personVec);
if (score > bestMatchScore) {
bestMatchScore = score;
bestMatchName = name;
}
}
vec.delete();
return bestMatchName;
};
//! [Recognize]
function loadModels(callback) {
var utils = new Utils('');
var detectModel = 'https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx';
var recognModel = 'https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx';
document.getElementById('status').innerHTML = 'Downloading YuNet model';
utils.createFileFromUrl('face_detection_yunet_2023mar.onnx', detectModel, () => {
document.getElementById('status').innerHTML = 'Downloading OpenFace model';
utils.createFileFromUrl('face_recognition_sface_2021dec.onnx', recognModel, () => {
document.getElementById('status').innerHTML = '';
netDet = new cv.FaceDetectorYN("face_detection_yunet_2023mar.onnx", "", new cv.Size(320, 320), 0.9, 0.3, 5000);
netRecogn = cv.readNet('face_recognition_sface_2021dec.onnx');
callback();
});
});
};
function main() {
if(!cv.FaceDetectorYN){
alert(`Error: This sample require OpenCV.js built with FaceDetectorYN. Please rebuild it with FaceDetectorYN or use the latest version of OpenCV.js.`);
return;
}
// Create a camera object.
var output = document.getElementById('output');
var camera = document.createElement("video");
camera.setAttribute("width", output.width);
camera.setAttribute("height", output.height);
// Get a permission from user to use a camera.
navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then(function(stream) {
camera.srcObject = stream;
camera.onloadedmetadata = function(e) {
camera.play();
};
});
//! [Open a camera stream]
var cap = new cv.VideoCapture(camera);
var frame = new cv.Mat(camera.height, camera.width, cv.CV_8UC4);
var frameBGR = new cv.Mat(camera.height, camera.width, cv.CV_8UC3);
//! [Open a camera stream]
//! [Add a person]
document.getElementById('addPersonButton').onclick = function() {
var rects = detectFaces(frameBGR);
if (rects.length > 0) {
var face = frameBGR.roi(rects[0]);
var name = prompt('Say your name:');
var cell = document.getElementById("targetNames").insertCell(0);
cell.innerHTML = name;
persons[name] = face2vec(face).clone();
var canvas = document.createElement("canvas");
canvas.setAttribute("width", 112);
canvas.setAttribute("height", 112);
var cell = document.getElementById("targetImgs").insertCell(0);
cell.appendChild(canvas);
var faceResized = new cv.Mat(canvas.height, canvas.width, cv.CV_8UC3);
cv.resize(face, faceResized, {width: canvas.width, height: canvas.height});
cv.cvtColor(faceResized, faceResized, cv.COLOR_BGR2RGB);
cv.imshow(canvas, faceResized);
faceResized.delete();
}
};
//! [Add a person]
//! [Define frames processing]
var isRunning = false;
const FPS = 30; // Target number of frames processed per second.
function captureFrame() {
var begin = Date.now();
cap.read(frame); // Read a frame from camera
cv.cvtColor(frame, frameBGR, cv.COLOR_RGBA2BGR);
var faces = detectFaces(frameBGR);
faces.forEach(function(rect) {
cv.rectangle(frame, {x: rect.x, y: rect.y}, {x: rect.x + rect.width, y: rect.y + rect.height}, [0, 255, 0, 255]);
if(rect.x1>0 && rect.y1>0)
cv.circle(frame, {x: rect.x1, y: rect.y1}, 2, [255, 0, 0, 255], 2)
if(rect.x2>0 && rect.y2>0)
cv.circle(frame, {x: rect.x2, y: rect.y2}, 2, [0, 0, 255, 255], 2)
if(rect.x3>0 && rect.y3>0)
cv.circle(frame, {x: rect.x3, y: rect.y3}, 2, [0, 255, 0, 255], 2)
if(rect.x4>0 && rect.y4>0)
cv.circle(frame, {x: rect.x4, y: rect.y4}, 2, [255, 0, 255, 255], 2)
if(rect.x5>0 && rect.y5>0)
cv.circle(frame, {x: rect.x5, y: rect.y5}, 2, [0, 255, 255, 255], 2)
var face = frameBGR.roi(rect);
var name = recognize(face);
cv.putText(frame, name, {x: rect.x, y: rect.y}, cv.FONT_HERSHEY_SIMPLEX, 1.0, [0, 255, 0, 255]);
});
cv.imshow(output, frame);
// Loop this function.
if (isRunning) {
var delay = 1000 / FPS - (Date.now() - begin);
setTimeout(captureFrame, delay);
}
};
//! [Define frames processing]
document.getElementById('startStopButton').onclick = function toggle() {
if (isRunning) {
isRunning = false;
document.getElementById('startStopButton').innerHTML = 'Start';
document.getElementById('addPersonButton').disabled = true;
} else {
function run() {
isRunning = true;
captureFrame();
document.getElementById('startStopButton').innerHTML = 'Stop';
document.getElementById('startStopButton').disabled = false;
document.getElementById('addPersonButton').disabled = false;
}
if (netDet == undefined || netRecogn == undefined) {
document.getElementById('startStopButton').disabled = true;
loadModels(run); // Load models and run a pipeline;
} else {
run();
}
}
};
document.getElementById('startStopButton').disabled = false;
};
</script>
</head>
<body onload="cv['onRuntimeInitialized']=()=>{ main() }">
<button id="startStopButton" type="button" disabled="true">Start</button>
<div id="status"></div>
<canvas id="output" width=640 height=480 style="max-width: 100%"></canvas>
<table>
<tr id="targetImgs"></tr>
<tr id="targetNames"></tr>
</table>
<button id="addPersonButton" type="button" disabled="true">Add a person</button>
</body>
</html>
+593
View File
@@ -0,0 +1,593 @@
import cv2 as cv
import numpy as np
import argparse
from tqdm import tqdm
from functools import partial
from copy import deepcopy
import os
from common import *
## General information on how to use the sample
'''
This sample proposes experimental inpainting sample using Latent Diffusion Model (LDM) for inpainting.
Most of the script is based on the code from the official repository of the LDM model: https://github.com/CompVis/latent-diffusion
Current limitations of the script:
- Slow diffusion sampling
- Not exact reproduction of the results from the original repository (due to issues related deviation in convolution operation.
See issue for more details: https://github.com/opencv/opencv/pull/25973)
LDM inpainting model was converted to ONNX graph using following steps:
Generate the onnx model using this [repo](https://github.com/Abdurrahheem/latent-diffusion/tree/ash/export2onnx) and follow instructions below
- git clone https://github.com/Abdurrahheem/latent-diffusion.git
- cd latent-diffusion
- conda env create -f environment.yaml
- conda activate ldm
- wget -O models/ldm/inpainting_big/last.ckpt https://heibox.uni-heidelberg.de/f/4d9ac7ea40c64582b7c9/?dl=1
- python -m scripts.inpaint.py --indir data/inpainting_examples/ --outdir outputs/inpainting_results --export=True
2. Build opencv
3. Run the script
- cd opencv/samples/dnn
- Download models using `python download_models.py ldm_inpainting`
- python ldm_inpainting.py
- For more options, use python ldm_inpainting.py -h
After running the code you will be promted with image. You can click on left mouse button and start selecting a region you would like to be inpainted (deleted).
Once you finish marking the region, click on left mouse button again and press esc button on your keyboard. The inpainting proccess will start.
Note: If you are running it on CPU it might take a large chank of time.
Also make sure to have abount 15GB of RAM to make proccess faster (other wise swapping will kick in and everything will be slower)
'''
def get_args_parser():
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--input', '-i', default="rubberwhale1.png", help='Path to image file.', required=False)
parser.add_argument('--samples', '-s', type=int, help='Number of times to sample the model.', default=50)
parser.add_argument('--mask', '-m', type=str, help='Path to mask image. If not provided, interactive mask creation will be used.', default=None)
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'ldm_inpainting', prefix="", alias="ldm_inpainting")
add_preproc_args(args.zoo, parser, 'ldm_inpainting', prefix="encoder_", alias="ldm_inpainting")
add_preproc_args(args.zoo, parser, 'ldm_inpainting', prefix="decoder_", alias="ldm_inpainting")
add_preproc_args(args.zoo, parser, 'ldm_inpainting', prefix="diffusor_", alias="ldm_inpainting")
parser = argparse.ArgumentParser(parents=[parser],
description='Diffusion based image inpainting using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
return parser.parse_args()
stdSize = 0.7
stdWeight = 2
stdImgSize = 512
imgWidth = None
fontSize = 1.5
fontThickness = 1
def keyboard_shorcuts():
print('''
Keyboard Shorcuts:
Press 'i' to increase brush size.
Press 'd' to decrease brush size.
Press 'r' to reset mask.
Press ' ' (space bar) after selecting area to be inpainted.
Press ESC to terminate the program.
'''
)
def help():
print(
'''
Use this script for image inpainting using OpenCV.
Firstly, download required models i.e. ldm_inpainting using `download_models.py ldm_inpainting` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
To run:
Example: python ldm_inpainting.py
'''
)
def make_batch_blob(image, mask):
blob_image = cv.dnn.blobFromImage(image, scalefactor=args.scale, size=(args.width, args.height), mean=args.mean, swapRB=args.rgb, crop=False)
blob_mask = cv.dnn.blobFromImage(mask, scalefactor=args.scale, size=(args.width, args.height), mean=args.mean, swapRB=False, crop=False)
blob_mask = (blob_mask >= 0.5).astype(np.float32)
masked_image = (1 - blob_mask) * blob_image
batch = {
"image": blob_image,
"mask": blob_mask,
"masked_image": masked_image
}
for k in batch:
batch[k] = batch[k]*2.0 - 1.0
return batch
def noise_like(shape, repeat=False):
repeat_noise = lambda: np.random.randn((1, *shape[1:])).repeat(shape[0], *((1,) * (len(shape) - 1)))
noise = lambda: np.random.randn(*shape)
return repeat_noise() if repeat else noise()
def make_ddim_timesteps(ddim_discr_method, num_ddim_timesteps, num_ddpm_timesteps, verbose=True):
if ddim_discr_method == 'uniform':
c = num_ddpm_timesteps // num_ddim_timesteps
ddim_timesteps = np.asarray(list(range(0, num_ddpm_timesteps, c)))
elif ddim_discr_method == 'quad':
ddim_timesteps = ((np.linspace(0, np.sqrt(num_ddpm_timesteps * .8), num_ddim_timesteps)) ** 2).astype(int)
else:
raise NotImplementedError(f'There is no ddim discretization method called "{ddim_discr_method}"')
# assert ddim_timesteps.shape[0] == num_ddim_timesteps
# add one to get the final alpha values right (the ones from first scale to data during sampling)
steps_out = ddim_timesteps + 1
if verbose:
print(f'Selected timesteps for ddim sampler: {steps_out}')
return steps_out
def make_ddim_sampling_parameters(alphacums, ddim_timesteps, eta, verbose=True):
# select alphas for computing the variance schedule
alphas = alphacums[ddim_timesteps]
alphas_prev = np.asarray([alphacums[0]] + alphacums[ddim_timesteps[:-1]].tolist())
# according the the formula provided in https://arxiv.org/abs/2010.02502
sigmas = eta * np.sqrt((1 - alphas_prev) / (1 - alphas) * (1 - alphas / alphas_prev))
if verbose:
print(f'Selected alphas for ddim sampler: a_t: {alphas}; a_(t-1): {alphas_prev}')
print(f'For the chosen value of eta, which is {eta}, '
f'this results in the following sigma_t schedule for ddim sampler {sigmas}')
return sigmas, alphas, alphas_prev
def make_beta_schedule(schedule, n_timestep, linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3):
if schedule == "linear":
betas = (
np.linspace(linear_start ** 0.5, linear_end ** 0.5, n_timestep).astype(np.float64) ** 2
)
elif schedule == "cosine":
timesteps = (
np.arange(n_timestep + 1).astype(np.float64) / n_timestep + cosine_s
)
alphas = timesteps / (1 + cosine_s) * np.pi / 2
alphas = np.cos(alphas).pow(2)
alphas = alphas / alphas[0]
betas = 1 - alphas[1:] / alphas[:-1]
betas = np.clip(betas, a_min=0, a_max=0.999)
elif schedule == "sqrt_linear":
betas = np.linspace(linear_start, linear_end, n_timestep).astype(np.float64)
elif schedule == "sqrt":
betas = np.linspace(linear_start, linear_end, n_timestep).astype(np.float64) ** 0.5
else:
raise ValueError(f"schedule '{schedule}' unknown.")
return betas
class DDIMSampler(object):
def __init__(self, model, schedule="linear", ddpm_num_timesteps=1000):
super().__init__()
self.model = model
self.ddpm_num_timesteps = ddpm_num_timesteps
self.schedule = schedule
def register_buffer(self, name, attr):
setattr(self, name, attr)
def make_schedule(self, ddim_num_steps, ddim_discretize="uniform", ddim_eta=0., verbose=True):
self.ddim_timesteps = make_ddim_timesteps(ddim_discr_method=ddim_discretize, num_ddim_timesteps=ddim_num_steps,
num_ddpm_timesteps=self.ddpm_num_timesteps,verbose=verbose)
alphas_cumprod = self.model.alphas_cumprod
assert alphas_cumprod.shape[0] == self.ddpm_num_timesteps, 'alphas have to be defined for each timestep'
to_numpy = partial(np.array, copy=True, dtype=np.float32)
self.register_buffer('betas', to_numpy(self.model.betas))
self.register_buffer('alphas_cumprod', to_numpy(alphas_cumprod))
self.register_buffer('alphas_cumprod_prev', to_numpy(self.model.alphas_cumprod_prev))
# calculations for diffusion q(x_t | x_{t-1}) and others
self.register_buffer('sqrt_alphas_cumprod', to_numpy(np.sqrt(alphas_cumprod)))
self.register_buffer('sqrt_one_minus_alphas_cumprod', to_numpy(np.sqrt(1. - alphas_cumprod)))
self.register_buffer('log_one_minus_alphas_cumprod', to_numpy(np.log(1. - alphas_cumprod)))
self.register_buffer('sqrt_recip_alphas_cumprod', to_numpy(np.sqrt(1. / alphas_cumprod)))
self.register_buffer('sqrt_recipm1_alphas_cumprod', to_numpy(np.sqrt(1. / alphas_cumprod - 1)))
# ddim sampling parameters
ddim_sigmas, ddim_alphas, ddim_alphas_prev = make_ddim_sampling_parameters(alphacums=alphas_cumprod,
ddim_timesteps=self.ddim_timesteps,
eta=ddim_eta,verbose=verbose)
self.register_buffer('ddim_sigmas', ddim_sigmas)
self.register_buffer('ddim_alphas', ddim_alphas)
self.register_buffer('ddim_alphas_prev', ddim_alphas_prev)
self.register_buffer('ddim_sqrt_one_minus_alphas', np.sqrt(1. - ddim_alphas))
sigmas_for_original_sampling_steps = ddim_eta * np.sqrt(
(1 - self.alphas_cumprod_prev) / (1 - self.alphas_cumprod) * (
1 - self.alphas_cumprod / self.alphas_cumprod_prev))
self.register_buffer('ddim_sigmas_for_original_num_steps', sigmas_for_original_sampling_steps)
def sample(self,
S,
batch_size,
shape,
conditioning=None,
eta=0.,
temperature=1.,
verbose=True,
x_T=None,
log_every_t=100,
unconditional_guidance_scale=1.,
unconditional_conditioning=None,
# this has to come in the same format as the conditioning, # e.g. as encoded tokens, ...
**kwargs
):
if conditioning is not None:
if isinstance(conditioning, dict):
cbs = conditioning[list(conditioning.keys())[0]].shape[0]
if cbs != batch_size:
print(f"Warning: Got {cbs} conditionings but batch-size is {batch_size}")
else:
if conditioning.shape[0] != batch_size:
print(f"Warning: Got {conditioning.shape[0]} conditionings but batch-size is {batch_size}")
self.make_schedule(ddim_num_steps=S, ddim_eta=eta, verbose=verbose)
# sampling
C, H, W = shape
size = (batch_size, C, H, W)
print(f'Data shape for DDIM sampling is {size}, eta {eta}')
samples, intermediates = self.ddim_sampling(conditioning, size,
ddim_use_original_steps=False,
temperature=temperature,
x_T=x_T,
log_every_t=log_every_t,
unconditional_guidance_scale=unconditional_guidance_scale,
unconditional_conditioning=unconditional_conditioning,
)
return samples, intermediates
def ddim_sampling(self, cond, shape,
x_T=None, ddim_use_original_steps=False,
timesteps=None,log_every_t=100, temperature=1.,
unconditional_guidance_scale=1., unconditional_conditioning=None,):
b = shape[0]
if x_T is None:
img = np.random.randn(*shape)
else:
img = x_T
if timesteps is None:
timesteps = self.ddpm_num_timesteps if ddim_use_original_steps else self.ddim_timesteps
elif timesteps is not None and not ddim_use_original_steps:
subset_end = int(min(timesteps / self.ddim_timesteps.shape[0], 1) * self.ddim_timesteps.shape[0]) - 1
timesteps = self.ddim_timesteps[:subset_end]
intermediates = {'x_inter': [img], 'pred_x0': [img]}
time_range = reversed(range(0,timesteps)) if ddim_use_original_steps else np.flip(timesteps)
total_steps = timesteps if ddim_use_original_steps else timesteps.shape[0]
print(f"Running DDIM Sampling with {total_steps} timesteps")
iterator = tqdm(time_range, desc='DDIM Sampler', total=total_steps)
for i, step in enumerate(iterator):
index = total_steps - i - 1
ts = np.full((b, ), step, dtype=np.int64)
outs = self.p_sample_ddim(img, cond, ts, index=index, use_original_steps=ddim_use_original_steps,
temperature=temperature, unconditional_guidance_scale=unconditional_guidance_scale,
unconditional_conditioning=unconditional_conditioning)
img, pred_x0 = outs
if index % log_every_t == 0 or index == total_steps - 1:
intermediates['x_inter'].append(img)
intermediates['pred_x0'].append(pred_x0)
return img, intermediates
def p_sample_ddim(self, x, c, t, index, repeat_noise=False, use_original_steps=False,
temperature=1., unconditional_guidance_scale=1., unconditional_conditioning=None):
b = x.shape[0]
if unconditional_conditioning is None or unconditional_guidance_scale == 1.:
e_t = self.model.apply_model(x, t, c)
alphas = self.model.alphas_cumprod if use_original_steps else self.ddim_alphas
alphas_prev = self.model.alphas_cumprod_prev if use_original_steps else self.ddim_alphas_prev
sqrt_one_minus_alphas = self.model.sqrt_one_minus_alphas_cumprod if use_original_steps else self.ddim_sqrt_one_minus_alphas
sigmas = self.model.ddim_sigmas_for_original_num_steps if use_original_steps else self.ddim_sigmas
# select parameters corresponding to the currently considered timestep
a_t = np.full((b, 1, 1, 1), alphas[index])
a_prev = np.full((b, 1, 1, 1), alphas_prev[index])
sigma_t = np.full((b, 1, 1, 1), sigmas[index])
sqrt_one_minus_at = np.full((b, 1, 1, 1), sqrt_one_minus_alphas[index])
# current prediction for x_0
pred_x0 = (x - sqrt_one_minus_at * e_t) / np.sqrt(a_t)
# direction pointing to x_t
dir_xt = np.sqrt(1. - a_prev - sigma_t**2) * e_t
noise = sigma_t * noise_like(x.shape, repeat_noise) * temperature
x_prev = np.sqrt(a_prev) * pred_x0 + dir_xt + noise
return x_prev, pred_x0
class DDIMInpainter(object):
def __init__(self,
args,
v_posterior=0., # weight for choosing posterior variance as sigma = (1-v) * beta_tilde + v * beta
parameterization="eps", # all assuming fixed variance schedules
linear_start=0.0015,
linear_end=0.0205,
conditioning_key="concat",
):
super().__init__()
self.v_posterior = v_posterior
self.parameterization = parameterization
self.conditioning_key = conditioning_key
self.register_schedule(linear_start=linear_start, linear_end=linear_end)
# Initialize models using provided paths or download if necessary
encoder_path = findModel(args.encoder_model, args.encoder_sha1)
decoder_path = findModel(args.decoder_model, args.decoder_sha1)
diffusor_path = findModel(args.diffusor_model, args.diffusor_sha1)
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
self.encoder = cv.dnn.readNet(encoder_path, "", "", engine)
self.diffusor = cv.dnn.readNet(diffusor_path, "", "", engine)
self.decoder = cv.dnn.readNet(decoder_path, "", "", engine)
self.sampler = DDIMSampler(self, ddpm_num_timesteps=self.num_timesteps)
self.set_backend(backend=get_backend_id(args.backend), target=get_target_id(args.target))
def set_backend(self, backend=cv.dnn.DNN_BACKEND_DEFAULT, target=cv.dnn.DNN_TARGET_CPU):
self.encoder.setPreferableBackend(backend)
self.encoder.setPreferableTarget(target)
self.decoder.setPreferableBackend(backend)
self.decoder.setPreferableTarget(target)
self.diffusor.setPreferableBackend(backend)
self.diffusor.setPreferableTarget(target)
def apply_diffusor(self, x, timestep, cond):
x = np.concatenate([x, cond], axis=1)
x = cv.Mat(x.astype(np.float32))
timestep = cv.Mat(timestep.astype(np.int64))
names = ["xc, t", "timesteps"]
self.diffusor.setInputsNames(names)
self.diffusor.setInput(x, names[0])
self.diffusor.setInput(timestep, names[1])
output = self.diffusor.forward()
return output
def register_buffer(self, name, attr):
setattr(self, name, attr)
def register_schedule(self, given_betas=None, beta_schedule="linear", timesteps=1000,
linear_start=1e-4, linear_end=2e-2, cosine_s=8e-3):
if given_betas is not None:
betas = given_betas
else:
betas = make_beta_schedule(beta_schedule, timesteps, linear_start=linear_start, linear_end=linear_end,
cosine_s=cosine_s)
alphas = 1. - betas
alphas_cumprod = np.cumprod(alphas, axis=0)
alphas_cumprod_prev = np.append(1., alphas_cumprod[:-1])
timesteps, = betas.shape
self.num_timesteps = int(timesteps)
self.linear_start = linear_start
self.linear_end = linear_end
assert alphas_cumprod.shape[0] == self.num_timesteps, 'alphas have to be defined for each timestep'
to_numpy = partial(np.array, dtype=np.float32)
self.register_buffer('betas', to_numpy(betas))
self.register_buffer('alphas_cumprod', to_numpy(alphas_cumprod))
self.register_buffer('alphas_cumprod_prev', to_numpy(alphas_cumprod_prev))
# calculations for diffusion q(x_t | x_{t-1}) and others
self.register_buffer('sqrt_alphas_cumprod', to_numpy(np.sqrt(alphas_cumprod)))
self.register_buffer('sqrt_one_minus_alphas_cumprod', to_numpy(np.sqrt(1. - alphas_cumprod)))
self.register_buffer('log_one_minus_alphas_cumprod', to_numpy(np.log(1. - alphas_cumprod)))
self.register_buffer('sqrt_recip_alphas_cumprod', to_numpy(np.sqrt(1. / alphas_cumprod)))
self.register_buffer('sqrt_recipm1_alphas_cumprod', to_numpy(np.sqrt(1. / alphas_cumprod - 1)))
# calculations for posterior q(x_{t-1} | x_t, x_0)
posterior_variance = (1 - self.v_posterior) * betas * (1. - alphas_cumprod_prev) / (
1. - alphas_cumprod) + self.v_posterior * betas
# above: equal to 1. / (1. / (1. - alpha_cumprod_tm1) + alpha_t / beta_t)
self.register_buffer('posterior_variance', to_numpy(posterior_variance))
# below: log calculation clipped because the posterior variance is 0 at the beginning of the diffusion chain
self.register_buffer('posterior_log_variance_clipped', to_numpy(np.log(np.maximum(posterior_variance, 1e-20))))
self.register_buffer('posterior_mean_coef1', to_numpy(
betas * np.sqrt(alphas_cumprod_prev) / (1. - alphas_cumprod)))
self.register_buffer('posterior_mean_coef2', to_numpy(
(1. - alphas_cumprod_prev) * np.sqrt(alphas) / (1. - alphas_cumprod)))
if self.parameterization == "eps":
lvlb_weights = self.betas ** 2 / (
2 * self.posterior_variance * to_numpy(alphas) * (1 - self.alphas_cumprod))
elif self.parameterization == "x0":
lvlb_weights = 0.5 * np.sqrt(alphas_cumprod) / (2. * 1 - alphas_cumprod)
else:
raise NotImplementedError("mu not supported")
# TODO how to choose this term
lvlb_weights[0] = lvlb_weights[1]
self.register_buffer('lvlb_weights', lvlb_weights)
assert not np.isnan(self.lvlb_weights).all()
def apply_model(self, x_noisy, t, cond, return_ids=False):
if isinstance(cond, dict):
# hybrid case, cond is exptected to be a dict
pass
else:
# if not isinstance(cond, list):
# cond = [cond]
key = 'c_concat' if self.conditioning_key == 'concat' else 'c_crossattn'
cond = {key: cond}
x_recon = self.apply_diffusor(x_noisy, t, cond['c_concat'])
if isinstance(x_recon, tuple) and not return_ids:
return x_recon[0]
else:
return x_recon
def inpaint(self, image : np.ndarray, mask : np.ndarray, S : int = 50) -> np.ndarray:
inpainted = self(image, mask, S)
return np.squeeze(inpainted)
def __call__(self, image : np.ndarray, mask : np.ndarray, S : int = 50) -> np.ndarray:
# Encode the image and mask
self.encoder.setInput(image)
c = self.encoder.forward()
cc = cv.resize(np.squeeze(mask), dsize=(c.shape[3], c.shape[2]), interpolation=cv.INTER_NEAREST) #TODO:check for correcteness of intepolation
cc = cc[None,None]
c = np.concatenate([c, cc], axis=1)
shape = (c.shape[1] - 1,) + c.shape[2:]
# Sample from the model
samples_ddim, _ = self.sampler.sample(
S=S,
conditioning=c,
batch_size=c.shape[0],
shape=shape,
verbose=False)
## Decode the sample
samples_ddim = samples_ddim.astype(np.float32)
samples_ddim = cv.Mat(samples_ddim)
self.decoder.setInput(samples_ddim)
x_samples_ddim = self.decoder.forward()
image = np.clip((image + 1.0) / 2.0, a_min=0.0, a_max=1.0)
mask = np.clip((mask + 1.0) / 2.0, a_min=0.0, a_max=1.0)
predicted_image = np.clip((x_samples_ddim + 1.0) / 2.0, a_min=0.0, a_max=1.0)
inpainted = (1 - mask) * image + mask * predicted_image
inpainted = np.transpose(inpainted, (0, 2, 3, 1)) * 255
return inpainted
def create_mask(img):
drawing = False # True if the mouse is pressed
brush_size = 20
# Mouse callback function
def draw_circle(event, x, y, flags, param):
nonlocal drawing, brush_size
if event == cv.EVENT_LBUTTONDOWN:
drawing = True
elif event == cv.EVENT_MOUSEMOVE:
if drawing:
cv.circle(mask, (x, y), brush_size, (255), thickness=-1)
elif event == cv.EVENT_LBUTTONUP:
drawing = False
# Create window with instructions
window_name = 'Draw Mask'
cv.namedWindow(window_name)
cv.setMouseCallback(window_name, draw_circle)
label = "Press 'i' to increase, 'd' to decrease brush size. And 'r' to reset mask. "
labelSize, _ = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
alpha = 0.5
temp_image = img.copy()
overlay = img.copy()
cv.rectangle(overlay, (0, 0), (labelSize[0]+10, labelSize[1]+int(30*fontSize)), (255, 255, 255), cv.FILLED)
cv.addWeighted(overlay, alpha, temp_image, 1 - alpha, 0, temp_image)
cv.putText(temp_image, "Draw the mask on the image. Press space bar when done.", (10, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.putText(temp_image, label, (10, int(50*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
mask = np.zeros((img.shape[0], img.shape[1]), np.uint8)
display_img = temp_image.copy()
while True:
display_img[mask > 0] = [255, 255, 255]
cv.imshow(window_name, display_img)
# Create a copy of the image to show instructions
key = cv.waitKey(30) & 0xFF
if key == ord('i'): # Increase brush size
brush_size += 1
print(f"Brush size increased to {brush_size}")
elif key == ord('d'): # Decrease brush size
brush_size = max(1, brush_size - 1)
print(f"Brush size decreased to {brush_size}")
elif key == ord('r'): # clear the mask
mask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8)
display_img = temp_image.copy()
print(f"Mask cleared")
elif key == ord(' '): # Press space bar to finish drawing
break
elif key == 27:
exit()
cv.destroyAllWindows()
return mask
def prepare_input(args, image):
if args.mask:
mask = cv.imread(args.mask, cv.IMREAD_GRAYSCALE)
if mask is None:
raise ValueError(f"Could not read mask file: {args.mask}")
if mask.shape[:2] != image.shape[:2]:
mask = cv.resize(mask, (image.shape[1], image.shape[0]), interpolation=cv.INTER_NEAREST)
else:
mask = create_mask(deepcopy(image))
batch = make_batch_blob(image, mask)
return batch
def main(args):
global imgWidth, fontSize, fontThickness
keyboard_shorcuts()
image = cv.imread(findFile(args.input))
imgWidth = min(image.shape[:2])
fontSize = min(1.5, (stdSize*imgWidth)/stdImgSize)
fontThickness = max(1,(stdWeight*imgWidth)//stdImgSize)
aspect_ratio = image.shape[0]/image.shape[1]
height = int(args.width*aspect_ratio)
batch = prepare_input(args, image)
model = DDIMInpainter(args)
result = model.inpaint(batch["masked_image"], batch["mask"], S=args.samples)
result = result.astype(np.uint8)
result = cv.resize(result, (args.width, height))
result = cv.cvtColor(result, cv.COLOR_RGB2BGR)
cv.imshow("Inpainted Image", result)
cv.waitKey(0)
cv.destroyAllWindows()
if __name__ == '__main__':
args = get_args_parser()
main(args)
+158
View File
@@ -0,0 +1,158 @@
'''
Mask R-CNN
This is an example of using Mask R-CNN for object detection and instance segmentation.
NOTE regarding OpenCV 5.0+:
The default model configuration (.pbtxt) used in this sample relies on retrieving
intermediate layers (e.g., 'detection_out_final'). OpenCV 5.0 introduces stricter
graph optimization which may prune intermediate layers not explicitly registered as outputs.
If you encounter an error such as "the number of requested and actual outputs must be the same",
please note that the provided .pbtxt may need to be updated to explicitly declare
'detection_out_final' as an output node.
'''
import cv2 as cv
import argparse
import numpy as np
parser = argparse.ArgumentParser(description=
'Use this script to run Mask-RCNN object detection and semantic '
'segmentation network from TensorFlow Object Detection API.')
parser.add_argument('--input', help='Path to input image or video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--model', required=True, help='Path to a .pb file with weights.')
parser.add_argument('--config', required=True, help='Path to a .pxtxt file contains network configuration.')
parser.add_argument('--classes', help='Optional path to a text file with names of classes.')
parser.add_argument('--colors', help='Optional path to a text file with colors for an every class. '
'An every color is represented with three values from 0 to 255 in BGR channels order.')
parser.add_argument('--width', type=int, default=800,
help='Preprocess input image by resizing to a specific width.')
parser.add_argument('--height', type=int, default=800,
help='Preprocess input image by resizing to a specific height.')
parser.add_argument('--thr', type=float, default=0.5, help='Confidence threshold')
args = parser.parse_args()
np.random.seed(324)
# Load names of classes
classes = None
if args.classes:
with open(args.classes, 'rt') as f:
classes = f.read().rstrip('\n').split('\n')
# Load colors
colors = None
if args.colors:
with open(args.colors, 'rt') as f:
colors = [np.array(color.split(' '), np.uint8) for color in f.read().rstrip('\n').split('\n')]
legend = None
def showLegend(classes):
global legend
if not classes is None and legend is None:
blockHeight = 30
assert(len(classes) == len(colors))
legend = np.zeros((blockHeight * len(colors), 200, 3), np.uint8)
for i in range(len(classes)):
block = legend[i * blockHeight:(i + 1) * blockHeight]
block[:,:] = colors[i]
cv.putText(block, classes[i], (0, blockHeight//2), cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255))
cv.namedWindow('Legend', cv.WINDOW_NORMAL)
cv.imshow('Legend', legend)
classes = None
def drawBox(frame, classId, conf, left, top, right, bottom):
# Draw a bounding box.
cv.rectangle(frame, (left, top), (right, bottom), (0, 255, 0))
label = '%.2f' % conf
# Print a label of class.
if classes:
assert(classId < len(classes))
label = '%s: %s' % (classes[classId], label)
labelSize, baseLine = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, 0.5, 1)
top = max(top, labelSize[1])
cv.rectangle(frame, (left, top - labelSize[1]), (left + labelSize[0], top + baseLine), (255, 255, 255), cv.FILLED)
cv.putText(frame, label, (left, top), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
# Load a network
net = cv.dnn.readNet(cv.samples.findFile(args.model), cv.samples.findFile(args.config))
net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
winName = 'Mask-RCNN in OpenCV'
cv.namedWindow(winName, cv.WINDOW_NORMAL)
cap = cv.VideoCapture(cv.samples.findFileOrKeep(args.input) if args.input else 0)
legend = None
while cv.waitKey(1) < 0:
hasFrame, frame = cap.read()
if not hasFrame:
cv.waitKey()
break
frameH = frame.shape[0]
frameW = frame.shape[1]
# Create a 4D blob from a frame.
blob = cv.dnn.blobFromImage(frame, size=(args.width, args.height), swapRB=True, crop=False)
# Run a model
net.setInput(blob)
# NOTE: In OpenCV 5.0, requesting 'detection_out_final' will fail if the .pbtxt
# does not register it as an output. See file header for details.
t0 = cv.getTickCount()
boxes, masks = net.forward(['detection_out_final', 'detection_masks'])
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
numClasses = masks.shape[1]
numDetections = boxes.shape[2]
# Draw segmentation
if not colors:
# Generate colors
colors = [np.array([0, 0, 0], np.uint8)]
for i in range(1, numClasses + 1):
colors.append((colors[i - 1] + np.random.randint(0, 256, [3], np.uint8)) / 2)
del colors[0]
boxesToDraw = []
for i in range(numDetections):
box = boxes[0, 0, i]
mask = masks[i]
score = box[2]
if score > args.thr:
classId = int(box[1])
left = int(frameW * box[3])
top = int(frameH * box[4])
right = int(frameW * box[5])
bottom = int(frameH * box[6])
left = max(0, min(left, frameW - 1))
top = max(0, min(top, frameH - 1))
right = max(0, min(right, frameW - 1))
bottom = max(0, min(bottom, frameH - 1))
boxesToDraw.append([frame, classId, score, left, top, right, bottom])
classMask = mask[classId]
classMask = cv.resize(classMask, (right - left + 1, bottom - top + 1))
mask = (classMask > 0.5)
roi = frame[top:bottom+1, left:right+1][mask]
frame[top:bottom+1, left:right+1][mask] = (0.7 * colors[classId] + 0.3 * roi).astype(np.uint8)
for box in boxesToDraw:
drawBox(*box)
# Put efficiency information.
label = 'Inference time: %.2f ms' % (t * 1000.0)
cv.putText(frame, label, (0, 15), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0))
showLegend(classes)
cv.imshow(winName, frame)
+133
View File
@@ -0,0 +1,133 @@
from __future__ import print_function
# Script to evaluate MobileNet-SSD object detection model trained in TensorFlow
# using both TensorFlow and OpenCV. Example:
#
# python mobilenet_ssd_accuracy.py \
# --weights=frozen_inference_graph.pb \
# --prototxt=ssd_mobilenet_v1_coco.pbtxt \
# --images=val2017 \
# --annotations=annotations/instances_val2017.json
#
# Tested on COCO 2017 object detection dataset, http://cocodataset.org/#download
import os
import cv2 as cv
import json
import argparse
parser = argparse.ArgumentParser(
description='Evaluate MobileNet-SSD model using both TensorFlow and OpenCV. '
'COCO evaluation framework is required: http://cocodataset.org')
parser.add_argument('--weights', required=True,
help='Path to frozen_inference_graph.pb of MobileNet-SSD model. '
'Download it from http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_11_06_2017.tar.gz')
parser.add_argument('--prototxt', help='Path to ssd_mobilenet_v1_coco.pbtxt from opencv_extra.', required=True)
parser.add_argument('--images', help='Path to COCO validation images directory.', required=True)
parser.add_argument('--annotations', help='Path to COCO annotations file.', required=True)
args = parser.parse_args()
### Get OpenCV predictions #####################################################
net = cv.dnn.readNetFromTensorflow(cv.samples.findFile(args.weights), cv.samples.findFile(args.prototxt))
net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
detections = []
for imgName in os.listdir(args.images):
inp = cv.imread(cv.samples.findFile(os.path.join(args.images, imgName)))
rows = inp.shape[0]
cols = inp.shape[1]
inp = cv.resize(inp, (300, 300))
net.setInput(cv.dnn.blobFromImage(inp, 1.0/127.5, (300, 300), (127.5, 127.5, 127.5), True))
out = net.forward()
for i in range(out.shape[2]):
score = float(out[0, 0, i, 2])
# Confidence threshold is in prototxt.
classId = int(out[0, 0, i, 1])
x = out[0, 0, i, 3] * cols
y = out[0, 0, i, 4] * rows
w = out[0, 0, i, 5] * cols - x
h = out[0, 0, i, 6] * rows - y
detections.append({
"image_id": int(imgName.rstrip('0')[:imgName.rfind('.')]),
"category_id": classId,
"bbox": [x, y, w, h],
"score": score
})
with open('cv_result.json', 'wt') as f:
json.dump(detections, f)
### Get TensorFlow predictions #################################################
import tensorflow as tf
with tf.gfile.FastGFile(args.weights) as f:
# Load the model
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
with tf.Session() as sess:
# Restore session
sess.graph.as_default()
tf.import_graph_def(graph_def, name='')
detections = []
for imgName in os.listdir(args.images):
inp = cv.imread(os.path.join(args.images, imgName))
rows = inp.shape[0]
cols = inp.shape[1]
inp = cv.resize(inp, (300, 300))
inp = inp[:, :, [2, 1, 0]] # BGR2RGB
out = sess.run([sess.graph.get_tensor_by_name('num_detections:0'),
sess.graph.get_tensor_by_name('detection_scores:0'),
sess.graph.get_tensor_by_name('detection_boxes:0'),
sess.graph.get_tensor_by_name('detection_classes:0')],
feed_dict={'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3)})
num_detections = int(out[0][0])
for i in range(num_detections):
classId = int(out[3][0][i])
score = float(out[1][0][i])
bbox = [float(v) for v in out[2][0][i]]
if score > 0.01:
x = bbox[1] * cols
y = bbox[0] * rows
w = bbox[3] * cols - x
h = bbox[2] * rows - y
detections.append({
"image_id": int(imgName.rstrip('0')[:imgName.rfind('.')]),
"category_id": classId,
"bbox": [x, y, w, h],
"score": score
})
with open('tf_result.json', 'wt') as f:
json.dump(detections, f)
### Evaluation part ############################################################
# %matplotlib inline
import matplotlib.pyplot as plt
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
import numpy as np
import skimage.io as io
import pylab
pylab.rcParams['figure.figsize'] = (10.0, 8.0)
annType = ['segm','bbox','keypoints']
annType = annType[1] #specify type here
prefix = 'person_keypoints' if annType=='keypoints' else 'instances'
print('Running demo for *%s* results.'%(annType))
#initialize COCO ground truth api
cocoGt=COCO(args.annotations)
#initialize COCO detections api
for resFile in ['tf_result.json', 'cv_result.json']:
print(resFile)
cocoDt=cocoGt.loadRes(resFile)
cocoEval = COCOeval(cocoGt,cocoDt,annType)
cocoEval.evaluate()
cocoEval.accumulate()
cocoEval.summarize()
+517
View File
@@ -0,0 +1,517 @@
%YAML 1.0
---
################################################################################
# Object detection models.
################################################################################
# YOLOv8 object detection family from ultralytics (https://github.com/ultralytics/ultralytics)
# Might be used for all YOLOv8n YOLOv8s YOLOv8m YOLOv8l and YOLOv8x
yolov8x:
load_info:
url: "https://huggingface.co/cabelo/yolov8/resolve/main/yolov8x.onnx?download=true"
sha1: "462f15d668c046d38e27d3df01fe8142dd004cb4"
model: "yolov8x.onnx"
mean: 0.0
scale: 0.00392
width: 640
height: 640
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov8"
sample: "object_detection"
yolov8s:
load_info:
url: "https://github.com/CVHub520/X-AnyLabeling/releases/download/v0.1.0/yolov8s.onnx"
sha1: "82cd83984396fe929909ecb58212b0e86d0904b1"
model: "yolov8s.onnx"
mean: 0.0
scale: 0.00392
width: 640
height: 640
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov8"
sample: "object_detection"
yolov8:
load_info:
url: "https://github.com/CVHub520/X-AnyLabeling/releases/download/v0.1.0/yolov8n.onnx"
sha1: "68f864475d06e2ec4037181052739f268eeac38d"
model: "yolov8n.onnx"
mean: 0.0
scale: 0.00392
width: 640
height: 640
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov8"
sample: "object_detection"
yolov8m:
load_info:
url: "https://github.com/CVHub520/X-AnyLabeling/releases/download/v0.1.0/yolov8m.onnx"
sha1: "656ffeb4f3b067bc30df956728b5f9c61a4cb090"
model: "yolov8m.onnx"
mean: 0.0
scale: 0.00392
width: 640
height: 640
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov8"
sample: "object_detection"
yolov8l:
load_info:
url: "https://github.com/CVHub520/X-AnyLabeling/releases/download/v0.1.0/yolov8l.onnx"
sha1: "462df53ca3a85d110bf6be7fc2e2bb1277124395"
model: "yolov8l.onnx"
mean: 0.0
scale: 0.00392
width: 640
height: 640
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov8"
sample: "object_detection"
# YOLOv5 object detection family from ultralytics (https://github.com/ultralytics/ultralytics)
# Might be used for all YOLOv5n YOLOv5s YOLOv5m YOLOv5l and YOLOv5x
yolov5l:
load_info:
url: "https://github.com/CVHub520/X-AnyLabeling/releases/download/v0.1.0/yolov5l.onnx"
sha1: "9de7e54c524b7fe7577bbd4cdbbdaed53375c8f1"
model: "yolov5l.onnx"
mean: 0.0
scale: 0.00392
width: 640
height: 640
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov5"
sample: "object_detection"
yolov4:
load_info:
url: "https://huggingface.co/opencv/opencv_contribution/resolve/main/yolov4/yolov4.onnx"
sha1: "68df7133bef095d79531ad62d79295d82614de3b"
model: "yolov4.onnx"
mean: [0, 0, 0]
scale: 0.00392
width: 608
height: 608
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov4"
sample: "object_detection"
yolov4-tiny:
load_info:
url: "https://huggingface.co/opencv/opencv_contribution/resolve/main/yolov4/yolov4-tiny.onnx"
sha1: "158a74e9c6da57f5e4161c5dfc1ab592f47d958a"
model: "yolov4-tiny.onnx"
mean: [0, 0, 0]
scale: 0.00392
width: 416
height: 416
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov4"
sample: "object_detection"
yolov3:
load_info:
url: "https://huggingface.co/qualcomm/Yolo-v3/resolve/226ada6de9dcb32eebad7f74bf526714e2af6136/Yolo-v3.onnx"
sha1: "c37641ddf05cfe133efd4b66832f269d95f523cf"
model: "yolov3.onnx"
mean: [0, 0, 0]
scale: 0.00392
width: 640
height: 640
rgb: true
labels: "object_detection_classes_yolo.txt"
postprocessing: "yolov4"
sample: "object_detection"
# Caffe implementation of SSD model from https://github.com/PINTO0309/MobileNet-SSD-RealSense
ssd_caffe:
load_info:
url: "https://github.com/PINTO0309/MobileNet-SSD-RealSense/raw/refs/heads/master/caffemodel/MobileNetSSD/MobileNetSSD_deploy.caffemodel"
sha1: "994d30a8afaa9e754d17d2373b2d62a7dfbaaf7a"
model: "MobileNetSSD_deploy.caffemodel"
config_load_info:
url: "https://github.com/PINTO0309/MobileNet-SSD-RealSense/raw/refs/heads/master/caffemodel/MobileNetSSD/MobileNetSSD_deploy.prototxt"
sha1: "25c8404cecdef638c2bd9ac7f3b46a8b96897deb"
config: "MobileNetSSD_deploy.prototxt"
mean: [127.5, 127.5, 127.5]
scale: 0.007843
width: 300
height: 300
rgb: false
labels: "object_detection_classes_pascal_voc.txt"
postprocessing: "ssd"
sample: "object_detection"
# TensorFlow implementation of SSD model from https://github.com/tensorflow/models/tree/master/research/object_detection
ssd_tf:
load_info:
url: "http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v1_coco_2017_11_17.tar.gz"
sha1: "9e4bcdd98f4c6572747679e4ce570de4f03a70e2"
download_sha: "6157ddb6da55db2da89dd561eceb7f944928e317"
download_name: "ssd_mobilenet_v1_coco_2017_11_17.tar.gz"
member: "ssd_mobilenet_v1_coco_2017_11_17/frozen_inference_graph.pb"
model: "ssd_mobilenet_v1_coco_2017_11_17.pb"
config_load_info:
url: "https://github.com/opencv/opencv_extra/raw/refs/heads/4.x/testdata/dnn/ssd_mobilenet_v1_coco_2017_11_17.pbtxt"
sha1: "c7cf985ce0a4a8953daaa4b8cacdd3c8e31437a6"
config: "ssd_mobilenet_v1_coco_2017_11_17.pbtxt"
mean: [0, 0, 0]
scale: 1.0
width: 300
height: 300
rgb: true
labels: "object_detection_classes_coco.txt"
postprocessing: "ssd"
sample: "object_detection"
# TensorFlow implementation of Faster-RCNN model from https://github.com/tensorflow/models/tree/master/research/object_detection
faster_rcnn_tf:
load_info:
url: "http://download.tensorflow.org/models/object_detection/faster_rcnn_inception_v2_coco_2018_01_28.tar.gz"
sha1: "f2e4bf386b9bb3e25ddfcbbd382c20f417e444f3"
download_sha: "c710f25e5c6a3ce85fe793d5bf266d581ab1c230"
download_name: "faster_rcnn_inception_v2_coco_2018_01_28.tar.gz"
member: "faster_rcnn_inception_v2_coco_2018_01_28/frozen_inference_graph.pb"
model: "faster_rcnn_inception_v2_coco_2018_01_28.pb"
config_load_info:
url: "https://github.com/opencv/opencv_extra/raw/refs/heads/4.x/testdata/dnn/faster_rcnn_inception_v2_coco_2018_01_28.pbtxt"
sha1: "059ee437fb4d6f82a6f1d2b3c7a8dd54c107687f"
config: "faster_rcnn_inception_v2_coco_2018_01_28.pbtxt"
mean: [0, 0, 0]
scale: 1.0
width: 800
height: 600
rgb: true
postprocessing: "ssd"
sample: "object_detection"
################################################################################
# Image classification models.
################################################################################
squeezenet:
load_info:
url: "https://github.com/onnx/models/raw/main/validated/vision/classification/squeezenet/model/squeezenet1.1-7.onnx?download="
sha1: "ec31942d17715941bb9b81f3a91dc59def9236be"
model: "squeezenet1.1-7.onnx"
mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225]
scale: 0.003921
width: 224
height: 224
rgb: true
labels: "classification_classes_ILSVRC2012.txt"
sample: "classification"
googlenet:
load_info:
url: "https://github.com/onnx/models/raw/69c5d3751dda5349fd3fc53f525395d180420c07/vision/classification/inception_and_googlenet/googlenet/model/googlenet-8.onnx"
sha1: "da39a3ee5e6b4b0d3255bfef95601890afd80709"
model: "googlenet-8.onnx"
mean: [103.939, 116.779, 123.675]
std: [1, 1, 1]
scale: 1.0
width: 224
height: 224
rgb: false
labels: "classification_classes_ILSVRC2012.txt"
sample: "classification"
resnet:
load_info:
url: "https://github.com/onnx/models/raw/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx"
sha1: "c3a67b3cb2f0a61a7eb75eb8bd9139c89557cbe0"
model: "resnet50-v2-7.onnx"
mean: [123.675, 116.28, 103.53]
std: [58.395, 57.12, 57.375]
scale: 1.0
width: 224
height: 224
rgb: true
labels: "classification_classes_ILSVRC2012.txt"
sample: "classification"
################################################################################
# Semantic segmentation models.
################################################################################
fcnresnet50:
load_info:
url: "https://github.com/onnx/models/raw/491ce05590abb7551d7fae43c067c060eeb575a6/validated/vision/object_detection_segmentation/fcn/model/fcn-resnet50-12.onnx"
sha1: "1bb0c7e0034038969aecc6251166f1612a139230"
model: "fcn-resnet50-12.onnx"
mean: [103.5, 116.2, 123.6]
scale: 0.019
width: 500
height: 500
rgb: false
sample: "segmentation"
fcnresnet101:
load_info:
url: "https://github.com/onnx/models/raw/fb8271d5d5d9b90dbb1eb5e8e40f8f580fb248b3/vision/object_detection_segmentation/fcn/model/fcn-resnet101-11.onnx"
sha1: "e7e76474bf6b73334ab32c4be1374c9e605f5aed"
model: "fcn-resnet101-11.onnx"
mean: [103.5, 116.2, 123.6]
scale: 0.019
width: 500
height: 500
rgb: false
sample: "segmentation"
u2netp:
load_info:
url: "https://github.com/danielgatis/rembg/releases/download/v0.0.0/u2netp.onnx"
sha1: "0a99236f0d5c1916a99a8c401b23e5ef32038606"
model: "u2netp.onnx"
mean: [123.6, 116.2, 103.5]
scale: 0.019
width: 320
height: 320
rgb: true
sample: "segmentation"
################################################################################
# Text detection models.
################################################################################
DB:
load_info:
url: "https://drive.google.com/uc?export=dowload&id=17_ABp79PlFt9yPCxSaarVc_DKTmrSGGf"
sha1: "bef233c28947ef6ec8c663d20a2b326302421fa3"
model: "DB_IC15_resnet50.onnx"
ocr_load_info:
ocr_url: "https://drive.google.com/uc?export=dowload&id=159VavnbvfBQkLIPSAu2SP5Yij1Fy4azw"
ocr_sha1: "c4ab1fb3f13c1c8ffc04f016e72ec85311de4ebe"
ocr_model: "VGG_CTC.onnx"
mean: [122.67891434, 116.66876762, 104.00698793]
scale: 0.00392
width: 736
height: 736
rgb: false
sample: "text_detection"
East:
load_info:
url: "https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1"
sha1: "fffabf5ac36f37bddf68e34e84b45f5c4247ed06"
download_name: "frozen_east_text_detection.tar.gz"
download_sha: "3ca8233d6edd748f7ed23246c8ca24cbf696bb94"
model: "frozen_east_text_detection.pb"
ocr_load_info:
ocr_url: "https://drive.google.com/uc?export=dowload&id=159VavnbvfBQkLIPSAu2SP5Yij1Fy4azw"
ocr_sha1: "c4ab1fb3f13c1c8ffc04f016e72ec85311de4ebe"
ocr_model: "VGG_CTC.onnx"
mean: [123.68, 116.78, 103.94]
scale: 1.0
width: 736
height: 736
rgb: false
sample: "text_detection"
OCR:
load_info:
url: "https://drive.google.com/uc?export=dowload&id=159VavnbvfBQkLIPSAu2SP5Yij1Fy4azw"
sha1: "c4ab1fb3f13c1c8ffc04f016e72ec85311de4ebe"
model: "VGG_CTC.onnx"
sample: "text_recognition"
# Edge Detection models.
################################################################################
dexined:
load_info:
url: "https://github.com/opencv/opencv_zoo/raw/refs/heads/main/models/edge_detection_dexined/edge_detection_dexined_2024sep.onnx?download="
sha1: "f86f2d32c3cf892771f76b5e6b629b16a66510e9"
model: "edge_detection_dexined_2024sep.onnx"
mean: [103.5, 116.2, 123.6]
scale: 1.0
width: 512
height: 512
rgb: false
sample: "edge_detection"
################################################################################
# Edge Detection models.
################################################################################
reid:
load_info:
url: "https://github.com/opencv/opencv_zoo/raw/main/models/person_reid_youtureid/person_reid_youtu_2021nov.onnx?download="
sha1: "d4316b100db40f8840aa82626e1cf3f519a7f1ae"
model: "person_reid_youtu_2021nov.onnx"
yolo_load_info:
yolo_url: "https://github.com/CVHub520/X-AnyLabeling/releases/download/v0.1.0/yolov8n.onnx"
yolo_sha1: "68f864475d06e2ec4037181052739f268eeac38d"
yolo_model: "yolov8n.onnx"
mean: [0.485, 0.456, 0.406]
std: [0.229, 0.224, 0.225]
scale: 0.00392
yolo_scale: 0.00392
yolo_width: 640
yolo_height: 640
width: 128
height: 256
rgb: false
yolo_rgb: true
sample: "person_reid"
################################################################################
# Tracker models.
################################################################################
vit:
load_info:
url: "https://github.com/opencv/opencv_zoo/raw/fef72f8fa7c52eaf116d3df358d24e6e959ada0e/models/object_tracking_vittrack/object_tracking_vittrack_2023sep.onnx"
sha1: "50008bb4f6a27b1aa940ad886b1bd1936ac4ed3e"
model: "object_tracking_vittrack_2023sep.onnx"
sample: "object_tracker"
nanotrack:
nanotrack_head_load_info:
nanotrack_head_url: "https://github.com/HonglinChu/SiamTrackers/raw/refs/heads/master/NanoTrack/models/nanotrackv2/nanotrack_head_sim.onnx"
nanotrack_head_sha1: "39f168489671700cf739e402dfc67d41ce648aef"
nanotrack_head_model: "nanotrack_head_sim.onnx"
nanotrack_back_load_info:
nanotrack_back_url: "https://github.com/HonglinChu/SiamTrackers/raw/refs/heads/master/NanoTrack/models/nanotrackv2/nanotrack_backbone_sim.onnx"
nanotrack_back_sha1: "6e773a364457b78574f9f63a23b0659ee8646f8f"
nanotrack_back_model: "nanotrack_backbone_sim.onnx"
sample: "object_tracker"
dasiamrpn:
dasiamrpn_load_info:
dasiamrpn_url: "https://github.com/opencv/opencv_zoo/raw/fef72f8fa7c52eaf116d3df358d24e6e959ada0e/models/object_tracking_dasiamrpn/object_tracking_dasiamrpn_model_2021nov.onnx?download="
dasiamrpn_sha1: "91b774fce7df4c0e4918469f0f482d9a27d0e2d4"
dasiamrpn_model: "object_tracking_dasiamrpn_model_2021nov.onnx"
dasiamrpn_kernel_r1_load_info:
dasiamrpn_kernel_r1_url: "https://github.com/opencv/opencv_zoo/raw/fef72f8fa7c52eaf116d3df358d24e6e959ada0e/models/object_tracking_dasiamrpn/object_tracking_dasiamrpn_kernel_r1_2021nov.onnx?download="
dasiamrpn_kernel_r1_sha1: "bb64620a54348657133eb28be2d3a2a8c76b84b3"
dasiamrpn_kernel_r1_model: "object_tracking_dasiamrpn_kernel_cls1_2021nov.onnx"
dasiamrpn_kernel_cls_load_info:
dasiamrpn_kernel_cls_url: "https://github.com/opencv/opencv_zoo/raw/fef72f8fa7c52eaf116d3df358d24e6e959ada0e/models/object_tracking_dasiamrpn/object_tracking_dasiamrpn_kernel_cls1_2021nov.onnx?download="
dasiamrpn_kernel_cls_sha1: "e9ccd270ce8059bdf7ed0d1845c03ef4a951ee0f"
dasiamrpn_kernel_cls_model: "object_tracking_dasiamrpn_kernel_cls1_2021nov.onnx"
sample: "object_tracker"
################################################################################
# Inpainting models.
################################################################################
lama:
load_info:
url: "https://github.com/gursimarsingh/opencv_zoo/raw/0417e12d24bba41613ae0380bd698cca73a4fb17/models/inpainting_lama/inpainting_lama_2025jan.onnx?download="
sha1: "7c6cdb9362bf73de2a80cfcaf17e121e3302f24c"
model: "inpainting_lama_2025jan.onnx"
mean: [0, 0, 0]
scale: 0.00392
width: 512
height: 512
rgb: false
sample: "inpainting"
ldm_inpainting:
encoder_load_info:
encoder_url: "https://dl.opencv.org/models/ldm_inpainting/InpaintEncoder.onnx"
encoder_sha1: "eb663262304473d81d6ae627d7117892dac56b5e"
encoder_model: "InpaintEncoder.onnx"
decoder_load_info:
decoder_url: "https://dl.opencv.org/models/ldm_inpainting/InpaintDecoder.onnx"
decoder_sha1: "af258c100e3a3b0970493b6375c8775beaffc9d1"
decoder_model: "InpaintDecoder.onnx"
diffusor_load_info:
diffusor_url: "https://dl.opencv.org/models/ldm_inpainting/LatentDiffusion.onnx"
diffusor_sha1: "2c6f8a505d9a93195510c854d8f023fab27ce70e"
diffusor_model: "LatentDiffusion.onnx"
mean: [0, 0, 0]
scale: 0.00392
width: 512
height: 512
rgb: true
sample: "ldm_inpainting"
################################################################################
# Macbeth chart detection model.
################################################################################
mcc:
load_info:
url: "https://github.com/gursimarsingh/opencv_zoo/raw/refs/heads/mcc_model/models/macbeth_chart_detector/frozen_inference_graph.pb?download="
sha1: "fae7dbef14c4ae1fca76f3662220fbd460ed5ed6"
model: "frozen_inference_graph.pb"
config_load_info:
url: "https://github.com/gursimarsingh/opencv_zoo/raw/refs/heads/mcc_model/models/macbeth_chart_detector/graph.pbtxt?download="
sha1: "8350cb8f078ecefa1cd566e89930ede25a192310"
config: "graph.pbtxt"
sample: "mcc"
################################################################################
# Deblurring model.
################################################################################
NAFNet:
load_info:
url: "https://drive.google.com/uc?export=dowload&id=1ZLRhkpCekNruJZggVpBgSoCx3k7bJ-5v"
sha1: "7dabf3d4ede0770ef326afc4511f7e67a791286d"
model: "deblurring_nafnet_2025may.onnx"
mean: [0, 0, 0]
scale: 0.00392
rgb: true
sample: "deblurring"
################################################################################
# Alpha Matting model.
################################################################################
modnet:
load_info:
url: "https://github.com/HarxSan/Modnet/raw/main/modnet.onnx"
sha1: "40eebf4387ea86c982bf6e363a7f84b659b145a0"
model: "modnet.onnx"
mean: [0.0, 0.0, 0.0]
scale: 0.00784313725
width: 512
height: 512
rgb: true
sample: "alpha_matting"
################################################################################
# Super-resolution models.
################################################################################
seemoredetails:
load_info:
url: "https://github.com/Naresh-19/opencv-superres-models/raw/main/seemore_x4v2_static512.onnx"
sha1: "584467bd36f5715aa12c3203bba16e4de6392034"
model: "seemore_x4v2_static512.onnx"
mean: [0.0, 0.0, 0.0]
scale: 0.00392
rgb: true
width: 512
height: 512
sample: "super_resolution"
input: true
################################################################################
# Auto white balance models.
################################################################################
fc4:
load_info:
url: "https://raw.githubusercontent.com/MykhailoTrushch/fc4-models/main/fc4_fold_0.onnx"
sha1: "e8a9a65ec0baaae3e4c97b34274a620eb362e905"
model: "fc4_fold_0.onnx"
sample: "auto_white_balance"
scale: 0.00392156862
rgb: true
mean: 0
+735
View File
@@ -0,0 +1,735 @@
//![includes]
#include <fstream>
#include <sstream>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>
#include <mutex>
#include <thread>
#include <queue>
#include "iostream"
#include "common.hpp"
//![includes]
using namespace cv;
using namespace dnn;
using namespace std;
const string about =
"Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
"To run:\n"
"\t ./example_dnn_object_detection model_name --input=path/to/your/input/image/or/video (don't give --input flag if want to use device camera)\n"
"Sample command:\n"
"\t ./example_dnn_object_detection yolov8 --input=$OPENCV_SAMPLES_DATA_PATH/baboon.jpg\n"
"Model path can also be specified using --model argument. ";
const string param_keys =
"{ help h | | Print help message. }"
"{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ device | 0 | camera device number. }"
"{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera. }"
"{ thr | .5 | Confidence threshold. }"
"{ nms | .4 | Non-maximum suppression threshold. }"
"{ async | 0 | Number of asynchronous forwards at the same time. "
"Choose 0 for synchronous mode }"
"{ padvalue | 114.0 | padding value. }"
"{ paddingmode | 2 | Choose one of padding modes: "
"0: resize to required input size without extra processing, "
"1: Image will be cropped after resize, "
"2: Resize image to the desired size while preserving the aspect ratio of original image }";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
float confThreshold, nmsThreshold, scale, paddingValue;
vector<string> labels;
Scalar meanv;
bool swapRB;
int inpWidth, inpHeight;
size_t asyncNumReq = 0;
ImagePaddingMode paddingMode;
string modelName, framework;
static void preprocess(const Mat& frame, Net& net, Size inpSize);
static void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, const string postprocessing);
static void drawPred(vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, Mat& frame, FontFace& sans, int stdSize, int stdWeight, int stdImgSize, int stdThickness);
static void callback(int pos, void* userdata);
static Scalar getColor(int classId);
static void yoloPostProcessing(
const vector<Mat>& outs,
vector<int>& keep_classIds,
vector<float>& keep_confidences,
vector<Rect2d>& keep_boxes,
float conf_threshold,
float iou_threshold,
const string& postprocessing);
static void printAliases(string& zooFile){
vector<string> aliases = findAliases(zooFile, "object_detection");
cout<<"Alias choices: [ ";
for (auto it: aliases){
cout<<"'"<<it<<"' ";
}
cout<<"]"<<endl;
}
static Scalar getTextColor(Scalar bgColor) {
double luminance = 0.299 * bgColor[2] + 0.587 * bgColor[1] + 0.114 * bgColor[0];
return luminance > 128 ? Scalar(0, 0, 0) : Scalar(255, 255, 255);
}
template <typename T>
class QueueFPS : public std::queue<T>
{
public:
QueueFPS() : counter(0) {}
void push(const T& entry)
{
std::lock_guard<std::mutex> lock(mutex);
std::queue<T>::push(entry);
counter += 1;
if (counter == 1)
{
// Start counting from a second frame (warmup).
tm.reset();
tm.start();
}
}
T get()
{
std::lock_guard<std::mutex> lock(mutex);
T entry = this->front();
this->pop();
return entry;
}
float getFPS()
{
tm.stop();
double fps = counter / tm.getTimeSec();
tm.start();
return static_cast<float>(fps);
}
void clear()
{
std::lock_guard<std::mutex> lock(mutex);
while (!this->empty())
this->pop();
}
unsigned int counter;
private:
TickMeter tm;
std::mutex mutex;
};
int main(int argc, char** argv)
{
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_INFO);
CommandLineParser parser(argc, argv, keys);
string zooFile = parser.get<String>("zoo");
if (!parser.has("@alias") || parser.has("help"))
{
cout << about << endl;
parser.printMessage();
printAliases(zooFile);
return -1;
}
zooFile = findFile(zooFile);
modelName = parser.get<String>("@alias");
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
if (!parser.has("model"))
{
cout << "Path to model is not provided in command line or model alias is not correct" << endl;
printAliases(zooFile);
return -1;
}
confThreshold = parser.get<float>("thr");
nmsThreshold = parser.get<float>("nms");
//![preprocess_params]
scale = parser.get<float>("scale");
meanv = parser.get<Scalar>("mean");
swapRB = parser.get<bool>("rgb");
inpWidth = parser.get<int>("width");
inpHeight = parser.get<int>("height");
int async = parser.get<int>("async");
paddingValue = parser.get<float>("padvalue");
const string postprocessing = parser.get<String>("postprocessing");
paddingMode = static_cast<ImagePaddingMode>(parser.get<int>("paddingmode"));
//![preprocess_params]
String sha1 = parser.get<String>("sha1");
String config_sha1 = parser.get<String>("config_sha1");
const string modelPath = findModel(parser.get<String>("model"), sha1);
const string configPath = findModel(parser.get<String>("config"), config_sha1);
framework = modelPath.substr(modelPath.rfind('.') + 1);
if (parser.has("labels"))
{
const string file = findFile(parser.get<String>("labels"));
ifstream ifs(file.c_str());
if (!ifs.is_open())
CV_Error(Error::StsError, "File " + file + " not found");
string line;
while (getline(ifs, line))
{
labels.push_back(line);
}
}
//![read_net]
EngineType engine = ENGINE_AUTO;
if ((parser.get<String>("backend") != "default") || (parser.get<String>("target") != "cpu")){
engine = ENGINE_CLASSIC;
}
Net net = readNet(modelPath, configPath, "", engine);
int backend = getBackendID(parser.get<String>("backend"));
net.setPreferableBackend(backend);
net.setPreferableTarget(getTargetID(parser.get<String>("target")));
net.setProfilingMode(DNN_PROFILE_SUMMARY);
//![read_net]
// Create a window
static const string kWinName = "Deep learning object detection in OpenCV";
namedWindow(kWinName, WINDOW_AUTOSIZE);
int initialConf = (int)(confThreshold * 100);
createTrackbar("Confidence threshold, %", kWinName, &initialConf, 99, callback, &net);
// Open a video file or an image file or a camera stream.
VideoCapture cap;
bool openSuccess = parser.has("input") ? cap.open(findFile(parser.get<String>("input"))) : cap.open(parser.get<int>("device"));
if (!openSuccess){
cout << "Could not open input file or camera device" << endl;
return 0;
}
FontFace sans("sans");
int stdSize = 15;
int stdWeight = 150;
int stdImgSize = 512;
int stdThickness = 2;
vector<int> classIds;
vector<float> confidences;
vector<Rect> boxes;
if (async > 0 && backend == DNN_BACKEND_INFERENCE_ENGINE){
asyncNumReq = async;
}
if (async != 0) {
// Threading is enabled
bool process = true;
// Frames capturing thread
QueueFPS<Mat> framesQueue;
std::thread framesThread([&]() {
Mat frame;
while (process) {
cap >> frame;
if (!frame.empty())
framesQueue.push(frame.clone());
else
break;
}
});
// Frames processing thread
QueueFPS<Mat> processedFramesQueue;
QueueFPS<std::vector<Mat>> predictionsQueue;
std::thread processingThread([&]() {
std::queue<AsyncArray> futureOutputs;
Mat blob;
while (process) {
// Get the next frame
Mat frame;
{
if (!framesQueue.empty()) {
frame = framesQueue.get();
if (asyncNumReq) {
if (futureOutputs.size() == asyncNumReq)
frame = Mat();
}
}
}
// Process the frame
if (!frame.empty()) {
preprocess(frame, net, Size(inpWidth, inpHeight));
processedFramesQueue.push(frame);
if (asyncNumReq) {
futureOutputs.push(net.forwardAsync());
} else {
//![forward]
vector<Mat> outs;
net.forward(outs, net.getUnconnectedOutLayersNames());
net.printPerfProfile();
predictionsQueue.push(outs);
//![forward]
}
}
while (!futureOutputs.empty() &&
futureOutputs.front().wait_for(std::chrono::seconds(0))) {
AsyncArray async_out = futureOutputs.front();
futureOutputs.pop();
Mat out;
async_out.get(out);
predictionsQueue.push({out});
}
}
});
// Postprocessing and rendering loop
while (waitKey(100) < 0) {
if (predictionsQueue.empty())
continue;
vector<Mat> outs = predictionsQueue.get();
Mat frame = processedFramesQueue.get();
classIds.clear();
confidences.clear();
boxes.clear();
postprocess(frame, outs, net, classIds, confidences, boxes, postprocessing);
drawPred(classIds, confidences, boxes, frame, sans, stdSize, stdWeight, stdImgSize, stdThickness);
int imgWidth = max(frame.rows, frame.cols);
int size = static_cast<int>((stdSize * imgWidth) / (stdImgSize * 1.5));
int weight = static_cast<int>((stdWeight * imgWidth) / (stdImgSize * 1.5));
if (predictionsQueue.counter > 1) {
string label = format("Camera: %.2f FPS", framesQueue.getFPS());
rectangle(frame, Point(0, 0), Point(10 * size, 3 * size + size / 4), Scalar::all(255), FILLED);
putText(frame, label, Point(0, size), Scalar::all(0), sans, size, weight);
label = format("Network: %.2f FPS", predictionsQueue.getFPS());
putText(frame, label, Point(0, 2 * size), Scalar::all(0), sans, size, weight);
label = format("Skipped frames: %d", framesQueue.counter - predictionsQueue.counter);
putText(frame, label, Point(0, 3 * size), Scalar::all(0), sans, size, weight);
}
imshow(kWinName, frame);
}
process = false;
framesThread.join();
processingThread.join();
} else {
if (asyncNumReq)
CV_Error(Error::StsNotImplemented, "Asynchronous forward is supported only with Inference Engine backend.");
// Threading is disabled, run synchronously
Mat frame, blob;
while (waitKey(1) < 0) {
cap >> frame;
if (frame.empty()) {
waitKey();
break;
}
preprocess(frame, net, Size(inpWidth, inpHeight));
TickMeter tickMeter;
vector<Mat> outs;
tickMeter.start();
net.forward(outs, net.getUnconnectedOutLayersNames());
tickMeter.stop();
net.printPerfProfile();
classIds.clear();
confidences.clear();
boxes.clear();
postprocess(frame, outs, net, classIds, confidences, boxes, postprocessing);
drawPred(classIds, confidences, boxes, frame, sans, stdSize, stdWeight, stdImgSize, stdThickness);
int imgWidth = max(frame.rows, frame.cols);
int size = static_cast<int>((stdSize * imgWidth) / (stdImgSize * 1.5));
int weight = static_cast<int>((stdWeight * imgWidth) / (stdImgSize * 1.5));
string label = format("FPS: %.2f", 1000.0 / tickMeter.getTimeMilli());
putText(frame, label, Point(0, size), Scalar(0, 255, 0), sans, size, weight);
imshow(kWinName, frame);
}
}
return 0;
}
void preprocess(const Mat& frame, Net& net, Size inpSize)
{
Size size(inpSize.width <= 0 ? frame.cols : inpSize.width, inpSize.height <= 0 ? frame.rows : inpSize.height);
// Prepare the blob from the image
Mat inp;
{
//![preprocess_call]
Image2BlobParams imgParams(
Scalar::all(scale),
size,
meanv,
swapRB,
CV_32F,
DNN_LAYOUT_NCHW,
paddingMode,
paddingValue);
inp = blobFromImageWithParams(frame, imgParams);
//![preprocess_call]
}
// Set the blob as the network input
net.setInput(inp);
}
void yoloPostProcessing(
const vector<Mat>& outs,
vector<int>& keep_classIds,
vector<float>& keep_confidences,
vector<Rect2d>& keep_boxes,
float conf_threshold,
float iou_threshold,
const string& postprocessing)
{
// Retrieve
vector<int> classIds;
vector<float> confidences;
vector<Rect2d> boxes;
vector<Mat> outs_copy = outs;
if (postprocessing == "yolov8")
{
transposeND(outs_copy[0], {0, 2, 1}, outs_copy[0]);
}
if (postprocessing == "yolonas")
{
// outs contains 2 elements of shape [1, 8400, 80] and [1, 8400, 4]. Concat them to get [1, 8400, 84]
Mat concat_out;
// squeeze the first dimension
outs_copy[0] = outs_copy[0].reshape(1, outs_copy[0].size[1]);
outs_copy[1] = outs_copy[1].reshape(1, outs_copy[1].size[1]);
hconcat(outs_copy[1], outs_copy[0], concat_out);
outs_copy[0] = concat_out;
// remove the second element
outs_copy.pop_back();
// unsqueeze the first dimension
outs_copy[0] = outs_copy[0].reshape(0, vector<int>{1, 8400, 84});
}
for (auto preds : outs_copy)
{
preds = preds.reshape(1, preds.size[1]); // [1, 8400, 85] -> [8400, 85]
for (int i = 0; i < preds.rows; ++i)
{
// filter out non-object
float obj_conf = (postprocessing == "yolov8" || postprocessing == "yolonas") ? 1.0f : preds.at<float>(i, 4);
if (obj_conf < conf_threshold)
continue;
Mat scores = preds.row(i).colRange((postprocessing == "yolov8" || postprocessing == "yolonas") ? 4 : 5, preds.cols);
double conf;
Point maxLoc;
minMaxLoc(scores, 0, &conf, 0, &maxLoc);
conf = (postprocessing == "yolov8" || postprocessing == "yolonas") ? conf : conf * obj_conf;
if (conf < conf_threshold)
continue;
// get bbox coords
float* det = preds.ptr<float>(i);
double cx = det[0];
double cy = det[1];
double w = det[2];
double h = det[3];
// [x1, y1, x2, y2]
if (postprocessing == "yolonas") {
boxes.push_back(Rect2d(cx, cy, w, h));
} else {
boxes.push_back(Rect2d(cx - 0.5 * w, cy - 0.5 * h,
cx + 0.5 * w, cy + 0.5 * h));
}
classIds.push_back(maxLoc.x);
confidences.push_back(static_cast<float>(conf));
}
}
// NMS
vector<int> keep_idx;
NMSBoxes(boxes, confidences, conf_threshold, iou_threshold, keep_idx);
for (auto i : keep_idx)
{
keep_classIds.push_back(classIds[i]);
keep_confidences.push_back(confidences[i]);
keep_boxes.push_back(boxes[i]);
}
}
void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, const string postprocessing)
{
static vector<int> outLayers = net.getUnconnectedOutLayers();
if (postprocessing == "ssd")
{
// Network produces output blob with a shape 1x1xNx7 where N is a number of
// detections and an every detection is a vector of values
// [batchId, classId, confidence, left, top, right, bottom]
CV_Assert(outs.size() > 0);
for (size_t k = 0; k < outs.size(); k++)
{
float* data = (float*)outs[k].data;
for (size_t i = 0; i < outs[k].total(); i += 7)
{
float confidence = data[i + 2];
if (confidence > confThreshold)
{
int left = (int)data[i + 3];
int top = (int)data[i + 4];
int right = (int)data[i + 5];
int bottom = (int)data[i + 6];
int width = right - left + 1;
int height = bottom - top + 1;
if (width <= 2 || height <= 2)
{
left = (int)(data[i + 3] * frame.cols);
top = (int)(data[i + 4] * frame.rows);
right = (int)(data[i + 5] * frame.cols);
bottom = (int)(data[i + 6] * frame.rows);
width = right - left + 1;
height = bottom - top + 1;
}
classIds.push_back((int)(data[i + 1]) - 1); // Skip 0th background class id.
boxes.push_back(Rect(left, top, width, height));
confidences.push_back(confidence);
}
}
}
}
else if (postprocessing == "yolov4")
{
// boxes[b,N,1,4]+confs[b,N,classes] (normalized) or boxes[b,N,4]+scores[b,N]+classIdx[b,N] (model-px)
bool isBoxConfsFormat = (outs.size() == 2 && outs[0].dims == 4 && outs[0].size[outs[0].dims - 1] == 4);
bool isBoxScoresIdxFormat = (outs.size() == 3 && outs[0].dims == 3 && outs[0].size[2] == 4);
if (isBoxScoresIdxFormat)
{
int N = outs[0].size[1];
const float* boxesPtr = outs[0].ptr<float>(0);
const float* scoresPtr = outs[1].ptr<float>(0);
const float* classIdxPtr = outs[2].ptr<float>(0);
for (int j = 0; j < N; ++j)
{
float score = scoresPtr[j];
if (score > confThreshold)
{
float x1 = boxesPtr[j * 4 + 0];
float y1 = boxesPtr[j * 4 + 1];
float x2 = boxesPtr[j * 4 + 2];
float y2 = boxesPtr[j * 4 + 3];
boxes.push_back(Rect((int)x1, (int)y1, (int)(x2 - x1), (int)(y2 - y1)));
confidences.push_back(score);
classIds.push_back((int)classIdxPtr[j]);
}
}
}
else if (isBoxConfsFormat)
{
Mat boxesMat = outs[0];
Mat confsMat = outs[1];
int numBoxes = (int)(boxesMat.total() / 4);
boxesMat = boxesMat.reshape(1, numBoxes);
confsMat = confsMat.reshape(1, numBoxes);
for (int j = 0; j < numBoxes; ++j)
{
Point maxLoc;
double confidence;
minMaxLoc(confsMat.row(j), 0, &confidence, 0, &maxLoc);
if (confidence > confThreshold)
{
const float* box = boxesMat.ptr<float>(j);
boxes.push_back(Rect((int)(box[0] * inpWidth), (int)(box[1] * inpHeight),
(int)((box[2] - box[0]) * inpWidth), (int)((box[3] - box[1]) * inpHeight)));
confidences.push_back((float)confidence);
classIds.push_back(maxLoc.x);
}
}
}
else
{
cout << "Unsupported YOLO ONNX output format" << endl;
exit(-1);
}
Image2BlobParams paramNet;
paramNet.scalefactor = Scalar::all(scale);
paramNet.size = Size(inpWidth, inpHeight);
paramNet.mean = meanv;
paramNet.swapRB = swapRB;
paramNet.paddingmode = paddingMode;
paramNet.blobRectsToImageRects(boxes, boxes, frame.size());
}
else if (postprocessing == "yolov8" || postprocessing == "yolov5")
{
//![forward_buffers]
vector<int> keep_classIds;
vector<float> keep_confidences;
vector<Rect2d> keep_boxes;
//![forward_buffers]
//![postprocess]
yoloPostProcessing(outs, keep_classIds, keep_confidences, keep_boxes, confThreshold, nmsThreshold, postprocessing);
//![postprocess]
for (size_t i = 0; i < keep_classIds.size(); ++i)
{
classIds.push_back(keep_classIds[i]);
confidences.push_back(keep_confidences[i]);
Rect2d box = keep_boxes[i];
boxes.push_back(Rect(cvFloor(box.x), cvFloor(box.y), cvFloor(box.width-box.x), cvFloor(box.height-box.y)));
}
if (framework == "onnx"){
Image2BlobParams paramNet;
paramNet.scalefactor = Scalar::all(scale);
paramNet.size = Size(inpWidth, inpHeight);
paramNet.mean = meanv;
paramNet.swapRB = swapRB;
paramNet.paddingmode = paddingMode;
paramNet.blobRectsToImageRects(boxes, boxes, frame.size());
}
}
else
{
cout<< ("Unknown postprocessing method: " + postprocessing)<<endl;
exit(-1);
}
// NMS is used inside Region layer only on DNN_BACKEND_OPENCV for other backends we need NMS in sample
// or NMS is required if the number of outputs > 1
if (outLayers.size() > 1)
{
map<int, vector<size_t> > class2indices;
for (size_t i = 0; i < classIds.size(); i++)
{
if (confidences[i] >= confThreshold)
{
class2indices[classIds[i]].push_back(i);
}
}
vector<Rect> nmsBoxes;
vector<float> nmsConfidences;
vector<int> nmsClassIds;
for (map<int, vector<size_t> >::iterator it = class2indices.begin(); it != class2indices.end(); ++it)
{
vector<Rect> localBoxes;
vector<float> localConfidences;
vector<size_t> classIndices = it->second;
for (size_t i = 0; i < classIndices.size(); i++)
{
localBoxes.push_back(boxes[classIndices[i]]);
localConfidences.push_back(confidences[classIndices[i]]);
}
vector<int> nmsIndices;
NMSBoxes(localBoxes, localConfidences, confThreshold, nmsThreshold, nmsIndices);
for (size_t i = 0; i < nmsIndices.size(); i++)
{
size_t idx = nmsIndices[i];
nmsBoxes.push_back(localBoxes[idx]);
nmsConfidences.push_back(localConfidences[idx]);
nmsClassIds.push_back(it->first);
}
}
boxes = nmsBoxes;
classIds = nmsClassIds;
confidences = nmsConfidences;
}
}
void drawPred(vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, Mat& frame, FontFace& sans, int stdSize, int stdWeight, int stdImgSize, int stdThickness)
{
//![draw_boxes]
int imgWidth = max(frame.rows, frame.cols);
int size = (stdSize*imgWidth)/stdImgSize;
int weight = (stdWeight*imgWidth)/stdImgSize;
int thickness = (stdThickness*imgWidth)/stdImgSize;
for (size_t idx = 0; idx < boxes.size(); ++idx){
Scalar boxColor = getColor(classIds[idx]);
int left = boxes[idx].x;
int top = boxes[idx].y;
int right = boxes[idx].x + boxes[idx].width;
int bottom = boxes[idx].y + boxes[idx].height;
rectangle(frame, Point(left, top), Point(right, bottom), boxColor, thickness);
string label = format("%.2f", confidences[idx]);
if (!labels.empty())
{
CV_Assert(classIds[idx] < (int)labels.size());
label = labels[classIds[idx]] + ": " + label;
}
Rect r = getTextSize(Size(), label, Point(), sans, size, weight);
int baseline = r.y + r.height;
Size labelSize = Size(r.width, r.height + size/4 - baseline);
top = max(top-thickness/2, labelSize.height);
rectangle(frame, Point(left-thickness/2, top-(labelSize.height)),
Point(left + labelSize.width, top), boxColor, FILLED);
putText(frame, label, Point(left, top-size/4), getTextColor(boxColor), sans, size, weight);
}
//![draw_boxes]
}
void callback(int pos, void*)
{
confThreshold = pos * 0.01f;
}
Scalar getColor(int classId) {
int r = min((classId >> 0 & 1) * 128 + (classId >> 3 & 1) * 64 + (classId >> 6 & 1) * 32 + 80, 255);
int g = min((classId >> 1 & 1) * 128 + (classId >> 4 & 1) * 64 + (classId >> 7 & 1) * 32 + 40, 255);
int b = min((classId >> 2 & 1) * 128 + (classId >> 5 & 1) * 64 + (classId >> 8 & 1) * 32 + 40, 255);
return Scalar(b, g, r);
}
+437
View File
@@ -0,0 +1,437 @@
import cv2 as cv
import argparse
import numpy as np
import sys
import copy
import time
from threading import Thread
import queue
from common import *
from tf_text_graph_common import readTextMessage
from tf_text_graph_ssd import createSSDGraph
from tf_text_graph_faster_rcnn import createFasterRCNNGraph
def help():
print(
'''
Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"\n
To run:
python object_detection.py model_name(e.g yolov8) --input=path/to/your/input/image/or/video (don't pass --input to use device camera)
Sample command:
python object_detection.py yolov8 --input=path/to/image
Model path can also be specified using --model argument
'''
)
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--input', help='Path to input image or video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--out_tf_graph', default='graph.pbtxt',
help='For models from TensorFlow Object Detection API, you may '
'pass a .config file which was used for training through --config '
'argument. This way an additional .pbtxt file with TensorFlow graph will be created.')
parser.add_argument('--thr', type=float, default=0.5, help='Confidence threshold')
parser.add_argument('--nms', type=float, default=0.4, help='Non-maximum suppression threshold')
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
parser.add_argument('--async', type=int, default=0,
dest='use_threads',
help='Choose 0 for synchronous mode and 1 for asynchronous mode')
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'object_detection')
parser = argparse.ArgumentParser(parents=[parser],
description='Use this script to run object detection deep learning networks using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
args = parser.parse_args()
if args.alias is None or hasattr(args, 'help'):
help()
exit(1)
cv.utils.logging.setLogLevel(cv.utils.logging.LOG_LEVEL_INFO)
args.model = findModel(args.model, args.sha1)
if args.config is not None:
args.config = findModel(args.config, args.config_sha1)
if args.labels is not None:
args.labels = findFile(args.labels)
# If config specified, try to load it as TensorFlow Object Detection API's pipeline.
config = readTextMessage(args.config)
if 'model' in config:
print('TensorFlow Object Detection API config detected')
if 'ssd' in config['model'][0]:
print('Preparing text graph representation for SSD model: ' + args.out_tf_graph)
createSSDGraph(args.model, args.config, args.out_tf_graph)
args.config = args.out_tf_graph
elif 'faster_rcnn' in config['model'][0]:
print('Preparing text graph representation for Faster-RCNN model: ' + args.out_tf_graph)
createFasterRCNNGraph(args.model, args.config, args.out_tf_graph)
args.config = args.out_tf_graph
# Load names of classes
labels = None
if args.labels:
with open(args.labels, 'rt') as f:
labels = f.read().rstrip('\n').split('\n')
# Load a network
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
net = cv.dnn.readNet(args.model, args.config, "", engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
if hasattr(cv.dnn, 'DNN_PROFILE_SUMMARY'):
net.setProfilingMode(cv.dnn.DNN_PROFILE_SUMMARY)
outNames = net.getUnconnectedOutLayersNames()
confThreshold = args.thr
nmsThreshold = args.nms
stdSize = 0.8
stdWeight = 2
stdImgSize = 512
asyncN = 0
def get_color(class_id):
r = min((class_id >> 0 & 1) * 128 + (class_id >> 3 & 1) * 64 + (class_id >> 6 & 1) * 32 + 80, 255)
g = min((class_id >> 1 & 1) * 128 + (class_id >> 4 & 1) * 64 + (class_id >> 7 & 1) * 32 + 40, 255)
b = min((class_id >> 2 & 1) * 128 + (class_id >> 5 & 1) * 64 + (class_id >> 8 & 1) * 32 + 40, 255)
return (int(b), int(g), int(r))
def get_text_color(bg_color):
luminance = 0.299 * bg_color[2] + 0.587 * bg_color[1] + 0.114 * bg_color[0]
return (0, 0, 0) if luminance > 128 else (255, 255, 255)
def postprocess(frame, outs):
frameHeight = frame.shape[0]
frameWidth = frame.shape[1]
classIds = []
confidences = []
boxes = []
if args.postprocessing == 'ssd':
# Network produces output blob with a shape 1x1xNx7 where N is a number of
# detections and an every detection is a vector of values
# [batchId, classId, confidence, left, top, right, bottom]
for out in outs:
for detection in out[0, 0]:
confidence = detection[2]
if confidence > confThreshold:
left = int(detection[3])
top = int(detection[4])
right = int(detection[5])
bottom = int(detection[6])
width = right - left + 1
height = bottom - top + 1
if width <= 2 or height <= 2:
left = int(detection[3] * frameWidth)
top = int(detection[4] * frameHeight)
right = int(detection[5] * frameWidth)
bottom = int(detection[6] * frameHeight)
width = right - left + 1
height = bottom - top + 1
classIds.append(int(detection[1]) - 1) # Skip background label
confidences.append(float(confidence))
boxes.append([left, top, width, height])
elif args.postprocessing == 'yolov4':
# boxes[b,N,1,4]+confs[b,N,classes] (normalized) or boxes[b,N,4]+scores[b,N]+classIdx[b,N] (model-px)
if len(outs) == 3 and outs[0].ndim == 3 and outs[0].shape[2] == 4:
boxesArr = outs[0][0]
scoresArr = outs[1][0]
classIdxArr = outs[2][0]
for j in range(boxesArr.shape[0]):
score = float(scoresArr[j])
if score > confThreshold:
x1 = boxesArr[j][0] / args.width
y1 = boxesArr[j][1] / args.height
x2 = boxesArr[j][2] / args.width
y2 = boxesArr[j][3] / args.height
left = int(x1 * frameWidth)
top = int(y1 * frameHeight)
width = int((x2 - x1) * frameWidth)
height = int((y2 - y1) * frameHeight)
classIds.append(int(classIdxArr[j]))
confidences.append(score)
boxes.append([left, top, width, height])
elif len(outs) == 2 and outs[0].ndim == 4 and outs[0].shape[-1] == 4:
boxesArr = outs[0].reshape(-1, 4)
confsArr = outs[1].reshape(boxesArr.shape[0], -1)
for j in range(boxesArr.shape[0]):
classId = np.argmax(confsArr[j])
confidence = float(confsArr[j][classId])
if confidence > confThreshold:
box = boxesArr[j]
left = int(box[0] * frameWidth)
top = int(box[1] * frameHeight)
width = int((box[2] - box[0]) * frameWidth)
height = int((box[3] - box[1]) * frameHeight)
classIds.append(classId)
confidences.append(confidence)
boxes.append([left, top, width, height])
else:
print('Unsupported YOLO ONNX output format')
exit()
elif args.postprocessing == 'yolov8' or args.postprocessing == 'yolov5':
# Network produces output blob with a shape NxC where N is a number of
# detected objects and C is a number of classes + 4 where the first 4
# numbers are [center_x, center_y, width, height]
box_scale_w = frameWidth / args.width
box_scale_h = frameHeight / args.height
for out in outs:
if args.postprocessing == 'yolov8':
out = out[0].transpose(1, 0)
else: # YOLOv5, no transposition needed
out = out[0]
for detection in out:
if args.postprocessing == 'yolov8':
scores = detection[4:]
obj_conf = 1
else:
scores = detection[5:]
obj_conf = detection[4]
classId = np.argmax(scores)
confidence = scores[classId]*obj_conf
if confidence > confThreshold:
center_x = int(detection[0] * box_scale_w)
center_y = int(detection[1] * box_scale_h)
width = int(detection[2] * box_scale_w)
height = int(detection[3] * box_scale_h)
left = int(center_x - width / 2)
top = int(center_y - height / 2)
classIds.append(classId)
confidences.append(float(confidence))
boxes.append([left, top, width, height])
else:
print('Unknown postprocessing method: ' + args.postprocessing)
exit()
# NMS is used inside Region layer only on DNN_BACKEND_OPENCV for another backends we need NMS in sample
# or NMS is required if number of outputs > 1
if len(outNames) > 1 or (args.postprocessing == 'yolov8' or args.postprocessing == 'yolov5') and args.backend != cv.dnn.DNN_BACKEND_OPENCV:
indices = []
classIds = np.array(classIds)
boxes = np.array(boxes)
confidences = np.array(confidences)
unique_classes = set(classIds)
for cl in unique_classes:
class_indices = np.where(classIds == cl)[0]
conf = confidences[class_indices]
box = boxes[class_indices].tolist()
nms_indices = cv.dnn.NMSBoxes(box, conf, confThreshold, nmsThreshold)
indices.extend(class_indices[nms_indices])
else:
indices = np.arange(0, len(classIds))
return boxes, classIds, confidences, indices
def drawPred(classIds, confidences, boxes, indices, fontSize, fontThickness):
for i in indices:
box = boxes[i]
left = box[0]
top = box[1]
right = box[0] + box[2]
bottom = box[1] + box[3]
bg_color = get_color(classIds[i])
cv.rectangle(frame, (left, top), (right, bottom), bg_color, fontThickness)
label = '%.2f' % confidences[i]
# Print a label of class.
if labels:
assert(classIds[i] < len(labels))
label = '%s: %s' % (labels[classIds[i]], label)
labelSize, baseLine = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
top = max(top, labelSize[1])
cv.rectangle(frame, (int(left-fontThickness/2), top - labelSize[1]), (left + labelSize[0], top + baseLine), bg_color, cv.FILLED)
cv.putText(frame, label, (left, top-fontThickness), cv.FONT_HERSHEY_SIMPLEX, fontSize, get_text_color(bg_color), fontThickness)
# Process inputs
winName = 'Deep learning object detection in OpenCV'
cv.namedWindow(winName, cv.WINDOW_AUTOSIZE)
def callback(pos):
global confThreshold
confThreshold = pos / 100.0
cv.createTrackbar('Confidence threshold, %', winName, int(confThreshold * 100), 99, callback)
cap = cv.VideoCapture(cv.samples.findFileOrKeep(args.input) if args.input else 0)
class QueueFPS(queue.Queue):
def __init__(self):
queue.Queue.__init__(self)
self.startTime = 0
self.counter = 0
def put(self, v):
queue.Queue.put(self, v)
self.counter += 1
if self.counter == 1:
self.startTime = time.time()
def getFPS(self):
return self.counter / (time.time() - self.startTime)
process = True
#
# Frames capturing thread
#
framesQueue = QueueFPS()
def framesThreadBody():
global framesQueue, process
while process:
hasFrame, frame = cap.read()
if not hasFrame:
break
framesQueue.put(frame)
#
# Frames processing thread
#
processedFramesQueue = queue.Queue()
predictionsQueue = QueueFPS()
def processingThreadBody():
global processedFramesQueue, predictionsQueue, args, process, asyncN
futureOutputs = []
while process:
# Get a next frame
frame = None
try:
frame = framesQueue.get_nowait()
if asyncN:
if len(futureOutputs) == asyncN:
frame = None # Skip the frame
else:
framesQueue.queue.clear() # Skip the rest of frames
except queue.Empty:
pass
if not frame is None:
frameHeight = frame.shape[0]
frameWidth = frame.shape[1]
# Create a 4D blob from a frame.
inpWidth = args.width if args.width else frameWidth
inpHeight = args.height if args.height else frameHeight
blob = cv.dnn.blobFromImage(frame, scalefactor=args.scale, mean=args.mean, size=(inpWidth, inpHeight), swapRB=args.rgb, ddepth=cv.CV_32F)
processedFramesQueue.put(frame)
# Run a model
net.setInput(blob)
if asyncN:
futureOutputs.append(net.forwardAsync())
else:
outs = net.forward(outNames)
net.printPerfProfile()
predictionsQueue.put(copy.deepcopy(outs))
while futureOutputs and futureOutputs[0].wait_for(0):
out = futureOutputs[0].get()
predictionsQueue.put(copy.deepcopy([out]))
del futureOutputs[0]
if args.use_threads:
framesThread = Thread(target=framesThreadBody)
framesThread.start()
processingThread = Thread(target=processingThreadBody)
processingThread.start()
#
# Postprocessing and rendering loop
#
while cv.waitKey(1) < 0:
try:
# Request prediction first because they put after frames
outs = predictionsQueue.get_nowait()
frame = processedFramesQueue.get_nowait()
imgWidth = max(frame.shape[:2])
fontSize = (stdSize*imgWidth)/stdImgSize
fontThickness = max(1,(stdWeight*imgWidth)//stdImgSize)
boxes, classIds, confidences, indices = postprocess(frame, outs)
drawPred(classIds, confidences, boxes, indices, fontSize, fontThickness)
fontSize = fontSize/2
# Put efficiency information.
if predictionsQueue.counter > 1:
label = 'Camera: %.2f FPS' % (framesQueue.getFPS())
cv.rectangle(frame, (0, 0), (int(260*fontSize), int(80*fontSize)), (255,255,255), cv.FILLED)
cv.putText(frame, label, (0, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
label = 'Network: %.2f FPS' % (predictionsQueue.getFPS())
cv.putText(frame, label, (0, int(2*25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
label = 'Skipped frames: %d' % (framesQueue.counter - predictionsQueue.counter)
cv.putText(frame, label, (0, int(3*25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.imshow(winName, frame)
except queue.Empty:
pass
process = False
framesThread.join()
processingThread.join()
else:
# Non-threaded processing if --async is 0
while cv.waitKey(1) < 0:
hasFrame, frame = cap.read()
if not hasFrame:
cv.waitKey()
break
frameHeight = frame.shape[0]
frameWidth = frame.shape[1]
inpWidth = args.width if args.width else frameWidth
inpHeight = args.height if args.height else frameHeight
blob = cv.dnn.blobFromImage(frame, scalefactor=args.scale, mean=args.mean, size=(inpWidth, inpHeight), swapRB=args.rgb, ddepth=cv.CV_32F)
net.setInput(blob)
outs = net.forward(outNames)
net.printPerfProfile()
boxes, classIds, confidences, indices = postprocess(frame, outs)
drawPred(classIds, confidences, boxes, indices, (stdSize*max(frame.shape[:2]))/stdImgSize, (stdWeight*max(frame.shape[:2]))//stdImgSize)
cv.imshow(winName, frame)
+309
View File
@@ -0,0 +1,309 @@
#include <iostream>
#include <cmath>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
#include "common.hpp"
using namespace cv;
using namespace std;
using namespace cv::dnn;
const string about = "Use this script for testing Object Tracking using OpenCV. \n\n"
"Firstly, download required models using the download_models.py <alias>.\n"
"Valid alias names are nanotrack, vit and dasiamrpn.\n\n"
"To run:\n"
"\t nanotrack: \n"
"\t\t e.g: ./example_dnn_object_tracker nanotrack\n\n"
"\t vit: \n"
"\t\t e.g: ./example_dnn_object_tracker vit\n\n"
"\t dasiamrpn: \n"
"\t\t e.g: ./example_dnn_object_tracker dasiamrpn\n\n"
"To switch between models in runtime, make sure all the models are downloaded using download_models.py\n";
const string param_keys =
"{ help h | | Print help message }"
"{ @alias | vit | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ input i | | Full path to input video folder, the specific camera index. (empty for camera 0) }"
"{ tracking_thrs | 0.3 | Tracking score threshold. If a bbox of score >= 0.3, it is considered as found }";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
static void loadParser(const string &modelName, const string &zooFile)
{
// Load appropriate preprocessing arguments based on model name
if (modelName == "vit")
{
keys += genPreprocArguments(modelName, zooFile, "");
}
else if (modelName == "nanotrack")
{
keys += genPreprocArguments(modelName, zooFile, "nanotrack_head_");
keys += genPreprocArguments(modelName, zooFile, "nanotrack_back_");
}
else if (modelName == "dasiamrpn")
{
keys += genPreprocArguments(modelName, zooFile, "dasiamrpn_");
keys += genPreprocArguments(modelName, zooFile, "dasiamrpn_kernel_r1_");
keys += genPreprocArguments(modelName, zooFile, "dasiamrpn_kernel_cls_");
}
return;
}
static void createTracker(const string &modelName, CommandLineParser &parser, Ptr<Tracker> &tracker) {
int backend = getBackendID(parser.get<String>("backend"));
int target = getTargetID(parser.get<String>("target"));
if (modelName == "dasiamrpn") {
const string net = parser.get<String>("dasiamrpn_model");
const string sha1 = parser.get<String>("dasiamrpn_sha1");
const string kernel_cls1 = parser.get<String>("dasiamrpn_kernel_cls_model");
const string kernel_cls_sha1 = parser.get<String>("dasiamrpn_kernel_cls_sha1");
const string kernel_r1 = parser.get<String>("dasiamrpn_kernel_r1_model");
const string kernel_sha1 = parser.get<String>("dasiamrpn_kernel_r1_sha1");
TrackerDaSiamRPN::Params params;
params.model = findModel(net, sha1);
params.kernel_cls1 = findModel(kernel_cls1, kernel_cls_sha1);
params.kernel_r1 = findModel(kernel_r1, kernel_sha1);
params.backend = backend;
params.target = target;
tracker = TrackerDaSiamRPN::create(params);
} else if (modelName == "nanotrack") {
const string backbone = parser.get<String>("nanotrack_back_model");
const string backSha1 = parser.get<String>("nanotrack_back_sha1");
const string headneck = parser.get<String>("nanotrack_head_model");
const string headSha1 = parser.get<String>("nanotrack_head_sha1");
TrackerNano::Params params;
params.backbone = findModel(backbone, backSha1);
params.neckhead = findModel(headneck, headSha1);
params.backend = backend;
params.target = target;
tracker = TrackerNano::create(params);
} else if (modelName == "vit") {
const string net = parser.get<String>("model");
const string sha1 = parser.get<String>("sha1");
float tracking_score_threshold = parser.get<float>("tracking_thrs");
TrackerVit::Params params;
params.net = findModel(net, sha1);
params.backend = backend;
params.target = target;
params.tracking_score_threshold = tracking_score_threshold;
tracker = TrackerVit::create(params);
} else {
cout<<"Pass the valid alias. Choices are {vit, nanotrack, dasiamrpn }."<<endl;
exit(0);
}
return;
}
int main(int argc, char** argv)
{
CommandLineParser parser(argc, argv, keys);
parser.about(about);
if (!parser.has("@alias") || parser.has("help"))
{
parser.printMessage();
return 0;
}
string modelName = parser.get<String>("@alias");
const string zooFile = findFile(parser.get<String>("zoo"));
loadParser(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
Ptr<Tracker> tracker;
createTracker(modelName, parser, tracker);
const string windowName = "TRACKING";
namedWindow(windowName, WINDOW_NORMAL);
FontFace fontFace("sans");
int stdSize = 20;
int stdWeight = 400;
int stdImgSize = 512;
int imgWidth = -1;
int fontSize = 50;
int fontWeight = 500;
double alpha = 0.4;
Rect selectRect;
string inputName = parser.get<String>("input");
string instructionLabel = "Press space bar to pause video to draw bounding box.";
Rect banner;
// Open a video file or an image file or a camera stream.
VideoCapture cap;
if (inputName.empty() || (isdigit(inputName[0]) && inputName.size() == 1))
{
int c = inputName.empty() ? 0 : inputName[0] - '0';
cout << "Trying to open camera #" << c << " ..." << endl;
if (!cap.open(c))
{
cout << "Capture from camera #" << c << " didn't work. Specify -i=<video> parameter to read from video file" << endl;
return 0;
}
}
else if (inputName.size())
{
string filePath = findFile(inputName);
if (!cap.open(filePath))
{
cout << "Could not open: " << inputName << endl;
return 0;
}
}
Mat image;
for (;;)
{
cap >> image;
if (image.empty())
{
cerr << "Can't capture frame. End of video stream?" << endl;
return 0;
}
else if (imgWidth == -1){
imgWidth = min(image.rows, image.cols);
fontSize = (stdSize*imgWidth)/stdImgSize;
fontWeight = (stdWeight*imgWidth)/stdImgSize;
banner = getTextSize(Size(), instructionLabel, Point(), fontFace, fontSize, fontWeight);
banner.height += 2 * fontSize; // padding
banner.width += 10; // padding
}
Mat org_img = image.clone();
rectangle(image, banner, Scalar::all(255), FILLED);
addWeighted(image, alpha, org_img, 1 - alpha, 0, image);
putText(image, instructionLabel, Point(10, fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
putText(image, "Press space bar after selecting.", Point(10, 2*fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
imshow(windowName, image);
int key = waitKey(30); //Simulating 30 FPS, if reduced frames move really fast
if (key == ' ')
{
selectRect = selectROI(windowName, image);
if (selectRect.width > 0 && selectRect.height > 0)
{
break;
}
else
{
cout << "No valid selection made. Please select again." << endl;
}
}
else if (key == 27) // ESC key to exit
{
exit(0);
}
}
cout << "ROI=" << selectRect << endl;
tracker->init(image, selectRect);
instructionLabel = "Press space bar to select new target";
banner = getTextSize(Size(), instructionLabel, Point(), fontFace, fontSize, fontWeight);
banner.height += 4 * fontSize; // padding
banner.width += 10; // padding
TickMeter tickMeter;
for (int count = 0; ; ++count)
{
cap >> image;
if (image.empty())
{
cerr << "Can't capture frame " << count << ". End of video stream?" << endl;
break;
}
Rect rect;
tickMeter.start();
bool ok = tracker->update(image, rect);
tickMeter.stop();
float score = tracker->getTrackingScore();
Mat render_image = image.clone();
int key = waitKey(30); //Simulating 30 FPS, if reduced frames move really fast
int h = image.rows;
int w = image.cols;
rectangle(render_image, banner, Scalar::all(255), FILLED);
rectangle(render_image, cv::Point(0, int(h - int(1.5*fontSize))), cv::Point(w, h), Scalar::all(255), FILLED);
addWeighted(render_image, alpha, image, 1 - alpha, 0, render_image);
putText(render_image, instructionLabel, Point(10, fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
putText(render_image, "For switching between trackers: press 'v' for ViT, 'n' for Nano, and 'd' for DaSiamRPN.", Point(10, h-10), Scalar(0,0,0), fontFace, int(0.8*fontSize), fontWeight);
if (ok){
if (key == ' '){
putText(render_image, "Select the new target", Point(10, 2*fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
selectRect = selectROI(windowName, render_image);
if (selectRect.width > 0 && selectRect.height > 0){
tracker->init(image, selectRect);
}
else{
cout<<"New target is not selected, switching to previous target"<<endl;
}
}
else if (key == 'v'){
modelName = "vit";
loadParser(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
createTracker(modelName, parser, tracker);
tracker->init(image, rect);
}
else if (key == 'n'){
modelName = "nanotrack";
loadParser(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
createTracker(modelName, parser, tracker);
tracker->init(image, rect);
}
else if (key == 'd'){
modelName = "dasiamrpn";
loadParser(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
createTracker(modelName, parser, tracker);
tracker->init(image, rect);
}
rectangle(render_image, rect, Scalar(0, 255, 0), 2);
}
string timeLabel = format("FPS: %.2f", tickMeter.getFPS());
string scoreLabel = format("Score: %f", score);
string algoLabel = "Algorithm: " + modelName;
putText(render_image, timeLabel, Point(10, 2*fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
putText(render_image, scoreLabel, Point(10, 3*fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
putText(render_image, algoLabel, Point(10, 4*fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
imshow(windowName, render_image);
tickMeter.reset();
if (key == 27 /*ESC*/)
exit(0);
}
return 0;
}
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env python
import sys
import cv2 as cv
import argparse
from common import *
def help():
print(
'''
Use this script for testing Object Tracking using OpenCV.
Firstly, download required models using the download_models.py.
To run:
nanotrack:
Download Model: python download_models.py nanotrack
Example: python object_tracker.py nanotrack
vit:
Download Model: python download_models.py vit
Example: python object_tracker.py vit
or
python object_tracker.py
dasiamrpn:
Download Model: python download_models.py dasiamrpn
Example: python object_tracker.py dasiamrpn
To switch between models in runtime, make sure all the models are downloaded using download_models.py'''
)
def load_parser(model_name):
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument("--input", type=str, help="Path to video source")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'object_tracker', alias=model_name)
if model_name == "dasiamrpn":
add_preproc_args(args.zoo, parser, 'object_tracker', prefix="dasiamrpn_", alias="dasiamrpn")
add_preproc_args(args.zoo, parser, 'object_tracker', prefix="dasiamrpn_kernel_r1_", alias="dasiamrpn")
add_preproc_args(args.zoo, parser, 'object_tracker', prefix="dasiamrpn_kernel_cls_", alias="dasiamrpn")
elif model_name == "nanotrack":
add_preproc_args(args.zoo, parser, 'object_tracker', prefix="nanotrack_back_", alias="nanotrack")
add_preproc_args(args.zoo, parser, 'object_tracker', prefix="nanotrack_head_", alias="nanotrack")
elif model_name != "vit":
print("Pass the valid alias. Choices are { nanotrack, vit, dasiamrpn }")
exit(0)
parser = argparse.ArgumentParser(parents=[parser],
description='''
Firstly, download required models using `python download_models.py {modelName}`
Run using python object_tracker.py {modelName}.
''',
formatter_class=argparse.RawTextHelpFormatter)
return parser.parse_args()
def createTracker(model_name, args):
if model_name == 'dasiamrpn':
print("Using Dasiamrpn Tracker.")
params = cv.TrackerDaSiamRPN_Params()
params.model = findModel(args.dasiamrpn_model, args.dasiamrpn_sha1)
params.kernel_cls1 = findModel(args.dasiamrpn_kernel_cls_model, args.dasiamrpn_kernel_cls_sha1)
params.kernel_r1 = findModel(args.dasiamrpn_kernel_r1_model, args.dasiamrpn_kernel_r1_sha1)
tracker = cv.TrackerDaSiamRPN_create(params)
elif model_name == 'nanotrack':
print("Using Nano Tracker.")
params = cv.TrackerNano_Params()
params.backbone = findModel(args.nanotrack_back_model, args.nanotrack_back_sha1)
params.neckhead = findModel(args.nanotrack_head_model, args.nanotrack_head_sha1)
tracker = cv.TrackerNano_create(params)
elif model_name == 'vit':
print("Using Vit Tracker.")
params = cv.TrackerVit_Params()
params.net = findModel(args.model, args.sha1)
tracker = cv.TrackerVit_create(params)
else:
help()
exit(-1)
return tracker
def main(model_name, args):
tracker = createTracker(model_name, args)
videoPath = args.input
print('Using video: {}'.format(videoPath))
cap = cv.VideoCapture(cv.samples.findFile(args.input) if args.input else 0)
if not cap.isOpened():
print("Can't open video stream: {}".format(videoPath))
exit(-1)
stdSize = 0.6
stdWeight = 2
stdImgSize = 512
imgWidth = -1 # Initialization
fontSize = 1.5
fontThickness = 1
alpha = 0.5
windowName = "TRACKING"
cv.namedWindow(windowName, cv.WINDOW_NORMAL)
while True:
ret, image = cap.read()
if not ret:
print("Video completed!!")
return -1
if imgWidth == -1:
imgWidth = min(image.shape[:2])
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize)
fontThickness = max(fontThickness,(stdWeight*imgWidth)//stdImgSize)
label = "Press space bar to pause video to draw bounding box."
labelSize, _ = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
org_img = image.copy()
cv.rectangle(image, (0, 0), (labelSize[0]+10, labelSize[1]+int(40*fontSize)), (255,255,255), cv.FILLED)
cv.addWeighted(image, alpha, org_img, 1 - alpha, 0, image)
cv.putText(image, label, (10, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.putText(image, "Press space bar after selecting.", (10, int(55*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.imshow(windowName, image)
key = cv.waitKey(30) & 0xFF
if key == ord(' '):
bbox = cv.selectROI(windowName, image)
print('ROI: {}'.format(bbox))
if bbox != (0, 0, 0, 0):
break
if key == ord('q') or key == 27:
return
try:
tracker.init(image, bbox)
except Exception as e:
print('Unable to initialize tracker with requested bounding box. Is there any object?')
print(e)
tick_meter = cv.TickMeter()
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if imgWidth == -1:
imgWidth = min(frame.shape[:2])
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize)
fontThickness = max(fontThickness,(stdWeight*imgWidth)//stdImgSize)
label="Press space bar to select new target"
labelSize, _ = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
tick_meter.reset()
tick_meter.start()
ok, newbox = tracker.update(frame)
tick_meter.stop()
score = tracker.getTrackingScore()
render_image = frame.copy()
key = cv.waitKey(30) & 0xFF
h, w = frame.shape[:2]
cv.rectangle(render_image, (0, 0), (labelSize[0]+10, labelSize[1]+int(100*fontSize)), (255,255,255), cv.FILLED)
cv.rectangle(render_image, (0, int(h-45*fontSize)), (w, h), (255,255,255), cv.FILLED)
cv.addWeighted(render_image, alpha, frame, 1 - alpha, 0, render_image)
cv.putText(render_image, label, (10, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.putText(render_image, "For switching between trackers: press 'v' for ViT, 'n' for Nanotrack, and 'd' for DaSiamRPN.", (10, h-10), cv.FONT_HERSHEY_SIMPLEX, 0.8*fontSize, (0, 0, 0), fontThickness)
if ok:
if key == ord(' '):
cv.putText(render_image, "Select the new target", (10, int(55*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
bbox = cv.selectROI(windowName, render_image)
print('ROI:', bbox)
if bbox != (0, 0, 0, 0):
tracker.init(frame, bbox)
elif key == ord('v'):
model_name = "vit"
args = load_parser(model_name)
tracker = createTracker(model_name, args)
tracker.init(frame, newbox)
elif key == ord('n'):
model_name = "nanotrack"
args = load_parser(model_name)
tracker = createTracker(model_name, args)
tracker.init(frame, newbox)
elif key == ord('d'):
model_name = "dasiamrpn"
args = load_parser(model_name)
tracker = createTracker(model_name, args)
tracker.init(frame, newbox)
elif key == ord('q') or key == 27:
return
cv.rectangle(render_image, newbox, (200, 0, 0), thickness=2)
time_label = f"FPS: {tick_meter.getFPS():.2f}"
score_label = f"Tracking score: {score:.2f}"
algo_label = f"Algorithm: {model_name}"
cv.putText(render_image, time_label, (10, int(55*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.putText(render_image, score_label, (10, int(85*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.putText(render_image, algo_label, (10, int(115*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.imshow(windowName, render_image)
if key in [ord('q'), 27]:
break
if __name__ == '__main__':
help()
if len(sys.argv) < 2 or sys.argv[1].startswith("--"):
model_name = "vit"
else:
model_name = sys.argv[1]
args = load_parser(model_name)
main(model_name, args)
cv.destroyAllWindows()
+157
View File
@@ -0,0 +1,157 @@
//
// this sample demonstrates the use of pretrained openpose networks with opencv's dnn module.
//
// it can be used for body pose detection, using either the COCO model(18 parts):
// http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/coco/pose_iter_440000.caffemodel
// https://raw.githubusercontent.com/opencv/opencv_extra/5.x/testdata/dnn/openpose_pose_coco.prototxt
//
// or the MPI model(16 parts):
// http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/mpi/pose_iter_160000.caffemodel
// https://raw.githubusercontent.com/opencv/opencv_extra/5.x/testdata/dnn/openpose_pose_mpi_faster_4_stages.prototxt
//
// (to simplify this sample, the body models are restricted to a single person.)
//
//
// you can also try the hand pose model:
// http://posefs1.perception.cs.cmu.edu/OpenPose/models/hand/pose_iter_102000.caffemodel
// https://raw.githubusercontent.com/CMU-Perceptual-Computing-Lab/openpose/master/models/hand/pose_deploy.prototxt
//
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace cv::dnn;
#include <iostream>
using namespace std;
// connection table, in the format [model_id][pair_id][from/to]
// please look at the nice explanation at the bottom of:
// https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/doc/output.md
//
const int POSE_PAIRS[3][20][2] = {
{ // COCO body
{1,2}, {1,5}, {2,3},
{3,4}, {5,6}, {6,7},
{1,8}, {8,9}, {9,10},
{1,11}, {11,12}, {12,13},
{1,0}, {0,14},
{14,16}, {0,15}, {15,17}
},
{ // MPI body
{0,1}, {1,2}, {2,3},
{3,4}, {1,5}, {5,6},
{6,7}, {1,14}, {14,8}, {8,9},
{9,10}, {14,11}, {11,12}, {12,13}
},
{ // hand
{0,1}, {1,2}, {2,3}, {3,4}, // thumb
{0,5}, {5,6}, {6,7}, {7,8}, // pinkie
{0,9}, {9,10}, {10,11}, {11,12}, // middle
{0,13}, {13,14}, {14,15}, {15,16}, // ring
{0,17}, {17,18}, {18,19}, {19,20} // small
}};
int main(int argc, char **argv)
{
CommandLineParser parser(argc, argv,
"{ h help | false | print this help message }"
"{ p proto | | (required) model configuration, e.g. hand/pose.prototxt }"
"{ m model | | (required) model weights, e.g. hand/pose_iter_102000.caffemodel }"
"{ i image | | (required) path to image file (containing a single person, or hand) }"
"{ d dataset | | specify what kind of model was trained. It could be (COCO, MPI, HAND) depends on dataset. }"
"{ width | 368 | Preprocess input image by resizing to a specific width. }"
"{ height | 368 | Preprocess input image by resizing to a specific height. }"
"{ t threshold | 0.1 | threshold or confidence value for the heatmap }"
"{ s scale | 0.003922 | scale for blob }"
);
String modelTxt = samples::findFile(parser.get<string>("proto"));
String modelBin = samples::findFile(parser.get<string>("model"));
String imageFile = samples::findFile(parser.get<String>("image"));
String dataset = parser.get<String>("dataset");
int W_in = parser.get<int>("width");
int H_in = parser.get<int>("height");
float thresh = parser.get<float>("threshold");
float scale = parser.get<float>("scale");
if (parser.get<bool>("help") || modelTxt.empty() || modelBin.empty() || imageFile.empty())
{
cout << "A sample app to demonstrate human or hand pose detection with a pretrained OpenPose dnn." << endl;
parser.printMessage();
return 0;
}
int midx, npairs, nparts;
if (!dataset.compare("COCO")) { midx = 0; npairs = 17; nparts = 18; }
else if (!dataset.compare("MPI")) { midx = 1; npairs = 14; nparts = 16; }
else if (!dataset.compare("HAND")) { midx = 2; npairs = 20; nparts = 22; }
else
{
std::cerr << "Can't interpret dataset parameter: " << dataset << std::endl;
exit(-1);
}
// read the network model
Net net = readNet(modelBin, modelTxt);
// and the image
Mat img = imread(imageFile);
if (img.empty())
{
std::cerr << "Can't read image from the file: " << imageFile << std::endl;
exit(-1);
}
// send it through the network
Mat inputBlob = blobFromImage(img, scale, Size(W_in, H_in), Scalar(0, 0, 0), false, false);
net.setInput(inputBlob);
Mat result = net.forward();
// the result is an array of "heatmaps", the probability of a body part being in location x,y
int H = result.size[2];
int W = result.size[3];
// find the position of the body parts
vector<Point> points(22);
for (int n=0; n<nparts; n++)
{
// Slice heatmap of corresponding body's part.
Mat heatMap(H, W, CV_32F, result.ptr(0,n));
// 1 maximum per heatmap
Point p(-1,-1),pm;
double conf;
minMaxLoc(heatMap, 0, &conf, 0, &pm);
if (conf > thresh)
p = pm;
points[n] = p;
}
// connect body parts and draw it !
float SX = float(img.cols) / W;
float SY = float(img.rows) / H;
for (int n=0; n<npairs; n++)
{
// lookup 2 connected body/hand parts
Point2f a = points[POSE_PAIRS[midx][n][0]];
Point2f b = points[POSE_PAIRS[midx][n][1]];
// we did not find enough confidence before
if (a.x<=0 || a.y<=0 || b.x<=0 || b.y<=0)
continue;
// scale to image size
a.x*=SX; a.y*=SY;
b.x*=SX; b.y*=SY;
line(img, a, b, Scalar(0,200,0), 2);
circle(img, a, 3, Scalar(0,0,200), -1);
circle(img, b, 3, Scalar(0,0,200), -1);
}
imshow("OpenPose", img);
waitKey();
return 0;
}
+122
View File
@@ -0,0 +1,122 @@
# To use Inference Engine backend, specify location of plugins:
# source /opt/intel/computer_vision_sdk/bin/setupvars.sh
import cv2 as cv
import numpy as np
import argparse
parser = argparse.ArgumentParser(
description='This script is used to demonstrate OpenPose human pose estimation network '
'from https://github.com/CMU-Perceptual-Computing-Lab/openpose project using OpenCV. '
'The sample and model are simplified and could be used for a single person on the frame.')
parser.add_argument('--input', help='Path to image or video. Skip to capture frames from camera')
parser.add_argument('--proto', help='Path to .prototxt')
parser.add_argument('--model', help='Path to .caffemodel')
parser.add_argument('--dataset', help='Specify what kind of model was trained. '
'It could be (COCO, MPI, HAND) depends on dataset.')
parser.add_argument('--thr', default=0.1, type=float, help='Threshold value for pose parts heat map')
parser.add_argument('--width', default=368, type=int, help='Resize input to specific width.')
parser.add_argument('--height', default=368, type=int, help='Resize input to specific height.')
parser.add_argument('--scale', default=0.003922, type=float, help='Scale for blob.')
args = parser.parse_args()
if args.dataset == 'COCO':
BODY_PARTS = { "Nose": 0, "Neck": 1, "RShoulder": 2, "RElbow": 3, "RWrist": 4,
"LShoulder": 5, "LElbow": 6, "LWrist": 7, "RHip": 8, "RKnee": 9,
"RAnkle": 10, "LHip": 11, "LKnee": 12, "LAnkle": 13, "REye": 14,
"LEye": 15, "REar": 16, "LEar": 17, "Background": 18 }
POSE_PAIRS = [ ["Neck", "RShoulder"], ["Neck", "LShoulder"], ["RShoulder", "RElbow"],
["RElbow", "RWrist"], ["LShoulder", "LElbow"], ["LElbow", "LWrist"],
["Neck", "RHip"], ["RHip", "RKnee"], ["RKnee", "RAnkle"], ["Neck", "LHip"],
["LHip", "LKnee"], ["LKnee", "LAnkle"], ["Neck", "Nose"], ["Nose", "REye"],
["REye", "REar"], ["Nose", "LEye"], ["LEye", "LEar"] ]
elif args.dataset == 'MPI':
BODY_PARTS = { "Head": 0, "Neck": 1, "RShoulder": 2, "RElbow": 3, "RWrist": 4,
"LShoulder": 5, "LElbow": 6, "LWrist": 7, "RHip": 8, "RKnee": 9,
"RAnkle": 10, "LHip": 11, "LKnee": 12, "LAnkle": 13, "Chest": 14,
"Background": 15 }
POSE_PAIRS = [ ["Head", "Neck"], ["Neck", "RShoulder"], ["RShoulder", "RElbow"],
["RElbow", "RWrist"], ["Neck", "LShoulder"], ["LShoulder", "LElbow"],
["LElbow", "LWrist"], ["Neck", "Chest"], ["Chest", "RHip"], ["RHip", "RKnee"],
["RKnee", "RAnkle"], ["Chest", "LHip"], ["LHip", "LKnee"], ["LKnee", "LAnkle"] ]
elif args.dataset == 'HAND':
BODY_PARTS = { "Wrist": 0,
"ThumbMetacarpal": 1, "ThumbProximal": 2, "ThumbMiddle": 3, "ThumbDistal": 4,
"IndexFingerMetacarpal": 5, "IndexFingerProximal": 6, "IndexFingerMiddle": 7, "IndexFingerDistal": 8,
"MiddleFingerMetacarpal": 9, "MiddleFingerProximal": 10, "MiddleFingerMiddle": 11, "MiddleFingerDistal": 12,
"RingFingerMetacarpal": 13, "RingFingerProximal": 14, "RingFingerMiddle": 15, "RingFingerDistal": 16,
"LittleFingerMetacarpal": 17, "LittleFingerProximal": 18, "LittleFingerMiddle": 19, "LittleFingerDistal": 20,
}
POSE_PAIRS = [ ["Wrist", "ThumbMetacarpal"], ["ThumbMetacarpal", "ThumbProximal"],
["ThumbProximal", "ThumbMiddle"], ["ThumbMiddle", "ThumbDistal"],
["Wrist", "IndexFingerMetacarpal"], ["IndexFingerMetacarpal", "IndexFingerProximal"],
["IndexFingerProximal", "IndexFingerMiddle"], ["IndexFingerMiddle", "IndexFingerDistal"],
["Wrist", "MiddleFingerMetacarpal"], ["MiddleFingerMetacarpal", "MiddleFingerProximal"],
["MiddleFingerProximal", "MiddleFingerMiddle"], ["MiddleFingerMiddle", "MiddleFingerDistal"],
["Wrist", "RingFingerMetacarpal"], ["RingFingerMetacarpal", "RingFingerProximal"],
["RingFingerProximal", "RingFingerMiddle"], ["RingFingerMiddle", "RingFingerDistal"],
["Wrist", "LittleFingerMetacarpal"], ["LittleFingerMetacarpal", "LittleFingerProximal"],
["LittleFingerProximal", "LittleFingerMiddle"], ["LittleFingerMiddle", "LittleFingerDistal"] ]
else:
raise(Exception("you need to specify either 'COCO', 'MPI', or 'Hand' in args.dataset"))
inWidth = args.width
inHeight = args.height
inScale = args.scale
net = cv.dnn.readNet(cv.samples.findFile(args.proto), cv.samples.findFile(args.model))
cap = cv.VideoCapture(args.input if args.input else 0)
while cv.waitKey(1) < 0:
hasFrame, frame = cap.read()
if not hasFrame:
cv.waitKey()
break
frameWidth = frame.shape[1]
frameHeight = frame.shape[0]
inp = cv.dnn.blobFromImage(frame, inScale, (inWidth, inHeight),
(0, 0, 0), swapRB=False, crop=False)
net.setInput(inp)
t0 = cv.getTickCount()
out = net.forward()
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
assert(len(BODY_PARTS) <= out.shape[1])
points = []
for i in range(len(BODY_PARTS)):
# Slice heatmap of corresponding body's part.
heatMap = out[0, i, :, :]
# Originally, we try to find all the local maximums. To simplify a sample
# we just find a global one. However only a single pose at the same time
# could be detected this way.
_, conf, _, point = cv.minMaxLoc(heatMap)
x = (frameWidth * point[0]) / out.shape[3]
y = (frameHeight * point[1]) / out.shape[2]
# Add a point if it's confidence is higher than threshold.
points.append((int(x), int(y)) if conf > args.thr else None)
for pair in POSE_PAIRS:
partFrom = pair[0]
partTo = pair[1]
assert(partFrom in BODY_PARTS)
assert(partTo in BODY_PARTS)
idFrom = BODY_PARTS[partFrom]
idTo = BODY_PARTS[partTo]
if points[idFrom] and points[idTo]:
cv.line(frame, points[idFrom], points[idTo], (0, 255, 0), 3)
cv.ellipse(frame, points[idFrom], (3, 3), 0, 0, 360, (0, 0, 255), cv.FILLED)
cv.ellipse(frame, points[idTo], (3, 3), 0, 0, 360, (0, 0, 255), cv.FILLED)
cv.putText(frame, '%.2f ms' % (t * 1000.0), (10, 20), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
cv.imshow('OpenPose using OpenCV', frame)
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env python
'''
This sample uses the RAFT model to calculate optical flow.
RAFT Original Paper: https://arxiv.org/pdf/2003.12039.pdf
RAFT Repo: https://github.com/princeton-vl/RAFT
Download the .onnx model from here https://github.com/opencv/opencv_zoo/raw/281d232cd99cd920853106d853c440edd35eb442/models/optical_flow_estimation_raft/optical_flow_estimation_raft_2023aug.onnx.
Note: the legacy FlowNet v2 Caffe pipeline (--proto/.caffemodel) has been removed together
with the Caffe importer. Please provide a single ONNX model.
'''
import argparse
import os.path
import numpy as np
import cv2 as cv
class OpticalFlow(object):
def __init__(self, model, height, width, proto=""):
if proto:
raise cv.error("Caffe support has been removed. Please provide a single ONNX model path (e.g. RAFT).")
self.net = cv.dnn.readNet(model)
self.net.setPreferableBackend(cv.dnn.DNN_BACKEND_OPENCV)
self.height = height
self.width = width
def compute_flow(self, first_img, second_img):
inp0 = cv.dnn.blobFromImage(first_img, size=(self.width, self.height))
inp1 = cv.dnn.blobFromImage(second_img, size=(self.width, self.height))
self.net.setInputsNames(["img0", "img1"])
self.net.setInput(inp0, "img0")
self.net.setInput(inp1, "img1")
flow = self.net.forward()
output = self.motion_to_color(flow)
return output
def motion_to_color(self, flow):
arr = np.arange(0, 255, dtype=np.uint8)
colormap = cv.applyColorMap(arr, cv.COLORMAP_HSV)
colormap = colormap.squeeze(1)
flow = flow.squeeze(0)
fx, fy = flow[0, ...], flow[1, ...]
rad = np.sqrt(fx**2 + fy**2)
maxrad = rad.max() if rad.max() != 0 else 1
ncols = arr.size
rad = rad[..., np.newaxis] / maxrad
a = np.arctan2(-fy / maxrad, -fx / maxrad) / np.pi
fk = (a + 1) / 2.0 * (ncols - 1)
k0 = fk.astype(np.int32)
k1 = (k0 + 1) % ncols
f = fk[..., np.newaxis] - k0[..., np.newaxis]
col0 = colormap[k0] / 255.0
col1 = colormap[k1] / 255.0
col = (1 - f) * col0 + f * col1
col = np.where(rad <= 1, 1 - rad * (1 - col), col * 0.75)
output = (255.0 * col).astype(np.uint8)
return output
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Use this script to calculate optical flow',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('-input', '-i', required=True, help='Path to input video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--height', default=320, type=int, help='Input height')
parser.add_argument('--width', default=448, type=int, help='Input width')
parser.add_argument('--model', '-m', required=True, help='Path to a single ONNX model (e.g. RAFT).')
args, _ = parser.parse_known_args()
if not os.path.isfile(args.model):
raise OSError("Model does not exist")
winName = 'Calculation optical flow in OpenCV'
cv.namedWindow(winName, cv.WINDOW_NORMAL)
cap = cv.VideoCapture(args.input if args.input else 0)
hasFrame, first_frame = cap.read()
opt_flow = OpticalFlow(args.model, 360, 480)
while cv.waitKey(1) < 0:
hasFrame, second_frame = cap.read()
if not hasFrame:
break
flow = opt_flow.compute_flow(first_frame, second_frame)
first_frame = second_frame
cv.imshow(winName, flow)
+380
View File
@@ -0,0 +1,380 @@
/*
This sample detects the query person in the given video file.
Authors of samples and Youtu ReID baseline:
Xing Sun <winfredsun@tencent.com>
Feng Zheng <zhengf@sustech.edu.cn>
Xinyang Jiang <sevjiang@tencent.com>
Fufu Yu <fufuyu@tencent.com>
Enwei Zhang <miyozhang@tencent.com>
Copyright (C) 2020-2021, Tencent.
Copyright (C) 2020-2021, SUSTech.
Copyright (C) 2024, Bigvision LLC.
How to use:
sample command to run:
./example_dnn_person_reid
The system will ask you to mark the person to be tracked
You can download ReID model using:
`python download_models.py reid`
and yolo model using:
`python download_models.py yolov8`
Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
*/
#include <iostream>
#include <fstream>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/dnn.hpp>
#include "common.hpp"
using namespace cv;
using namespace cv::dnn;
using namespace std;
const string about = "Use this script for Person Re-identification using OpenCV. \n\n"
"Firstly, download required models i.e. reid and yolov8 using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
"To run:\n"
"\t Example: ./example_dnn_person_reid reid\n\n"
"Re-Identification model path can also be specified using --model argument. Detection model can be set using --yolo_model argument.\n\n";
const string param_keys =
"{help h | | show help message}"
"{ @alias | reid | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{query q | | Path to target image. Skip this argument to select target in the video frame.}"
"{input i | | video file path}"
"{yolo_model | | Path to yolov8n.onnx}";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
struct MatComparator
{
bool operator()(const Mat &a, const Mat &b) const
{
return a.data < b.data; // This is a simple pointer comparison, not content!
}
};
map<Mat, Rect, MatComparator> imgDict;
int height, width, yoloHeight, yoloWidth;
float scale, yoloScale;
bool swapRB, yoloSwapRB;
Scalar mean_v, stnd;
static void extractFeatures(vector<Mat> &imglist, Net &net, vector<Mat> &features)
{
for (size_t st = 0; st < imglist.size(); st++)
{
Mat blob;
blobFromImage(imglist[st], blob, scale, Size(width, height), mean_v, swapRB, false, CV_32F);
// Check if standard deviation values are non-zero
if (stnd[0] != 0.0 && stnd[1] != 0.0 && stnd[2] != 0.0)
{
// Divide blob by std for each channel
divide(blob, stnd, blob);
}
net.setInput(blob);
Mat out=net.forward();
vector<int> s {out.size[0], out.size[1]};
out = out.reshape(1, s);
for (int i = 0; i < out.rows; i++)
{
Mat norm_features;
normalize(out.row(i), norm_features, 1.0, 0.0, NORM_L2);
features.push_back(norm_features);
}
}
return;
}
static int findMatching(const Mat &queryFeatures, const vector<Mat> &galleryFeatures)
{
if (queryFeatures.empty() || galleryFeatures.empty())
return -1; // No valid index if either feature list is empty
int bestIndex = -1;
float maxSimilarity = FLT_MIN;
for (int j = 0; j < (int)galleryFeatures.size(); j++)
{
float currentSimilarity = static_cast<float>(queryFeatures.dot(galleryFeatures[j]));
if (currentSimilarity > maxSimilarity)
{
maxSimilarity = currentSimilarity;
bestIndex = j;
}
}
return bestIndex;
}
static void yoloDetector(Mat &frame, Net &net, vector<Mat>& images)
{
int ht = frame.rows;
int wt = frame.cols;
int length = max(ht, wt);
Mat image = Mat::zeros(Size(length, length), frame.type());
frame.copyTo(image(Rect(0, 0, wt, ht)));
// Calculate the scale
double norm_scale = static_cast<double>(length) / yoloWidth;
Mat blob;
blobFromImage(image, blob, yoloScale, Size(yoloWidth, yoloHeight), Scalar(), yoloSwapRB, false, CV_32F);
net.setInput(blob);
vector<Mat> outputs;
net.forward(outputs);
Mat reshapedMatrix = outputs[0].reshape(0, 84); // Reshape to 2D (84 rows, 8400 columns)
Mat outputTransposed;
transpose(reshapedMatrix, outputTransposed);
int rows = outputTransposed.rows;
vector<Rect2d> boxes;
vector<float> scores;
vector<int> class_ids;
for (int i = 0; i < rows; i++) {
double minScore, maxScore;
Point minClassLoc, maxClassLoc;
minMaxLoc(outputTransposed.row(i).colRange(4, outputTransposed.cols), &minScore, &maxScore, &minClassLoc, &maxClassLoc);
if (maxScore >= 0.25 && maxClassLoc.x == 0) {
double centerX = outputTransposed.at<float>(i, 0);
double centerY = outputTransposed.at<float>(i, 1);
double w = outputTransposed.at<float>(i, 2);
double h = outputTransposed.at<float>(i, 3);
Rect2d box(
centerX - 0.5 * w, // x
centerY - 0.5 * h, // y
w, // width
h // height
);
boxes.push_back(box);
scores.push_back(static_cast<float>(maxScore));
class_ids.push_back(maxClassLoc.x); // x location gives the index
}
}
// Apply Non-Maximum Suppression
vector<int> indexes;
NMSBoxes(boxes, scores, 0.25f, 0.45f, indexes, 0.5f, 0);
images.resize(indexes.size());
for (size_t i = 0; i < indexes.size(); i++) {
int index = indexes[i];
int x = static_cast<int>(round(boxes[index].x * norm_scale));
int y = static_cast<int>(round(boxes[index].y * norm_scale));
int w = static_cast<int>(round(boxes[index].width * norm_scale));
int h = static_cast<int>(round(boxes[index].height * norm_scale));
// Make sure the box is within the frame
x = max(0, x);
y = max(0, y);
w = min(w, frame.cols - x);
h = min(h, frame.rows - y);
// Crop the image
Rect roi(x, y, w, h); // Define a region of interest
images[i] = frame(roi); // Crop the region from the frame
imgDict[images[i]] = roi;
}
return;
}
int main(int argc, char **argv)
{
CommandLineParser parser(argc, argv, keys);
if (!parser.has("@alias") || parser.has("help"))
{
cout<<about<<endl;
parser.printMessage();
return 0;
}
string modelName = parser.get<String>("@alias");
string zooFile = findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile);
keys += genPreprocArguments(modelName, zooFile, "yolo_");
parser = CommandLineParser(argc, argv, keys);
parser.about("Use this script to run ReID networks using OpenCV.");
const string sha1 = parser.get<String>("sha1");
const string yoloSha1 = parser.get<String>("yolo_sha1");
const string modelPath = findModel(parser.get<String>("model"), sha1);
const string queryImagePath = parser.get<String>("query");
string videoPath = parser.get<String>("input");
const string yoloPath = findModel(parser.get<String>("yolo_model"), yoloSha1);
const string backend = parser.get<String>("backend");
const string target = parser.get<String>("target");
height = parser.get<int>("height");
width = parser.get<int>("width");
yoloHeight = parser.get<int>("yolo_height");
yoloWidth = parser.get<int>("yolo_width");
scale = parser.get<float>("scale");
yoloScale = parser.get<float>("yolo_scale");
swapRB = parser.get<bool>("rgb");
yoloSwapRB = parser.get<bool>("yolo_rgb");
mean_v = parser.get<Scalar>("mean");
stnd = parser.get<Scalar>("std");
int stdSize = 20;
int stdWeight = 400;
int stdImgSize = 512;
int imgWidth = -1; // Initialization
int fontSize = 50;
int fontWeight = 500;
EngineType engine = ENGINE_AUTO;
if (backend != "default" || target != "cpu"){
engine = ENGINE_CLASSIC;
}
Net reidNet = readNetFromONNX(modelPath, engine);
reidNet.setPreferableBackend(getBackendID(backend));
reidNet.setPreferableTarget(getTargetID(target));
if(yoloPath.empty()){
cout<<"[ERROR] Please pass path to yolov8.onnx model file using --yolo_model."<<endl;
return -1;
}
Net net = readNetFromONNX(yoloPath, engine);
FontFace fontFace("sans");
VideoCapture cap;
if (!videoPath.empty()){
videoPath = findFile(videoPath);
cap.open(videoPath);
}
else
cap.open(0);
if (!cap.isOpened()) {
cerr << "Error: Video could not be opened." << endl;
return -1;
}
vector<Mat> queryImages;
Mat queryImg;
if (!queryImagePath.empty()) {
queryImg = imread(queryImagePath);
if (queryImg.empty()) {
cerr << "Error: Query image could not be loaded." << endl;
return -1;
}
queryImages.push_back(queryImg);
} else {
Mat image;
for(;;) {
cap.read(image);
if (image.empty()) {
cerr << "Error reading the video" << endl;
return -1;
}
if (imgWidth == -1){
imgWidth = min(image.rows, image.cols);
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize);
fontWeight = min(fontWeight, (stdWeight*imgWidth)/stdImgSize);
}
const string label = "Press space bar to pause video to draw bounding box.";
Rect r = getTextSize(Size(), label, Point(), fontFace, fontSize, fontWeight);
r.height += 2 * fontSize; // padding
r.width += 10; // padding
rectangle(image, r, Scalar::all(255), FILLED);
putText(image, label, Point(10, fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
putText(image, "Press space bar after selecting.", Point(10, 2*fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
imshow("TRACKING", image);
int key = waitKey(200);
if(key == ' '){
Rect rect = selectROI("TRACKING", image);
if (rect.width > 0 && rect.height > 0) {
queryImg = image(rect).clone();
queryImages.push_back(queryImg);
break;
}
}
if (key == 'q' || key == 27) {
return 0;
}
}
}
Mat frame;
vector<Mat> queryFeatures;
extractFeatures(queryImages, reidNet, queryFeatures);
vector<Mat> detectedImages;
vector<Mat> galleryFeatures;
for(;;) {
if (!cap.read(frame) || frame.empty()) {
break;
}
if (imgWidth == -1){
imgWidth = min(frame.rows, frame.cols);
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize);
fontWeight = min(fontWeight, (stdWeight*imgWidth)/stdImgSize);
}
detectedImages.clear();
galleryFeatures.clear();
yoloDetector(frame, net, detectedImages);
extractFeatures(detectedImages, reidNet, galleryFeatures);
int match_idx = findMatching(queryFeatures[0], galleryFeatures);
if (match_idx != -1 && static_cast<int>(detectedImages.size()) > match_idx) {
Mat matchImg = detectedImages[match_idx];
Rect bbox = imgDict[matchImg];
rectangle(frame, bbox, Scalar(0, 0, 255), 2);
putText(frame, "Target", Point(bbox.x, bbox.y - 10), Scalar(0,0,255), fontFace, fontSize, fontWeight);
}
const string label = "Tracking";
Rect r = getTextSize(Size(), label, Point(), fontFace, fontSize, fontWeight);
r.height += fontSize; // padding
r.width += 10; // padding
rectangle(frame, r, Scalar::all(255), FILLED);
putText(frame, label, Point(10, fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
imshow("TRACKING", frame);
int key = waitKey(30);
if (key == 'q' || key == 27) {
break;
}
}
cap.release();
destroyAllWindows();
return 0;
}
+270
View File
@@ -0,0 +1,270 @@
#!/usr/bin/env python
'''
This sample detects the query person in the given video file.
Authors of samples and Youtu ReID baseline:
Xing Sun <winfredsun@tencent.com>
Feng Zheng <zhengf@sustech.edu.cn>
Xinyang Jiang <sevjiang@tencent.com>
Fufu Yu <fufuyu@tencent.com>
Enwei Zhang <miyozhang@tencent.com>
Copyright (C) 2020-2021, Tencent.
Copyright (C) 2020-2021, SUSTech.
Copyright (C) 2024, Bigvision LLC.
How to use:
sample command to run:
`python person_reid.py`
You can download ReID model using
`python download_models.py reid`
and yolo model using:
`python download_models.py yolov8`
Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
'''
import argparse
import os.path
import numpy as np
import cv2 as cv
from common import *
def help():
print(
'''
Use this script for Person Re-identification using OpenCV.
Firstly, download required models i.e. reid and yolov8 using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
To run:
Example: python person_reid.py reid
Re-identification model path can also be specified using --model argument and detection model can be specified using --yolo_model argument.
'''
)
def get_args_parser():
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--query', '-q', help='Path to target image. Skip this argument to select target in the video frame.')
parser.add_argument('--input', '-i', default=0, help='Path to video file.', required=False)
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'person_reid', prefix="", alias="reid")
add_preproc_args(args.zoo, parser, 'person_reid', prefix="yolo_", alias="reid")
parser = argparse.ArgumentParser(parents=[parser],
description='Person Re-identification using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
return parser.parse_args()
img_dict = {} # Dictionary to store bounding boxes for corresponding cropped image
def yolo_detector(frame, net):
global img_dict
height, width, _ = frame.shape
length = max((height, width))
image = np.zeros((length, length, 3), np.uint8)
image[0:height, 0:width] = frame
scale = length/args.yolo_width
# Create blob from the frame with correct scale factor and size for the model
blob = cv.dnn.blobFromImage(image, scalefactor=args.yolo_scale, size=(args.yolo_width, args.yolo_height), swapRB=args.yolo_rgb)
net.setInput(blob)
outputs = net.forward()
outputs = np.array([cv.transpose(outputs[0])])
rows = outputs.shape[1]
boxes = []
scores = []
class_ids = []
for i in range(rows):
classes_scores = outputs[0][i][4:]
(_, maxScore, _, (x, maxClassIndex)) = cv.minMaxLoc(classes_scores)
if maxScore >= 0.25:
box = [
outputs[0][i][0] - (0.5 * outputs[0][i][2]),
outputs[0][i][1] - (0.5 * outputs[0][i][3]),
outputs[0][i][2],
outputs[0][i][3],
]
boxes.append(box)
scores.append(maxScore)
class_ids.append(maxClassIndex)
# Apply Non-Maximum Suppression
indexes = cv.dnn.NMSBoxes(boxes, scores, 0.25, 0.45, 0.5)
images = []
for i in indexes:
x, y, w, h = boxes[i]
x = round(x*scale)
y = round(y*scale)
w = round(w*scale)
h = round(h*scale)
x, y = max(0, x), max(0, y)
w, h = min(w, frame.shape[1] - x), min(h, frame.shape[0] - y)
crop_img = frame[y:y+h, x:x+w]
images.append(crop_img)
img_dict[crop_img.tobytes()] = (x, y, w, h)
return images
def extract_feature(images, net):
"""
Extract features from images
:param images: the input images
:param net: the model network
"""
feat_list = []
# net = reid_net.copy()
for img in images:
blob = cv.dnn.blobFromImage(img, scalefactor=args.scale, size=(args.width, args.height), mean=args.mean, swapRB=args.rgb, crop=False, ddepth=cv.CV_32F)
for j in range(blob.shape[1]):
blob[:, j, :, :] /= args.std[j]
net.setInput(blob)
feat = net.forward()
feat = np.reshape(feat, (feat.shape[0], feat.shape[1]))
feat_list.append(feat)
feats = np.concatenate(feat_list, axis = 0)
return feats
def find_matching(query_feat, gallery_feat):
"""
Return the index of the gallery image most similar to the query image
:param query_feat: array of feature vectors of query images
:param gallery_feat: array of feature vectors of gallery images
"""
cv.normalize(query_feat, query_feat, 1.0, 0.0, cv.NORM_L2)
cv.normalize(gallery_feat, gallery_feat, 1.0, 0.0, cv.NORM_L2)
sim = query_feat.dot(gallery_feat.T)
index = np.argmax(sim, axis=1)[0]
return index
def main():
if hasattr(args, 'help'):
help()
exit(1)
args.model = findModel(args.model, args.sha1)
if args.yolo_model is None:
print("[ERROR] Please pass path to yolov8.onnx model file using --yolo_model.")
exit(1)
else:
args.yolo_model = findModel(args.yolo_model, args.yolo_sha1)
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
yolo_net = cv.dnn.readNetFromONNX(args.yolo_model, engine)
reid_net = cv.dnn.readNetFromONNX(args.model, engine)
reid_net.setPreferableBackend(get_backend_id(args.backend))
reid_net.setPreferableTarget(get_target_id(args.target))
cap = cv.VideoCapture(cv.samples.findFile(args.input) if args.input else 0)
query_images = []
stdSize = 0.6
stdWeight = 2
stdImgSize = 512
imgWidth = -1 # Initialization
fontSize = 1.5
fontThickness = 1
if args.query:
query_images = [cv.imread(findFile(args.query))]
else:
while True:
ret, image = cap.read()
if not ret:
print("Error reading the video")
return -1
if imgWidth == -1:
imgWidth = min(image.shape[:2])
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize)
fontThickness = max(fontThickness,(stdWeight*imgWidth)//stdImgSize)
label = "Press space bar to pause video to draw bounding box."
labelSize, _ = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
cv.rectangle(image, (0, 0), (labelSize[0]+10, labelSize[1]+int(30*fontSize)), (255,255,255), cv.FILLED)
cv.putText(image, label, (10, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.putText(image, "Press space bar after selecting.", (10, int(50*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.imshow('TRACKING', image)
key = cv.waitKey(100) & 0xFF
if key == ord(' '):
rect = cv.selectROI("TRACKING", image)
if rect:
x, y, w, h = rect
query_image = image[y:y + h, x:x + w]
query_images = [query_image]
break
if key == ord('q') or key == 27:
return
query_feat = extract_feature(query_images, reid_net)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
if imgWidth == -1:
imgWidth = min(frame.shape[:2])
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize)
fontThickness = max(fontThickness,(stdWeight*imgWidth)//stdImgSize)
images = yolo_detector(frame, yolo_net)
gallery_feat = extract_feature(images, reid_net)
match_idx = find_matching(query_feat, gallery_feat)
match_img = images[match_idx]
x, y, w, h = img_dict[match_img.tobytes()]
cv.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv.putText(frame, "Target", (x, y - 10), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 255), fontThickness)
label="Tracking"
labelSize, _ = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
cv.rectangle(frame, (0, 0), (labelSize[0]+10, labelSize[1]+10), (255,255,255), cv.FILLED)
cv.putText(frame, label, (10, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.imshow("TRACKING", frame)
if cv.waitKey(1) & 0xFF in [ord('q'), 27]:
break
cap.release()
cv.destroyAllWindows()
return
if __name__ == '__main__':
args = get_args_parser()
main()
+134
View File
@@ -0,0 +1,134 @@
'''
This is a sample script to run Qwen2.5 inference in OpenCV using ONNX model.
The script loads the Qwen2.5 model and runs inference on a given prompt using
the ChatML format (<|im_start|> / <|im_end|> special tokens).
Model: https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct
Exporting Qwen2.5 model to ONNX:
1. Install the required dependencies:
pip install optimum[exporters] optimum-onnx[onnxruntime] torch transformers
2. Export the model to ONNX:
Without KV-cache:
optimum-cli export onnx --model Qwen/Qwen2.5-0.5B-Instruct --task causal-lm qwen2.5_instruct_onnx/
With KV-cache (recommended, faster autoregressive inference):
optimum-cli export onnx --model Qwen/Qwen2.5-0.5B-Instruct --task causal-lm-with-past qwen2.5_instruct_onnx_with_past/
Run the script:
1. Install the required dependencies:
pip install numpy
2. Run the script:
Without KV-cache (causal-lm export):
python qwen_inference.py --model=<path-to-onnx-model> \
--tokenizer_path=<path-to-qwen2.5-config.json> \
--prompt="What is OpenCV?"
With KV-cache (causal-lm-with-past export):
python qwen_inference.py --model=<path-to-onnx-model> \
--tokenizer_path=<path-to-qwen2.5-config.json> \
--prompt="What is OpenCV?" \
--use_kv_cache
'''
import numpy as np
import argparse
import cv2 as cv
def parse_args():
parser = argparse.ArgumentParser(description='Use this script to run Qwen2.5 inference in OpenCV',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--model', type=str, required=True, help='Path to Qwen2.5 ONNX model file.')
parser.add_argument('--tokenizer_path', type=str, required=True, help='Path to Qwen2.5 tokenizer config.json.')
parser.add_argument('--prompt', type=str, default='What is OpenCV?', help='User prompt.')
parser.add_argument('--max_new_tokens', type=int, default=64, help='Maximum number of new tokens to generate.')
parser.add_argument('--use_kv_cache', action='store_true', default=False, help='Enable KV-cache for faster inference (requires causal-lm-with-past export).')
parser.add_argument('--seed', type=int, default=0, help='Random seed.')
return parser.parse_args()
def build_chatml_prompt(user_prompt):
'''Wrap user prompt in Qwen2.5 ChatML format.'''
return '<|im_start|>user\n' + user_prompt + '<|im_end|>\n<|im_start|>assistant\n'
def qwen_inference(net, prompt, max_new_tokens, tokenizer, use_kv_cache=True):
print("Inferencing Qwen2.5 model...")
tokens = list(tokenizer.encode(prompt))
input_ids = np.array(tokens, dtype=np.int64).reshape(1, -1)
# Qwen2.5 special token IDs
im_end_id = 151645 # <|im_end|>
eos_id = 151643 # <|endoftext|>
stop_ids = (im_end_id, eos_id)
generated = []
if use_kv_cache:
net.enableKVCache()
prompt_len = input_ids.shape[1]
# Prefill: process full prompt once to populate KV-cache
net.setInput(input_ids, 'input_ids')
net.setInput(np.ones((1, prompt_len), dtype=np.int64), 'attention_mask')
net.setInput(np.arange(prompt_len, dtype=np.int64).reshape(1, -1), 'position_ids')
logits = net.forward()
new_id = int(np.argmax(logits[:, -1, :].reshape(-1)))
generated = [new_id]
# Generate: feed one new token per step; OpenCV routes present.* -> past_key_values.*
for _ in range(max_new_tokens - 1):
if new_id in stop_ids:
break
cur_len = prompt_len + len(generated)
net.setInput(np.array([[new_id]], dtype=np.int64), 'input_ids')
net.setInput(np.ones((1, cur_len), dtype=np.int64), 'attention_mask')
net.setInput(np.array([[cur_len - 1]], dtype=np.int64), 'position_ids')
logits = net.forward()
new_id = int(np.argmax(logits[:, -1, :].reshape(-1)))
generated.append(new_id)
else:
# Without KV-cache: feed full growing sequence each step
for _ in range(max_new_tokens):
seq_len = input_ids.shape[1]
net.setInput(input_ids, 'input_ids')
net.setInput(np.ones((1, seq_len), dtype=np.int64), 'attention_mask')
net.setInput(np.arange(seq_len, dtype=np.int64).reshape(1, -1), 'position_ids')
logits = net.forward()
new_id = int(np.argmax(logits[:, -1, :].reshape(-1)))
if new_id in stop_ids:
break
generated.append(new_id)
input_ids = np.concatenate([input_ids, [[new_id]]], axis=1)
return np.array([tokens + generated], dtype=np.int64)
if __name__ == '__main__':
args = parse_args()
np.random.seed(args.seed)
print("Preparing Qwen2.5 model...")
tokenizer = cv.dnn.Tokenizer.load(args.tokenizer_path)
net = cv.dnn.readNetFromONNX(args.model, cv.dnn.ENGINE_NEW)
chatml_prompt = build_chatml_prompt(args.prompt)
print(f"Prompt:\n{chatml_prompt}")
prompt_len = len(tokenizer.encode(chatml_prompt))
tokens = qwen_inference(net, chatml_prompt, args.max_new_tokens, tokenizer, args.use_kv_cache)
response = tokenizer.decode(tokens[0][prompt_len:].tolist())
print(f"Response:\n{response}")
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+314
View File
@@ -0,0 +1,314 @@
#include <fstream>
#include <sstream>
#include <iostream>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>
#include "common.hpp"
using namespace cv;
using namespace std;
using namespace dnn;
const string about =
"Use this script to run semantic segmentation deep learning networks using OpenCV.\n\n"
"Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
"To run:\n"
"\t ./example_dnn_classification modelName(e.g. u2netp) --input=$OPENCV_SAMPLES_DATA_PATH/butterfly.jpg (or ignore this argument to use device camera)\n"
"Model path can also be specified using --model argument.";
const string param_keys =
"{ help h | | Print help message. }"
"{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ device | 0 | camera device number. }"
"{ input i | | Path to input image or video file. Skip this argument to capture frames from a camera. }"
"{ colors | | Optional path to a text file with colors for an every class. "
"Every color is represented with three values from 0 to 255 in BGR channels order. }";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
string keys = param_keys + backend_keys + target_keys;
vector<string> labels;
vector<Vec3b> colors;
static void colorizeSegmentation(const Mat &score, Mat &segm)
{
const int rows = score.size[2];
const int cols = score.size[3];
const int chns = score.size[1];
if (colors.empty())
{
// Generate colors.
colors.push_back(Vec3b());
for (int i = 1; i < chns; ++i)
{
Vec3b color;
for (int j = 0; j < 3; ++j)
color[j] = (colors[i - 1][j] + rand() % 256) / 2;
colors.push_back(color);
}
}
else if (chns != (int)colors.size())
{
CV_Error(Error::StsError, format("Number of output labels does not match "
"number of colors (%d != %zu)",
chns, colors.size()));
}
Mat maxCl = Mat::zeros(rows, cols, CV_8UC1);
Mat maxVal(rows, cols, CV_32FC1, score.data);
for (int ch = 1; ch < chns; ch++)
{
for (int row = 0; row < rows; row++)
{
const float *ptrScore = score.ptr<float>(0, ch, row);
uint8_t *ptrMaxCl = maxCl.ptr<uint8_t>(row);
float *ptrMaxVal = maxVal.ptr<float>(row);
for (int col = 0; col < cols; col++)
{
if (ptrScore[col] > ptrMaxVal[col])
{
ptrMaxVal[col] = ptrScore[col];
ptrMaxCl[col] = (uchar)ch;
}
}
}
}
segm.create(rows, cols, CV_8UC3);
for (int row = 0; row < rows; row++)
{
const uchar *ptrMaxCl = maxCl.ptr<uchar>(row);
Vec3b *ptrSegm = segm.ptr<Vec3b>(row);
for (int col = 0; col < cols; col++)
{
ptrSegm[col] = colors[ptrMaxCl[col]];
}
}
}
static void showLegend(FontFace fontFace)
{
static const int kBlockHeight = 30;
static Mat legend;
if (legend.empty())
{
const int numClasses = (int)labels.size();
if ((int)colors.size() != numClasses)
{
CV_Error(Error::StsError, format("Number of output labels does not match "
"number of labels (%zu != %zu)",
colors.size(), labels.size()));
}
legend.create(kBlockHeight * numClasses, 200, CV_8UC3);
for (int i = 0; i < numClasses; i++)
{
Mat block = legend.rowRange(i * kBlockHeight, (i + 1) * kBlockHeight);
block.setTo(colors[i]);
Rect r = getTextSize(Size(), labels[i], Point(), fontFace, 15, 400);
r.height += 15; // padding
r.width += 10; // padding
rectangle(block, r, Scalar::all(255), FILLED);
putText(block, labels[i], Point(10, kBlockHeight/2), Scalar(0,0,0), fontFace, 15, 400);
}
namedWindow("Legend", WINDOW_AUTOSIZE);
imshow("Legend", legend);
}
}
int main(int argc, char **argv)
{
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_INFO);
CommandLineParser parser(argc, argv, keys);
const string modelName = parser.get<String>("@alias");
const string zooFile = findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
parser.about(about);
if (!parser.has("@alias") || parser.has("help"))
{
parser.printMessage();
return 0;
}
string sha1 = parser.get<String>("sha1");
float scale = parser.get<float>("scale");
Scalar mean = parser.get<Scalar>("mean");
bool swapRB = parser.get<bool>("rgb");
int inpWidth = parser.get<int>("width");
int inpHeight = parser.get<int>("height");
String model = findModel(parser.get<String>("model"), sha1);
const string backend = parser.get<String>("backend");
const string target = parser.get<String>("target");
int stdSize = 20;
int stdWeight = 400;
int stdImgSize = 512;
int imgWidth = -1; // Initialization
int fontSize = 50;
int fontWeight = 500;
FontFace fontFace("sans");
// Open file with labels names.
if (parser.has("labels"))
{
string file = findFile(parser.get<String>("labels"));
ifstream ifs(file.c_str());
if (!ifs.is_open())
CV_Error(Error::StsError, "File " + file + " not found");
string line;
while (getline(ifs, line))
{
labels.push_back(line);
}
}
// Open file with colors.
if (parser.has("colors"))
{
string file = findFile(parser.get<String>("colors"));
ifstream ifs(file.c_str());
if (!ifs.is_open())
CV_Error(Error::StsError, "File " + file + " not found");
string line;
while (getline(ifs, line))
{
istringstream colorStr(line.c_str());
Vec3b color;
for (int i = 0; i < 3 && !colorStr.eof(); ++i)
colorStr >> color[i];
colors.push_back(color);
}
}
if (!parser.check())
{
parser.printErrors();
return 1;
}
CV_Assert(!model.empty());
//! [Read and initialize network]
EngineType engine = ENGINE_AUTO;
if (backend != "default" || target != "cpu"){
engine = ENGINE_CLASSIC;
}
Net net = readNetFromONNX(model, engine);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
net.setProfilingMode(DNN_PROFILE_SUMMARY);
//! [Read and initialize network]
// Create a window
static const string kWinName = "Deep learning semantic segmentation in OpenCV";
namedWindow(kWinName, WINDOW_AUTOSIZE);
//! [Open a video file or an image file or a camera stream]
VideoCapture cap;
if (parser.has("input"))
cap.open(findFile(parser.get<String>("input")));
else
cap.open(parser.get<int>("device"));
if (!cap.isOpened()) {
cerr << "Error: Video could not be opened." << endl;
return -1;
}
//! [Open a video file or an image file or a camera stream]
// Process frames.
Mat frame, blob;
while (waitKey(1) < 0)
{
cap >> frame;
if (frame.empty())
{
waitKey();
break;
}
if (imgWidth == -1){
imgWidth = max(frame.rows, frame.cols);
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize);
fontWeight = min(fontWeight, (stdWeight*imgWidth)/stdImgSize);
}
imshow("Original Image", frame);
//! [Create a 4D blob from a frame]
blobFromImage(frame, blob, scale, Size(inpWidth, inpHeight), mean, swapRB, false);
//! [Set input blob]
net.setInput(blob);
//! [Set input blob]
int64 t0 = getTickCount();
if (modelName == "u2netp")
{
vector<Mat> output;
net.forward(output, net.getUnconnectedOutLayersNames());
net.printPerfProfile();
Mat pred = output[0].reshape(1, output[0].size[2]);
pred.convertTo(pred, CV_8U, 255.0);
Mat mask;
resize(pred, mask, Size(frame.cols, frame.rows), 0, 0, INTER_AREA);
// Create overlays for foreground and background
Mat foreground_overlay;
// Set foreground (object) to red
Mat all_zeros = Mat::zeros(frame.size(), CV_8UC1);
vector<Mat> channels = {all_zeros, all_zeros, mask};
merge(channels, foreground_overlay);
// Blend the overlays with the original frame
addWeighted(frame, 0.25, foreground_overlay, 0.75, 0, frame);
}
else
{
//! [Make forward pass]
Mat score = net.forward();
net.printPerfProfile();
//! [Make forward pass]
Mat segm;
colorizeSegmentation(score, segm);
resize(segm, segm, frame.size(), 0, 0, INTER_NEAREST);
addWeighted(frame, 0.1, segm, 0.9, 0.0, frame);
}
// Put efficiency information.
double t = (getTickCount() - t0) * 1000.0 / getTickFrequency();
string label = format("Inference time: %.2f ms", t);
Rect r = getTextSize(Size(), label, Point(), fontFace, fontSize, fontWeight);
r.height += fontSize; // padding
r.width += 10; // padding
rectangle(frame, r, Scalar::all(255), FILLED);
putText(frame, label, Point(10, fontSize), Scalar(0,0,0), fontFace, fontSize, fontWeight);
imshow(kWinName, frame);
if (!labels.empty())
showLegend(fontFace);
}
return 0;
}
+184
View File
@@ -0,0 +1,184 @@
import cv2 as cv
import argparse
import numpy as np
from common import *
def help():
print(
'''
Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"\n
To run:
python segmentation.py model_name(e.g. u2netp) --input=path/to/your/input/image/or/video (don't give --input flag if want to use device camera)
Model path can also be specified using --model argument
'''
)
def get_args_parser(func_args):
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = ("cpu", "opencl", "opencl_fp16", "ncs2_vpu", "hddl_vpu", "vulkan", "cuda", "cuda_fp16")
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--input', help='Path to input image or video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--colors', help='Optional path to a text file with colors for an every class. '
'An every color is represented with three values from 0 to 255 in BGR channels order.')
parser.add_argument('--backend', default="default", type=str, choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN")
parser.add_argument('--target', default="cpu", type=str, choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)")
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'segmentation')
parser = argparse.ArgumentParser(parents=[parser],
description='Use this script to run semantic segmentation deep learning networks using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
return parser.parse_args(func_args)
def showLegend(labels, colors, legend):
if not labels is None and legend is None:
blockHeight = 30
assert(len(labels) == len(colors))
legend = np.zeros((blockHeight * len(colors), 200, 3), np.uint8)
for i in range(len(labels)):
block = legend[i * blockHeight:(i + 1) * blockHeight]
block[:,:] = colors[i]
cv.putText(block, labels[i], (0, blockHeight//2), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0))
cv.namedWindow('Legend', cv.WINDOW_AUTOSIZE)
cv.imshow('Legend', legend)
labels = None
def main(func_args=None):
args = get_args_parser(func_args)
if args.alias is None or hasattr(args, 'help'):
help()
exit(1)
cv.utils.logging.setLogLevel(cv.utils.logging.LOG_LEVEL_INFO)
args.model = findModel(args.model, args.sha1)
if args.labels is not None:
args.labels = findFile(args.labels)
np.random.seed(324)
stdSize = 0.8
stdWeight = 2
stdImgSize = 512
imgWidth = -1 # Initialization
fontSize = 1.5
fontThickness = 1
# Load names of labels
labels = None
if args.labels:
with open(args.labels, 'rt') as f:
labels = f.read().rstrip('\n').split('\n')
# Load colors
colors = None
if args.colors:
with open(args.colors, 'rt') as f:
colors = [np.array(color.split(' '), np.uint8) for color in f.read().rstrip('\n').split('\n')]
# Load a network
engine = cv.dnn.ENGINE_AUTO
if args.backend != "default" or args.target != "cpu":
engine = cv.dnn.ENGINE_CLASSIC
net = cv.dnn.readNetFromONNX(args.model, engine)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
if hasattr(cv.dnn, 'DNN_PROFILE_SUMMARY'):
net.setProfilingMode(cv.dnn.DNN_PROFILE_SUMMARY)
winName = 'Deep learning semantic segmentation in OpenCV'
cv.namedWindow(winName, cv.WINDOW_AUTOSIZE)
cap = cv.VideoCapture(cv.samples.findFile(args.input) if args.input else 0)
if not cap.isOpened():
print("Failed to open the input video")
exit(-1)
legend = None
while cv.waitKey(1) < 0:
hasFrame, frame = cap.read()
if not hasFrame:
cv.waitKey()
break
if imgWidth == -1:
imgWidth = max(frame.shape[:2])
fontSize = min(fontSize, (stdSize*imgWidth)/stdImgSize)
fontThickness = max(fontThickness,(stdWeight*imgWidth)//stdImgSize)
cv.imshow("Original Image", frame)
frameHeight = frame.shape[0]
frameWidth = frame.shape[1]
# Create a 4D blob from a frame.
inpWidth = args.width if args.width else frameWidth
inpHeight = args.height if args.height else frameHeight
blob = cv.dnn.blobFromImage(frame, args.scale, (inpWidth, inpHeight), args.mean, args.rgb, crop=False)
net.setInput(blob)
t0 = cv.getTickCount()
if args.alias == 'u2netp':
output = net.forward(net.getUnconnectedOutLayersNames())
net.printPerfProfile()
pred = output[0][0, 0, :, :]
mask = (pred * 255).astype(np.uint8)
mask = cv.resize(mask, (frame.shape[1], frame.shape[0]), interpolation=cv.INTER_AREA)
# Create overlays for foreground and background
foreground_overlay = np.zeros_like(frame, dtype=np.uint8)
# Set foreground (object) to red and background to blue
foreground_overlay[:, :, 2] = mask # Red foreground
# Blend the overlays with the original frame
frame = cv.addWeighted(frame, 0.25, foreground_overlay, 0.75, 0)
else:
score = net.forward()
net.printPerfProfile()
numClasses = score.shape[1]
height = score.shape[2]
width = score.shape[3]
# Draw segmentation
if not colors:
# Generate colors
colors = [np.array([0, 0, 0], np.uint8)]
for i in range(1, numClasses):
colors.append((colors[i - 1] + np.random.randint(0, 256, [3], np.uint8)) / 2)
classIds = np.argmax(score[0], axis=0)
segm = np.stack([colors[idx] for idx in classIds.flatten()])
segm = segm.reshape(height, width, 3)
segm = cv.resize(segm, (frameWidth, frameHeight), interpolation=cv.INTER_NEAREST)
frame = (0.1 * frame + 0.9 * segm).astype(np.uint8)
showLegend(labels, colors, legend)
label = 'Inference time: %.2f ms' % ((cv.getTickCount() - t0) * 1000.0 / cv.getTickFrequency())
labelSize, _ = cv.getTextSize(label, cv.FONT_HERSHEY_SIMPLEX, fontSize, fontThickness)
cv.rectangle(frame, (0, 0), (labelSize[0]+10, labelSize[1]), (255,255,255), cv.FILLED)
cv.putText(frame, label, (10, int(25*fontSize)), cv.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
cv.imshow(winName, frame)
if __name__ == "__main__":
main()
+62
View File
@@ -0,0 +1,62 @@
# This file is part of OpenCV project.
# It is subject to the license terms in the LICENSE file found in the top-level directory
# of this distribution and at http://opencv.org/license.html.
#
# Copyright (C) 2017, Intel Corporation, all rights reserved.
# Third party copyrights are property of their respective owners.
import tensorflow as tf
import struct
import argparse
import numpy as np
parser = argparse.ArgumentParser(description='Convert weights of a frozen TensorFlow graph to fp16.')
parser.add_argument('--input', required=True, help='Path to frozen graph.')
parser.add_argument('--output', required=True, help='Path to output graph.')
parser.add_argument('--ops', default=['Conv2D', 'MatMul'], nargs='+',
help='List of ops which weights are converted.')
args = parser.parse_args()
DT_FLOAT = 1
DT_HALF = 19
# For the frozen graphs, an every node that uses weights connected to Const nodes
# through an Identity node. Usually they're called in the same way with '/read' suffix.
# We'll replace all of them to Cast nodes.
# Load the model
with tf.gfile.FastGFile(args.input) as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
# Set of all inputs from desired nodes.
inputs = []
for node in graph_def.node:
if node.op in args.ops:
inputs += node.input
weightsNodes = []
for node in graph_def.node:
# From the whole inputs we need to keep only an Identity nodes.
if node.name in inputs and node.op == 'Identity' and node.attr['T'].type == DT_FLOAT:
weightsNodes.append(node.input[0])
# Replace Identity to Cast.
node.op = 'Cast'
node.attr['DstT'].type = DT_FLOAT
node.attr['SrcT'].type = DT_HALF
del node.attr['T']
del node.attr['_class']
# Convert weights to halfs.
for node in graph_def.node:
if node.name in weightsNodes:
node.attr['dtype'].type = DT_HALF
node.attr['value'].tensor.dtype = DT_HALF
floats = node.attr['value'].tensor.tensor_content
floats = struct.unpack('f' * (len(floats) / 4), floats)
halfs = np.array(floats).astype(np.float16).view(np.uint16)
node.attr['value'].tensor.tensor_content = struct.pack('H' * len(halfs), *halfs)
tf.train.write_graph(graph_def, "", args.output, as_text=False)
+403
View File
@@ -0,0 +1,403 @@
import argparse
import cv2 as cv
import numpy as np
import os
"""
Link to original paper : https://arxiv.org/abs/1812.11703
Link to original repo : https://github.com/STVIR/pysot
You can download the pre-trained weights of the Tracker Model from https://drive.google.com/file/d/11bwgPFVkps9AH2NOD1zBDdpF_tQghAB-/view?usp=sharing
You can download the target net (target branch of SiamRPN++) from https://drive.google.com/file/d/1dw_Ne3UMcCnFsaD6xkZepwE4GEpqq7U_/view?usp=sharing
You can download the search net (search branch of SiamRPN++) from https://drive.google.com/file/d/1Lt4oE43ZSucJvze3Y-Z87CVDreO-Afwl/view?usp=sharing
You can download the head model (RPN Head) from https://drive.google.com/file/d/1zT1yu12mtj3JQEkkfKFJWiZ71fJ-dQTi/view?usp=sharing
"""
class ModelBuilder():
""" This class generates the SiamRPN++ Tracker Model by using Imported ONNX Nets
"""
def __init__(self, target_net, search_net, rpn_head):
super(ModelBuilder, self).__init__()
# Build the target branch
self.target_net = target_net
# Build the search branch
self.search_net = search_net
# Build RPN_Head
self.rpn_head = rpn_head
def template(self, z):
""" Takes the template of size (1, 1, 127, 127) as an input to generate kernel
"""
self.target_net.setInput(z)
outNames = self.target_net.getUnconnectedOutLayersNames()
self.zfs_1, self.zfs_2, self.zfs_3 = self.target_net.forward(outNames)
def track(self, x):
""" Takes the search of size (1, 1, 255, 255) as an input to generate classification score and bounding box regression
"""
self.search_net.setInput(x)
outNames = self.search_net.getUnconnectedOutLayersNames()
xfs_1, xfs_2, xfs_3 = self.search_net.forward(outNames)
self.rpn_head.setInput(np.stack([self.zfs_1, self.zfs_2, self.zfs_3]), 'input_1')
self.rpn_head.setInput(np.stack([xfs_1, xfs_2, xfs_3]), 'input_2')
outNames = self.rpn_head.getUnconnectedOutLayersNames()
cls, loc = self.rpn_head.forward(outNames)
return {'cls': cls, 'loc': loc}
class Anchors:
""" This class generate anchors.
"""
def __init__(self, stride, ratios, scales, image_center=0, size=0):
self.stride = stride
self.ratios = ratios
self.scales = scales
self.image_center = image_center
self.size = size
self.anchor_num = len(self.scales) * len(self.ratios)
self.anchors = self.generate_anchors()
def generate_anchors(self):
"""
generate anchors based on predefined configuration
"""
anchors = np.zeros((self.anchor_num, 4), dtype=np.float32)
size = self.stride**2
count = 0
for r in self.ratios:
ws = int(np.sqrt(size * 1. / r))
hs = int(ws * r)
for s in self.scales:
w = ws * s
h = hs * s
anchors[count][:] = [-w * 0.5, -h * 0.5, w * 0.5, h * 0.5][:]
count += 1
return anchors
class SiamRPNTracker:
def __init__(self, model):
super(SiamRPNTracker, self).__init__()
self.anchor_stride = 8
self.anchor_ratios = [0.33, 0.5, 1, 2, 3]
self.anchor_scales = [8]
self.track_base_size = 8
self.track_context_amount = 0.5
self.track_exemplar_size = 127
self.track_instance_size = 255
self.track_lr = 0.4
self.track_penalty_k = 0.04
self.track_window_influence = 0.44
self.score_size = (self.track_instance_size - self.track_exemplar_size) // \
self.anchor_stride + 1 + self.track_base_size
self.anchor_num = len(self.anchor_ratios) * len(self.anchor_scales)
hanning = np.hanning(self.score_size)
window = np.outer(hanning, hanning)
self.window = np.tile(window.flatten(), self.anchor_num)
self.anchors = self.generate_anchor(self.score_size)
self.model = model
def get_subwindow(self, im, pos, model_sz, original_sz, avg_chans):
"""
Args:
im: bgr based input image frame
pos: position of the center of the frame
model_sz: exemplar / target image size
s_z: original / search image size
avg_chans: channel average
Return:
im_patch: sub_windows for the given image input
"""
if isinstance(pos, float):
pos = [pos, pos]
sz = original_sz
im_h, im_w, im_d = im.shape
c = (original_sz + 1) / 2
cx, cy = pos
context_xmin = np.floor(cx - c + 0.5)
context_xmax = context_xmin + sz - 1
context_ymin = np.floor(cy - c + 0.5)
context_ymax = context_ymin + sz - 1
left_pad = int(max(0., -context_xmin))
top_pad = int(max(0., -context_ymin))
right_pad = int(max(0., context_xmax - im_w + 1))
bottom_pad = int(max(0., context_ymax - im_h + 1))
context_xmin += left_pad
context_xmax += left_pad
context_ymin += top_pad
context_ymax += top_pad
if any([top_pad, bottom_pad, left_pad, right_pad]):
size = (im_h + top_pad + bottom_pad, im_w + left_pad + right_pad, im_d)
te_im = np.zeros(size, np.uint8)
te_im[top_pad:top_pad + im_h, left_pad:left_pad + im_w, :] = im
if top_pad:
te_im[0:top_pad, left_pad:left_pad + im_w, :] = avg_chans
if bottom_pad:
te_im[im_h + top_pad:, left_pad:left_pad + im_w, :] = avg_chans
if left_pad:
te_im[:, 0:left_pad, :] = avg_chans
if right_pad:
te_im[:, im_w + left_pad:, :] = avg_chans
im_patch = te_im[int(context_ymin):int(context_ymax + 1),
int(context_xmin):int(context_xmax + 1), :]
else:
im_patch = im[int(context_ymin):int(context_ymax + 1),
int(context_xmin):int(context_xmax + 1), :]
if not np.array_equal(model_sz, original_sz):
im_patch = cv.resize(im_patch, (model_sz, model_sz))
im_patch = im_patch.transpose(2, 0, 1)
im_patch = im_patch[np.newaxis, :, :, :]
im_patch = im_patch.astype(np.float32)
return im_patch
def generate_anchor(self, score_size):
"""
Args:
im: bgr based input image frame
pos: position of the center of the frame
model_sz: exemplar / target image size
s_z: original / search image size
avg_chans: channel average
Return:
anchor: anchors for pre-determined values of stride, ratio, and scale
"""
anchors = Anchors(self.anchor_stride, self.anchor_ratios, self.anchor_scales)
anchor = anchors.anchors
x1, y1, x2, y2 = anchor[:, 0], anchor[:, 1], anchor[:, 2], anchor[:, 3]
anchor = np.stack([(x1 + x2) * 0.5, (y1 + y2) * 0.5, x2 - x1, y2 - y1], 1)
total_stride = anchors.stride
anchor_num = anchors.anchor_num
anchor = np.tile(anchor, score_size * score_size).reshape((-1, 4))
ori = - (score_size // 2) * total_stride
xx, yy = np.meshgrid([ori + total_stride * dx for dx in range(score_size)],
[ori + total_stride * dy for dy in range(score_size)])
xx, yy = np.tile(xx.flatten(), (anchor_num, 1)).flatten(), \
np.tile(yy.flatten(), (anchor_num, 1)).flatten()
anchor[:, 0], anchor[:, 1] = xx.astype(np.float32), yy.astype(np.float32)
return anchor
def _convert_bbox(self, delta, anchor):
"""
Args:
delta: localisation
anchor: anchor of pre-determined anchor size
Return:
delta: prediction of bounding box
"""
delta_transpose = np.transpose(delta, (1, 2, 3, 0))
delta_contig = np.ascontiguousarray(delta_transpose)
delta = delta_contig.reshape(4, -1)
delta[0, :] = delta[0, :] * anchor[:, 2] + anchor[:, 0]
delta[1, :] = delta[1, :] * anchor[:, 3] + anchor[:, 1]
delta[2, :] = np.exp(delta[2, :]) * anchor[:, 2]
delta[3, :] = np.exp(delta[3, :]) * anchor[:, 3]
return delta
def _softmax(self, x):
"""
Softmax in the direction of the depth of the layer
"""
x = x.astype(dtype=np.float32)
x_max = x.max(axis=1)[:, np.newaxis]
e_x = np.exp(x-x_max)
div = np.sum(e_x, axis=1)[:, np.newaxis]
y = e_x / div
return y
def _convert_score(self, score):
"""
Args:
cls: score
Return:
cls: score for cls
"""
score_transpose = np.transpose(score, (1, 2, 3, 0))
score_con = np.ascontiguousarray(score_transpose)
score_view = score_con.reshape(2, -1)
score = np.transpose(score_view, (1, 0))
score = self._softmax(score)
return score[:,1]
def _bbox_clip(self, cx, cy, width, height, boundary):
"""
Adjusting the bounding box
"""
bbox_h, bbox_w = boundary
cx = max(0, min(cx, bbox_w))
cy = max(0, min(cy, bbox_h))
width = max(10, min(width, bbox_w))
height = max(10, min(height, bbox_h))
return cx, cy, width, height
def init(self, img, bbox):
"""
Args:
img(np.ndarray): bgr based input image frame
bbox: (x, y, w, h): bounding box
"""
x, y, w, h = bbox
self.center_pos = np.array([x + (w - 1) / 2, y + (h - 1) / 2])
self.h = h
self.w = w
w_z = self.w + self.track_context_amount * np.add(h, w)
h_z = self.h + self.track_context_amount * np.add(h, w)
s_z = round(np.sqrt(w_z * h_z))
self.channel_average = np.mean(img, axis=(0, 1))
z_crop = self.get_subwindow(img, self.center_pos, self.track_exemplar_size, s_z, self.channel_average)
self.model.template(z_crop)
def track(self, img):
"""
Args:
img(np.ndarray): BGR image
Return:
bbox(list):[x, y, width, height]
"""
w_z = self.w + self.track_context_amount * np.add(self.w, self.h)
h_z = self.h + self.track_context_amount * np.add(self.w, self.h)
s_z = np.sqrt(w_z * h_z)
scale_z = self.track_exemplar_size / s_z
s_x = s_z * (self.track_instance_size / self.track_exemplar_size)
x_crop = self.get_subwindow(img, self.center_pos, self.track_instance_size, round(s_x), self.channel_average)
outputs = self.model.track(x_crop)
score = self._convert_score(outputs['cls'])
pred_bbox = self._convert_bbox(outputs['loc'], self.anchors)
def change(r):
return np.maximum(r, 1. / r)
def sz(w, h):
pad = (w + h) * 0.5
return np.sqrt((w + pad) * (h + pad))
# scale penalty
s_c = change(sz(pred_bbox[2, :], pred_bbox[3, :]) /
(sz(self.w * scale_z, self.h * scale_z)))
# aspect ratio penalty
r_c = change((self.w / self.h) /
(pred_bbox[2, :] / pred_bbox[3, :]))
penalty = np.exp(-(r_c * s_c - 1) * self.track_penalty_k)
pscore = penalty * score
# window penalty
pscore = pscore * (1 - self.track_window_influence) + \
self.window * self.track_window_influence
best_idx = np.argmax(pscore)
bbox = pred_bbox[:, best_idx] / scale_z
lr = penalty[best_idx] * score[best_idx] * self.track_lr
cpx, cpy = self.center_pos
x,y,w,h = bbox
cx = x + cpx
cy = y + cpy
# smooth bbox
width = self.w * (1 - lr) + w * lr
height = self.h * (1 - lr) + h * lr
# clip boundary
cx, cy, width, height = self._bbox_clip(cx, cy, width, height, img.shape[:2])
# update state
self.center_pos = np.array([cx, cy])
self.w = width
self.h = height
bbox = [cx - width / 2, cy - height / 2, width, height]
best_score = score[best_idx]
return {'bbox': bbox, 'best_score': best_score}
def get_frames(video_name):
"""
Args:
Path to input video frame
Return:
Frame
"""
cap = cv.VideoCapture(video_name if video_name else 0)
while True:
ret, frame = cap.read()
if ret:
yield frame
else:
break
def main():
""" Sample SiamRPN Tracker
"""
# Computation backends supported by layers
backends = (cv.dnn.DNN_BACKEND_DEFAULT, cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_BACKEND_OPENCV,
cv.dnn.DNN_BACKEND_VKCOM, cv.dnn.DNN_BACKEND_CUDA)
# Target Devices for computation
targets = (cv.dnn.DNN_TARGET_CPU, cv.dnn.DNN_TARGET_OPENCL, cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD,
cv.dnn.DNN_TARGET_VULKAN, cv.dnn.DNN_TARGET_CUDA, cv.dnn.DNN_TARGET_CUDA_FP16)
parser = argparse.ArgumentParser(description='Use this script to run SiamRPN++ Visual Tracker',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--input_video', type=str, help='Path to input video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--target_net', type=str, default='target_net.onnx', help='Path to part of SiamRPN++ ran on target frame.')
parser.add_argument('--search_net', type=str, default='search_net.onnx', help='Path to part of SiamRPN++ ran on search frame.')
parser.add_argument('--rpn_head', type=str, default='rpn_head.onnx', help='Path to RPN Head ONNX model.')
parser.add_argument('--backend', choices=backends, default=cv.dnn.DNN_BACKEND_DEFAULT, type=int,
help="Select a computation backend: "
"%d: automatically (by default), "
"%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"%d: OpenCV Implementation, "
"%d: VKCOM, "
"%d: CUDA" % backends)
parser.add_argument('--target', choices=targets, default=cv.dnn.DNN_TARGET_CPU, type=int,
help='Select a target device: '
'%d: CPU target (by default), '
'%d: OpenCL, '
'%d: OpenCL FP16, '
'%d: Myriad, '
'%d: Vulkan, '
'%d: CUDA, '
'%d: CUDA fp16 (half-float preprocess)' % targets)
args, _ = parser.parse_known_args()
if args.input_video and not os.path.isfile(args.input_video):
raise OSError("Input video file does not exist")
if not os.path.isfile(args.target_net):
raise OSError("Target Net does not exist")
if not os.path.isfile(args.search_net):
raise OSError("Search Net does not exist")
if not os.path.isfile(args.rpn_head):
raise OSError("RPN Head Net does not exist")
#Load the Networks
target_net = cv.dnn.readNetFromONNX(args.target_net)
target_net.setPreferableBackend(args.backend)
target_net.setPreferableTarget(args.target)
search_net = cv.dnn.readNetFromONNX(args.search_net)
search_net.setPreferableBackend(args.backend)
search_net.setPreferableTarget(args.target)
rpn_head = cv.dnn.readNetFromONNX(args.rpn_head)
rpn_head.setPreferableBackend(args.backend)
rpn_head.setPreferableTarget(args.target)
model = ModelBuilder(target_net, search_net, rpn_head)
tracker = SiamRPNTracker(model)
first_frame = True
cv.namedWindow('SiamRPN++ Tracker', cv.WINDOW_AUTOSIZE)
for frame in get_frames(args.input_video):
if first_frame:
try:
init_rect = cv.selectROI('SiamRPN++ Tracker', frame, False, False)
except:
exit()
tracker.init(frame, init_rect)
first_frame = False
else:
outputs = tracker.track(frame)
bbox = list(map(int, outputs['bbox']))
x,y,w,h = bbox
cv.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 3)
cv.imshow('SiamRPN++ Tracker', frame)
key = cv.waitKey(1)
if key == ord("q"):
break
if __name__ == '__main__':
main()
+587
View File
@@ -0,0 +1,587 @@
#include <opencv2/core.hpp>
#include <opencv2/videoio.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/dnn.hpp>
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <cmath>
#include <random>
#include <numeric>
using namespace cv;
using namespace std;
class FilterbankFeatures {
// Initializes pre-processing class. Default values are the values used by the Jasper
// architecture for pre-processing. For more details, refer to the paper here:
// https://arxiv.org/abs/1904.03288
private:
int sample_rate = 16000;
double window_size = 0.02;
double window_stride = 0.01;
int win_length = static_cast<int>(sample_rate * window_size); // Number of samples in window
int hop_length = static_cast<int>(sample_rate * window_stride); // Number of steps to advance between frames
int n_fft = 512; // Size of window for STFT
// Parameters for filterbanks calculation
int n_filt = 64;
double lowfreq = 0.;
double highfreq = sample_rate / 2;
public:
// Mel filterbanks preparation
double hz_to_mel(double frequencies)
{
//Converts frequencies from hz to mel scale
// Fill in the linear scale
double f_min = 0.0;
double f_sp = 200.0 / 3;
double mels = (frequencies - f_min) / f_sp;
// Fill in the log-scale part
double min_log_hz = 1000.0; // beginning of log region (Hz)
double min_log_mel = (min_log_hz - f_min) / f_sp; // same (Mels)
double logstep = std::log(6.4) / 27.0; // step size for log region
if (frequencies >= min_log_hz)
{
mels = min_log_mel + std::log(frequencies / min_log_hz) / logstep;
}
return mels;
}
vector<double> mel_to_hz(vector<double>& mels)
{
// Converts frequencies from mel to hz scale
// Fill in the linear scale
double f_min = 0.0;
double f_sp = 200.0 / 3;
vector<double> freqs;
for (size_t i = 0; i < mels.size(); i++)
{
freqs.push_back(f_min + f_sp * mels[i]);
}
// And now the nonlinear scale
double min_log_hz = 1000.0; // beginning of log region (Hz)
double min_log_mel = (min_log_hz - f_min) / f_sp; // same (Mels)
double logstep = std::log(6.4) / 27.0; // step size for log region
for(size_t i = 0; i < mels.size(); i++)
{
if (mels[i] >= min_log_mel)
{
freqs[i] = min_log_hz * exp(logstep * (mels[i] - min_log_mel));
}
}
return freqs;
}
vector<double> mel_frequencies(int n_mels, double fmin, double fmax)
{
// Calculates n mel frequencies between 2 frequencies
double min_mel = hz_to_mel(fmin);
double max_mel = hz_to_mel(fmax);
vector<double> mels;
double step = (max_mel - min_mel) / (n_mels - 1);
for(double i = min_mel; i < max_mel; i += step)
{
mels.push_back(i);
}
mels.push_back(max_mel);
vector<double> res = mel_to_hz(mels);
return res;
}
vector<vector<double>> mel(int n_mels, double fmin, double fmax)
{
// Generates mel filterbank matrix
double num = 1 + n_fft / 2;
vector<vector<double>> weights(n_mels, vector<double>(static_cast<int>(num), 0.));
// Center freqs of each FFT bin
vector<double> fftfreqs;
double step = (sample_rate / 2) / (num - 1);
for(double i = 0; i <= sample_rate / 2; i += step)
{
fftfreqs.push_back(i);
}
// 'Center freqs' of mel bands - uniformly spaced between limits
vector<double> mel_f = mel_frequencies(n_mels + 2, fmin, fmax);
vector<double> fdiff;
for(size_t i = 1; i < mel_f.size(); ++i)
{
fdiff.push_back(mel_f[i]- mel_f[i - 1]);
}
vector<vector<double>> ramps(mel_f.size(), vector<double>(fftfreqs.size()));
for (size_t i = 0; i < mel_f.size(); ++i)
{
for (size_t j = 0; j < fftfreqs.size(); ++j)
{
ramps[i][j] = mel_f[i] - fftfreqs[j];
}
}
double lower, upper, enorm;
for (int i = 0; i < n_mels; ++i)
{
// using Slaney-style mel which is scaled to be approx constant energy per channel
enorm = 2./(mel_f[i + 2] - mel_f[i]);
for (int j = 0; j < static_cast<int>(num); ++j)
{
// lower and upper slopes for all bins
lower = (-1) * ramps[i][j] / fdiff[i];
upper = ramps[i + 2][j] / fdiff[i + 1];
weights[i][j] = max(0., min(lower, upper)) * enorm;
}
}
return weights;
}
// STFT preparation
vector<double> pad_window_center(vector<double>&data, int size)
{
// Pad the window out to n_fft size
int n = static_cast<int>(data.size());
int lpad = static_cast<int>((size - n) / 2);
vector<double> pad_array;
for(int i = 0; i < lpad; ++i)
{
pad_array.push_back(0.);
}
for(size_t i = 0; i < data.size(); ++i)
{
pad_array.push_back(data[i]);
}
for(int i = 0; i < lpad; ++i)
{
pad_array.push_back(0.);
}
return pad_array;
}
vector<vector<double>> frame(vector<double>& x)
{
// Slices a data array into overlapping frames.
int n_frames = static_cast<int>(1 + (x.size() - n_fft) / hop_length);
vector<vector<double>> new_x(n_fft, vector<double>(n_frames));
for (int i = 0; i < n_fft; ++i)
{
for (int j = 0; j < n_frames; ++j)
{
new_x[i][j] = x[i + j * hop_length];
}
}
return new_x;
}
vector<double> hanning()
{
// https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows
vector<double> window_tensor;
for (int j = 1 - win_length; j < win_length; j+=2)
{
window_tensor.push_back(1 - (0.5 * (1 - cos(CV_PI * j / (win_length - 1)))));
}
return window_tensor;
}
vector<vector<double>> stft_power(vector<double>& y)
{
// Short Time Fourier Transform. The STFT represents a signal in the time-frequency
// domain by computing discrete Fourier transforms (DFT) over short overlapping windows.
// https://en.wikipedia.org/wiki/Short-time_Fourier_transform
// Pad the time series so that frames are centered
vector<double> new_y;
int num = int(n_fft / 2);
for (int i = 0; i < num; ++i)
{
new_y.push_back(y[num - i]);
}
for (size_t i = 0; i < y.size(); ++i)
{
new_y.push_back(y[i]);
}
for (size_t i = y.size() - 2; i >= y.size() - num - 1; --i)
{
new_y.push_back(y[i]);
}
// Compute a window function
vector<double> window_tensor = hanning();
// Pad the window out to n_fft size
vector<double> fft_window = pad_window_center(window_tensor, n_fft);
// Window the time series
vector<vector<double>> y_frames = frame(new_y);
// Multiply on fft_window
for (size_t i = 0; i < y_frames.size(); ++i)
{
for (size_t j = 0; j < y_frames[0].size(); ++j)
{
y_frames[i][j] *= fft_window[i];
}
}
// Transpose frames for computing stft
vector<vector<double>> y_frames_transpose(y_frames[0].size(), vector<double>(y_frames.size()));
for (size_t i = 0; i < y_frames[0].size(); ++i)
{
for (size_t j = 0; j < y_frames.size(); ++j)
{
y_frames_transpose[i][j] = y_frames[j][i];
}
}
// Short Time Fourier Transform
// and get power of spectrum
vector<vector<double>> spectrum_power(y_frames_transpose[0].size() / 2 + 1 );
for (size_t i = 0; i < y_frames_transpose.size(); ++i)
{
Mat dstMat;
dft(y_frames_transpose[i], dstMat, DFT_COMPLEX_OUTPUT);
// we need only the first part of the spectrum, the second part is symmetrical
for (int j = 0; j < static_cast<int>(y_frames_transpose[0].size()) / 2 + 1; ++j)
{
double power_re = dstMat.at<double>(2 * j) * dstMat.at<double>(2 * j);
double power_im = dstMat.at<double>(2 * j + 1) * dstMat.at<double>(2 * j + 1);
spectrum_power[j].push_back(power_re + power_im);
}
}
return spectrum_power;
}
Mat calculate_features(vector<double>& x)
{
// Calculates filterbank features matrix.
// Do preemphasis
std::default_random_engine generator;
std::normal_distribution<double> normal_distr(0, 1);
double dither = 1e-5;
for(size_t i = 0; i < x.size(); ++i)
{
x[i] += dither * static_cast<double>(normal_distr(generator));
}
double preemph = 0.97;
for (size_t i = x.size() - 1; i > 0; --i)
{
x[i] -= preemph * x[i-1];
}
// Calculate Short Time Fourier Transform and get power of spectrum
auto spectrum_power = stft_power(x);
vector<vector<double>> filterbanks = mel(n_filt, lowfreq, highfreq);
// Calculate log of multiplication of filterbanks matrix on spectrum_power matrix
vector<vector<double>> x_stft(filterbanks.size(), vector<double>(spectrum_power[0].size(), 0));
for (size_t i = 0; i < filterbanks.size(); ++i)
{
for (size_t j = 0; j < filterbanks[0].size(); ++j)
{
for (size_t k = 0; k < spectrum_power[0].size(); ++k)
{
x_stft[i][k] += filterbanks[i][j] * spectrum_power[j][k];
}
}
for (size_t k = 0; k < spectrum_power[0].size(); ++k)
{
x_stft[i][k] = std::log(x_stft[i][k] + 1e-20);
}
}
// normalize data
auto elments_num = x_stft[0].size();
for(size_t i = 0; i < x_stft.size(); ++i)
{
double x_mean = std::accumulate(x_stft[i].begin(), x_stft[i].end(), 0.) / elments_num; // arithmetic mean
double x_std = 0; // standard deviation
for(size_t j = 0; j < elments_num; ++j)
{
double subtract = x_stft[i][j] - x_mean;
x_std += subtract * subtract;
}
x_std /= elments_num;
x_std = sqrt(x_std) + 1e-10; // make sure x_std is not zero
for(size_t j = 0; j < elments_num; ++j)
{
x_stft[i][j] = (x_stft[i][j] - x_mean) / x_std; // standard score
}
}
Mat calculate_features(static_cast<int>(x_stft.size()), static_cast<int>(x_stft[0].size()), CV_32F);
for(int i = 0; i < calculate_features.size[0]; ++i)
{
for(int j = 0; j < calculate_features.size[1]; ++j)
{
calculate_features.at<float>(i, j) = static_cast<float>(x_stft[i][j]);
}
}
return calculate_features;
}
};
class Decoder {
// Used for decoding the output of jasper model
private:
unordered_map<int, char> labels_map = fillMap();
int blank_id = 28;
public:
unordered_map<int, char> fillMap()
{
vector<char> labels={' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p'
,'q','r','s','t','u','v','w','x','y','z','\''};
unordered_map<int, char> map;
for(int i = 0; i < static_cast<int>(labels.size()); ++i)
{
map[i] = labels[i];
}
return map;
}
string decode(Mat& x)
{
// Takes output of Jasper model and performs ctc decoding algorithm to
// remove duplicates and special symbol. Returns prediction
vector<int> prediction;
for(int i = 0; i < x.size[1]; ++i)
{
double maxEl = -1e10;
int ind = 0;
for(int j = 0; j < x.size[2]; ++j)
{
if (maxEl <= x.at<float>(0, i, j))
{
maxEl = x.at<float>(0, i, j);
ind = j;
}
}
prediction.push_back(ind);
}
// CTC decoding procedure
vector<double> decoded_prediction = {};
int previous = blank_id;
for(int i = 0; i < static_cast<int>(prediction.size()); ++i)
{
if (( prediction[i] != previous || previous == blank_id) && prediction[i] != blank_id)
{
decoded_prediction.push_back(prediction[i]);
}
previous = prediction[i];
}
string hypotheses = {};
for(size_t i = 0; i < decoded_prediction.size(); ++i)
{
auto it = labels_map.find(static_cast<char>(decoded_prediction[i]));
if (it != labels_map.end())
hypotheses.push_back(it->second);
}
return hypotheses;
}
};
static string predict(Mat& features, dnn::Net net, Decoder decoder)
{
// Passes the features through the Jasper model and decodes the output to english transcripts.
// expand 2d features matrix to 3d
vector<int> sizes = {1, static_cast<int>(features.size[0]),
static_cast<int>(features.size[1])};
features = features.reshape(0, sizes);
// make prediction
net.setInput(features);
Mat output = net.forward();
// decode output to transcript
auto prediction = decoder.decode(output);
return prediction;
}
static int readAudioFile(vector<double>& inputAudio, string file, int audioStream)
{
VideoCapture cap;
int samplingRate = 16000;
vector<int> params { CAP_PROP_AUDIO_STREAM, audioStream,
CAP_PROP_VIDEO_STREAM, -1,
CAP_PROP_AUDIO_DATA_DEPTH, CV_32F,
CAP_PROP_AUDIO_SAMPLES_PER_SECOND, samplingRate
};
cap.open(file, CAP_ANY, params);
if (!cap.isOpened())
{
cerr << "Error : Can't read audio file: '" << file << "' with audioStream = " << audioStream << endl;
return -1;
}
const int audioBaseIndex = (int)cap.get(CAP_PROP_AUDIO_BASE_INDEX);
vector<double> frameVec;
Mat frame;
for (;;)
{
if (cap.grab())
{
cap.retrieve(frame, audioBaseIndex);
frameVec = frame;
inputAudio.insert(inputAudio.end(), frameVec.begin(), frameVec.end());
}
else
{
break;
}
}
return samplingRate;
}
static int readAudioMicrophone(vector<double>& inputAudio, int microTime)
{
VideoCapture cap;
int samplingRate = 16000;
vector<int> params { CAP_PROP_AUDIO_STREAM, 0,
CAP_PROP_VIDEO_STREAM, -1,
CAP_PROP_AUDIO_DATA_DEPTH, CV_32F,
CAP_PROP_AUDIO_SAMPLES_PER_SECOND, samplingRate
};
cap.open(0, CAP_ANY, params);
if (!cap.isOpened())
{
cerr << "Error: Can't open microphone" << endl;
return -1;
}
const int audioBaseIndex = (int)cap.get(CAP_PROP_AUDIO_BASE_INDEX);
vector<double> frameVec;
Mat frame;
if (microTime <= 0)
{
cerr << "Error: Duration of audio chunk must be > 0" << endl;
return -1;
}
size_t sizeOfData = static_cast<size_t>(microTime * samplingRate);
while (inputAudio.size() < sizeOfData)
{
if (cap.grab())
{
cap.retrieve(frame, audioBaseIndex);
frameVec = frame;
inputAudio.insert(inputAudio.end(), frameVec.begin(), frameVec.end());
}
else
{
cerr << "Error: Grab error" << endl;
break;
}
}
return samplingRate;
}
int main(int argc, char** argv)
{
const String keys =
"{help h usage ? | | This script runs Jasper Speech recognition model }"
"{input_file i | | Path to input audio file. If not specified, microphone input will be used }"
"{audio_duration t | 15 | Duration of audio chunk to be captured from microphone }"
"{audio_stream a | 0 | CAP_PROP_AUDIO_STREAM value }"
"{show_spectrogram s | false | Show a spectrogram of the input audio: true / false / 1 / 0 }"
"{model m | jasper.onnx | Path to the onnx file of Jasper. You can download the converted onnx model "
"from https://drive.google.com/drive/folders/1wLtxyao4ItAg8tt4Sb63zt6qXzhcQoR6?usp=sharing}"
"{backend b | dnn::DNN_BACKEND_DEFAULT | Select a computation backend: "
"dnn::DNN_BACKEND_DEFAULT, "
"dnn::DNN_BACKEND_INFERENCE_ENGINE, "
"dnn::DNN_BACKEND_OPENCV }"
"{target t | dnn::DNN_TARGET_CPU | Select a target device: "
"dnn::DNN_TARGET_CPU, "
"dnn::DNN_TARGET_OPENCL, "
"dnn::DNN_TARGET_OPENCL_FP16 }"
;
CommandLineParser parser(argc, argv, keys);
if (parser.has("help"))
{
parser.printMessage();
return 0;
}
// Load Network
dnn::Net net = dnn::readNetFromONNX(parser.get<std::string>("model"));
net.setPreferableBackend(parser.get<int>("backend"));
net.setPreferableTarget(parser.get<int>("target"));
// Get audio
vector<double>inputAudio = {};
int samplingRate = 0;
if (parser.has("input_file"))
{
string audio = samples::findFile(parser.get<std::string>("input_file"));
samplingRate = readAudioFile(inputAudio, audio, parser.get<int>("audio_stream"));
}
else
{
samplingRate = readAudioMicrophone(inputAudio, parser.get<int>("audio_duration"));
}
if ((inputAudio.size() == 0) || samplingRate <= 0)
{
cerr << "Error: problems with audio reading, check input arguments" << endl;
return -1;
}
if (inputAudio.size() / samplingRate < 6)
{
cout << "Warning: For predictable network performance duration of audio must exceed 6 sec."
" Audio will be extended with zero samples" << endl;
for(int i = static_cast<int>(inputAudio.size()) - 1; i < samplingRate * 6; ++i)
{
inputAudio.push_back(0);
}
}
// Calculate features
FilterbankFeatures filter;
auto calculated_features = filter.calculate_features(inputAudio);
// Show spectogram if required
if (parser.get<bool>("show_spectrogram") == true)
{
Mat spectogram;
normalize(calculated_features, spectogram, 0, 255, NORM_MINMAX, CV_8U);
applyColorMap(spectogram, spectogram, COLORMAP_INFERNO);
imshow("spectogram", spectogram);
waitKey(0);
}
Decoder decoder;
string prediction = predict(calculated_features, net, decoder);
for( auto &transcript: prediction)
{
cout << transcript;
}
return 0;
}
+567
View File
@@ -0,0 +1,567 @@
import numpy as np
import cv2 as cv
import argparse
import os
'''
You can download the converted onnx model from https://drive.google.com/drive/folders/1wLtxyao4ItAg8tt4Sb63zt6qXzhcQoR6?usp=sharing
or convert the model yourself.
You can get the original pre-trained Jasper model from NVIDIA : https://ngc.nvidia.com/catalog/models/nvidia:jasper_pyt_onnx_fp16_amp/files
Download and unzip : `$ wget --content-disposition https://api.ngc.nvidia.com/v2/models/nvidia/jasper_pyt_onnx_fp16_amp/versions/20.10.0/zip -O jasper_pyt_onnx_fp16_amp_20.10.0.zip && unzip -o ./jasper_pyt_onnx_fp16_amp_20.10.0.zip && unzip -o ./jasper_pyt_onnx_fp16_amp.zip`
you can get the script to convert the model here : https://gist.github.com/spazewalker/507f1529e19aea7e8417f6e935851a01
You can convert the model using the following steps:
1. Import onnx and load the original model
```
import onnx
model = onnx.load("./jasper-onnx/1/model.onnx")
```
3. Change data type of input layer
```
inp = model.graph.input[0]
model.graph.input.remove(inp)
inp.type.tensor_type.elem_type = 1
model.graph.input.insert(0,inp)
```
4. Change the data type of output layer
```
out = model.graph.output[0]
model.graph.output.remove(out)
out.type.tensor_type.elem_type = 1
model.graph.output.insert(0,out)
```
5. Change the data type of every initializer and cast it's values from FP16 to FP32
```
for i,init in enumerate(model.graph.initializer):
model.graph.initializer.remove(init)
init.data_type = 1
init.raw_data = np.frombuffer(init.raw_data, count=np.product(init.dims), dtype=np.float16).astype(np.float32).tobytes()
model.graph.initializer.insert(i,init)
```
6. Add an additional reshape node to handle the inconsistent input from python and c++ of openCV.
see https://github.com/opencv/opencv/issues/19091
Make & insert a new node with 'Reshape' operation & required initializer
```
tensor = numpy_helper.from_array(np.array([0,64,-1]),name='shape_reshape')
model.graph.initializer.insert(0,tensor)
node = onnx.helper.make_node(op_type='Reshape',inputs=['input__0','shape_reshape'], outputs=['input_reshaped'], name='reshape__0')
model.graph.node.insert(0,node)
model.graph.node[1].input[0] = 'input_reshaped'
```
7. Finally save the model
```
with open('jasper_dynamic_input_float.onnx','wb') as f:
onnx.save_model(model,f)
```
Original Repo : https://github.com/NVIDIA/DeepLearningExamples/tree/master/PyTorch/SpeechRecognition/Jasper
'''
class FilterbankFeatures:
def __init__(self,
sample_rate=16000, window_size=0.02, window_stride=0.01,
n_fft=512, preemph=0.97, n_filt=64, lowfreq=0,
highfreq=None, log=True, dither=1e-5):
'''
Initializes pre-processing class. Default values are the values used by the Jasper
architecture for pre-processing. For more details, refer to the paper here:
https://arxiv.org/abs/1904.03288
'''
self.win_length = int(sample_rate * window_size) # frame size
self.hop_length = int(sample_rate * window_stride) # stride
self.n_fft = n_fft or 2 ** np.ceil(np.log2(self.win_length))
self.log = log
self.dither = dither
self.n_filt = n_filt
self.preemph = preemph
highfreq = highfreq or sample_rate / 2
self.window_tensor = np.hanning(self.win_length)
self.filterbanks = self.mel(sample_rate, self.n_fft, n_mels=n_filt, fmin=lowfreq, fmax=highfreq)
self.filterbanks.dtype=np.float32
self.filterbanks = np.expand_dims(self.filterbanks,0)
def normalize_batch(self, x, seq_len):
'''
Normalizes the features.
'''
x_mean = np.zeros((seq_len.shape[0], x.shape[1]), dtype=x.dtype)
x_std = np.zeros((seq_len.shape[0], x.shape[1]), dtype=x.dtype)
for i in range(x.shape[0]):
x_mean[i, :] = np.mean(x[i, :, :seq_len[i]],axis=1)
x_std[i, :] = np.std(x[i, :, :seq_len[i]],axis=1)
# make sure x_std is not zero
x_std += 1e-10
return (x - np.expand_dims(x_mean,2)) / np.expand_dims(x_std,2)
def calculate_features(self, x, seq_len):
'''
Calculates filterbank features.
args:
x : mono channel audio
seq_len : length of the audio sample
returns:
x : filterbank features
'''
dtype = x.dtype
seq_len = np.ceil(seq_len / self.hop_length)
seq_len = np.array(seq_len,dtype=np.int32)
# dither
if self.dither > 0:
x += self.dither * np.random.randn(*x.shape)
# do preemphasis
if self.preemph is not None:
x = np.concatenate(
(np.expand_dims(x[0],-1), x[1:] - self.preemph * x[:-1]), axis=0)
# Short Time Fourier Transform
x = self.stft(x, n_fft=self.n_fft, hop_length=self.hop_length,
win_length=self.win_length,
fft_window=self.window_tensor)
# get power spectrum
x = (x**2).sum(-1)
# dot with filterbank energies
x = np.matmul(np.array(self.filterbanks,dtype=x.dtype), x)
# log features if required
if self.log:
x = np.log(x + 1e-20)
# normalize if required
x = self.normalize_batch(x, seq_len).astype(dtype)
return x
# Mel Frequency calculation
def hz_to_mel(self, frequencies):
'''
Converts frequencies from hz to mel scale. Input can be a number or a vector.
'''
frequencies = np.asanyarray(frequencies)
f_min = 0.0
f_sp = 200.0 / 3
mels = (frequencies - f_min) / f_sp
# Fill in the log-scale part
min_log_hz = 1000.0 # beginning of log region (Hz)
min_log_mel = (min_log_hz - f_min) / f_sp # same (Mels)
logstep = np.log(6.4) / 27.0 # step size for log region
if frequencies.ndim:
# If we have array data, vectorize
log_t = frequencies >= min_log_hz
mels[log_t] = min_log_mel + np.log(frequencies[log_t] / min_log_hz) / logstep
elif frequencies >= min_log_hz:
# If we have scalar data, directly
mels = min_log_mel + np.log(frequencies / min_log_hz) / logstep
return mels
def mel_to_hz(self, mels):
'''
Converts frequencies from mel to hz scale. Input can be a number or a vector.
'''
mels = np.asanyarray(mels)
# Fill in the linear scale
f_min = 0.0
f_sp = 200.0 / 3
freqs = f_min + f_sp * mels
# And now the nonlinear scale
min_log_hz = 1000.0 # beginning of log region (Hz)
min_log_mel = (min_log_hz - f_min) / f_sp # same (Mels)
logstep = np.log(6.4) / 27.0 # step size for log region
if mels.ndim:
# If we have vector data, vectorize
log_t = mels >= min_log_mel
freqs[log_t] = min_log_hz * np.exp(logstep * (mels[log_t] - min_log_mel))
elif mels >= min_log_mel:
# If we have scalar data, check directly
freqs = min_log_hz * np.exp(logstep * (mels - min_log_mel))
return freqs
def mel_frequencies(self, n_mels=128, fmin=0.0, fmax=11025.0):
'''
Calculates n mel frequencies between 2 frequencies
args:
n_mels : number of bands
fmin : min frequency
fmax : max frequency
returns:
mels : vector of mel frequencies
'''
# 'Center freqs' of mel bands - uniformly spaced between limits
min_mel = self.hz_to_mel(fmin)
max_mel = self.hz_to_mel(fmax)
mels = np.linspace(min_mel, max_mel, n_mels)
return self.mel_to_hz(mels)
def mel(self, sr, n_fft, n_mels=128, fmin=0.0, fmax=None, dtype=np.float32):
'''
Generates mel filterbank
args:
sr : Sampling rate
n_fft : number of FFT components
n_mels : number of Mel bands to generate
fmin : lowest frequency (in Hz)
fmax : highest frequency (in Hz). sr/2.0 if None
dtype : the data type of the output basis.
returns:
mels : Mel transform matrix
'''
# default Max freq = half of sampling rate
if fmax is None:
fmax = float(sr) / 2
# Initialize the weights
n_mels = int(n_mels)
weights = np.zeros((n_mels, int(1 + n_fft // 2)), dtype=dtype)
# Center freqs of each FFT bin
fftfreqs = np.linspace(0, float(sr) / 2, int(1 + n_fft // 2), endpoint=True)
# 'Center freqs' of mel bands - uniformly spaced between limits
mel_f = self.mel_frequencies(n_mels + 2, fmin=fmin, fmax=fmax)
fdiff = np.diff(mel_f)
ramps = np.subtract.outer(mel_f, fftfreqs)
for i in range(n_mels):
# lower and upper slopes for all bins
lower = -ramps[i] / fdiff[i]
upper = ramps[i + 2] / fdiff[i + 1]
# .. then intersect them with each other and zero
weights[i] = np.maximum(0, np.minimum(lower, upper))
# Using Slaney-style mel which is scaled to be approx constant energy per channel
enorm = 2.0 / (mel_f[2 : n_mels + 2] - mel_f[:n_mels])
weights *= enorm[:, np.newaxis]
return weights
# STFT preparation
def pad_window_center(self, data, size, axis=-1, **kwargs):
'''
Centers the data and pads.
args:
data : Vector to be padded and centered
size : Length to pad data
axis : Axis along which to pad and center the data
kwargs : arguments passed to np.pad
return : centered and padded data
'''
kwargs.setdefault("mode", "constant")
n = data.shape[axis]
lpad = int((size - n) // 2)
lengths = [(0, 0)] * data.ndim
lengths[axis] = (lpad, int(size - n - lpad))
if lpad < 0:
raise Exception(
("Target size ({:d}) must be at least input size ({:d})").format(size, n)
)
return np.pad(data, lengths, **kwargs)
def frame(self, x, frame_length, hop_length):
'''
Slices a data array into (overlapping) frames.
args:
x : array to frame
frame_length : length of frame
hop_length : Number of steps to advance between frames
return : A framed view of `x`
'''
if x.shape[-1] < frame_length:
raise Exception(
"Input is too short (n={:d})"
" for frame_length={:d}".format(x.shape[-1], frame_length)
)
x = np.asfortranarray(x)
n_frames = 1 + (x.shape[-1] - frame_length) // hop_length
strides = np.asarray(x.strides)
new_stride = np.prod(strides[strides > 0] // x.itemsize) * x.itemsize
shape = list(x.shape)[:-1] + [frame_length, n_frames]
strides = list(strides) + [hop_length * new_stride]
return np.lib.stride_tricks.as_strided(x, shape=shape, strides=strides)
def dtype_r2c(self, d, default=np.complex64):
'''
Find the complex numpy dtype corresponding to a real dtype.
args:
d : The real-valued dtype to convert to complex.
default : The default complex target type, if `d` does not match a known dtype
return : The complex dtype
'''
mapping = {
np.dtype(np.float32): np.complex64,
np.dtype(np.float64): np.complex128,
}
dt = np.dtype(d)
if dt.kind == "c":
return dt
return np.dtype(mapping.get(dt, default))
def stft(self, y, n_fft, hop_length=None, win_length=None, fft_window=None, pad_mode='reflect', return_complex=False):
'''
Short Time Fourier Transform. The STFT represents a signal in the time-frequency
domain by computing discrete Fourier transforms (DFT) over short overlapping windows.
args:
y : input signal
n_fft : length of the windowed signal after padding with zeros.
hop_length : number of audio samples between adjacent STFT columns.
win_length : Each frame of audio is windowed by window of length win_length and
then padded with zeros to match n_fft
fft_window : a vector or array of length `n_fft` having values computed by a
window function
pad_mode : mode while padding the signal
return_complex : returns array with complex data type if `True`
return : Matrix of short-term Fourier transform coefficients.
'''
if win_length is None:
win_length = n_fft
if hop_length is None:
hop_length = int(win_length // 4)
if y.ndim!=1:
raise Exception(f'Invalid input shape. Only Mono Channeled audio supported. Input must have shape (Audio,). Got {y.shape}')
# Pad the window out to n_fft size
fft_window = self.pad_window_center(fft_window, n_fft)
# Reshape so that the window can be broadcast
fft_window = fft_window.reshape((-1, 1))
# Pad the time series so that frames are centered
y = np.pad(y, int(n_fft // 2), mode=pad_mode)
# Window the time series.
y_frames = self.frame(y, frame_length=n_fft, hop_length=hop_length)
# Convert data type to complex
dtype = self.dtype_r2c(y.dtype)
# Pre-allocate the STFT matrix
stft_matrix = np.empty( (int(1 + n_fft // 2), y_frames.shape[-1]), dtype=dtype, order="F")
stft_matrix = np.fft.rfft( fft_window * y_frames, axis=0)
return stft_matrix if return_complex==True else np.stack((stft_matrix.real,stft_matrix.imag),axis=-1)
class Decoder:
'''
Used for decoding the output of jasper model.
'''
def __init__(self):
labels=[' ','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',"'"]
self.labels_map = {i: label for i,label in enumerate(labels)}
self.blank_id = 28
def decode(self,x):
"""
Takes output of Jasper model and performs ctc decoding algorithm to
remove duplicates and special symbol. Returns prediction
"""
x = np.argmax(x,axis=-1)
hypotheses = []
prediction = x.tolist()
# CTC decoding procedure
decoded_prediction = []
previous = self.blank_id
for p in prediction:
if (p != previous or previous == self.blank_id) and p != self.blank_id:
decoded_prediction.append(p)
previous = p
hypothesis = ''.join([self.labels_map[c] for c in decoded_prediction])
hypotheses.append(hypothesis)
return hypotheses
def predict(features, net, decoder):
'''
Passes the features through the Jasper model and decodes the output to english transcripts.
args:
features : input features, calculated using FilterbankFeatures class
net : Jasper model dnn.net object
decoder : Decoder object
return : Predicted text
'''
# make prediction
net.setInput(features)
output = net.forward()
# decode output to transcript
prediction = decoder.decode(output.squeeze(0))
return prediction[0]
def readAudioFile(file, audioStream):
cap = cv.VideoCapture(file)
samplingRate = 16000
params = np.asarray([cv.CAP_PROP_AUDIO_STREAM, audioStream,
cv.CAP_PROP_VIDEO_STREAM, -1,
cv.CAP_PROP_AUDIO_DATA_DEPTH, cv.CV_32F,
cv.CAP_PROP_AUDIO_SAMPLES_PER_SECOND, samplingRate
])
cap.open(file, cv.CAP_ANY, params)
if cap.isOpened() is False:
print("Error : Can't read audio file:", file, "with audioStream = ", audioStream)
return
audioBaseIndex = int (cap.get(cv.CAP_PROP_AUDIO_BASE_INDEX))
inputAudio = []
while(1):
if (cap.grab()):
frame = np.asarray([])
frame = cap.retrieve(frame, audioBaseIndex)
for i in range(len(frame[1][0])):
inputAudio.append(frame[1][0][i])
else:
break
inputAudio = np.asarray(inputAudio, dtype=np.float64)
return inputAudio, samplingRate
def readAudioMicrophone(microTime):
cap = cv.VideoCapture()
samplingRate = 16000
params = np.asarray([cv.CAP_PROP_AUDIO_STREAM, 0,
cv.CAP_PROP_VIDEO_STREAM, -1,
cv.CAP_PROP_AUDIO_DATA_DEPTH, cv.CV_32F,
cv.CAP_PROP_AUDIO_SAMPLES_PER_SECOND, samplingRate
])
cap.open(0, cv.CAP_ANY, params)
if cap.isOpened() is False:
print("Error: Can't open microphone")
print("Error: problems with audio reading, check input arguments")
return
audioBaseIndex = int(cap.get(cv.CAP_PROP_AUDIO_BASE_INDEX))
cvTickFreq = cv.getTickFrequency()
sysTimeCurr = cv.getTickCount()
sysTimePrev = sysTimeCurr
inputAudio = []
while ((sysTimeCurr - sysTimePrev) / cvTickFreq < microTime):
if (cap.grab()):
frame = np.asarray([])
frame = cap.retrieve(frame, audioBaseIndex)
for i in range(len(frame[1][0])):
inputAudio.append(frame[1][0][i])
sysTimeCurr = cv.getTickCount()
else:
print("Error: Grab error")
break
inputAudio = np.asarray(inputAudio, dtype=np.float64)
print("Number of samples: ", len(inputAudio))
return inputAudio, samplingRate
if __name__ == '__main__':
# Computation backends supported by layers
backends = (cv.dnn.DNN_BACKEND_DEFAULT, cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_BACKEND_OPENCV)
# Target Devices for computation
targets = (cv.dnn.DNN_TARGET_CPU, cv.dnn.DNN_TARGET_OPENCL, cv.dnn.DNN_TARGET_OPENCL_FP16)
parser = argparse.ArgumentParser(description='This script runs Jasper Speech recognition model',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--input_type', type=str, required=True, help='file or microphone')
parser.add_argument('--micro_time', type=int, default=15, help='Duration of microphone work in seconds. Must be more than 6 sec')
parser.add_argument('--input_audio', type=str, help='Path to input audio file. OR Path to a txt file with relative path to multiple audio files in different lines')
parser.add_argument('--audio_stream', type=int, default=0, help='CAP_PROP_AUDIO_STREAM value')
parser.add_argument('--show_spectrogram', action='store_true', help='Whether to show a spectrogram of the input audio.')
parser.add_argument('--model', type=str, default='jasper.onnx', help='Path to the onnx file of Jasper. default="jasper.onnx"')
parser.add_argument('--output', type=str, help='Path to file where recognized audio transcript must be saved. Leave this to print on console.')
parser.add_argument('--backend', choices=backends, default=cv.dnn.DNN_BACKEND_DEFAULT, type=int,
help='Select a computation backend: '
"%d: automatically (by default) "
"%d: OpenVINO Inference Engine "
"%d: OpenCV Implementation " % backends)
parser.add_argument('--target', choices=targets, default=cv.dnn.DNN_TARGET_CPU, type=int,
help='Select a target device: '
"%d: CPU target (by default) "
"%d: OpenCL "
"%d: OpenCL FP16 " % targets)
args, _ = parser.parse_known_args()
if args.input_audio and not os.path.isfile(args.input_audio):
raise OSError("Input audio file does not exist")
if not os.path.isfile(args.model):
raise OSError("Jasper model file does not exist")
features = []
if args.input_type == "file":
if args.input_audio.endswith('.txt'):
with open(args.input_audio) as f:
content = f.readlines()
content = [x.strip() for x in content]
audio_file_paths = content
for audio_file_path in audio_file_paths:
if not os.path.isfile(audio_file_path):
raise OSError("Audio file({audio_file_path}) does not exist")
else:
audio_file_paths = [args.input_audio]
audio_file_paths = [os.path.abspath(x) for x in audio_file_paths]
# Read audio Files
for audio_file_path in audio_file_paths:
audio = readAudioFile(audio_file_path, args.audio_stream)
if audio is None:
raise Exception(f"Can't read {args.input_audio}. Try a different format")
features.append(audio[0])
elif args.input_type == "microphone":
# Read audio from microphone
audio = readAudioMicrophone(args.micro_time)
if audio is None:
raise Exception(f"Can't open microphone. Try a different format")
features.append(audio[0])
else:
raise Exception(f"input_type {args.input_type} doesn't exist. Please enter 'file' or 'microphone'")
# Get Filterbank Features
feature_extractor = FilterbankFeatures()
for i in range(len(features)):
X = features[i]
seq_len = np.array([X.shape[0]], dtype=np.int32)
features[i] = feature_extractor.calculate_features(x=X, seq_len=seq_len)
# Load Network
net = cv.dnn.readNetFromONNX(args.model)
net.setPreferableBackend(args.backend)
net.setPreferableTarget(args.target)
# Show spectogram if required
if args.show_spectrogram and not args.input_audio.endswith('.txt'):
img = cv.normalize(src=features[0][0], dst=None, alpha=0, beta=255, norm_type=cv.NORM_MINMAX, dtype=cv.CV_8U)
img = cv.applyColorMap(img, cv.COLORMAP_JET)
cv.imshow('spectogram', img)
cv.waitKey(0)
# Initialize decoder
decoder = Decoder()
# Make prediction
prediction = []
print("Predicting...")
for feature in features:
print(f"\rAudio file {len(prediction)+1}/{len(features)}", end='')
prediction.append(predict(feature, net, decoder))
print("")
# save transcript if required
if args.output:
with open(args.output,'w') as f:
for pred in prediction:
f.write(pred+'\n')
print("Transcript was written to {}".format(args.output))
else:
print(prediction)
cv.destroyAllWindows()
+198
View File
@@ -0,0 +1,198 @@
/*
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory
of this distribution and at http://opencv.org/license.html.
Copyright (C) 2025, Bigvision LLC.
This sample demonstrates super-resolution using the SeeMoreDetails model.
The model upscales images by 4x while enhancing details and reducing noise.
Supports image inputs only.
SeeMoreDetails Repo: https://github.com/eduardzamfir/seemoredetails
*/
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include "common.hpp"
using namespace cv;
using namespace cv::dnn;
using namespace std;
const int WINDOW_OFFSET_X = 50;
const int WINDOW_OFFSET_Y = 50;
const int WINDOW_SPACING = 50;
const string param_keys =
"{ help h | | Print help message }"
"{ @alias | seemoredetails | Model alias from models.yml }"
"{ zoo | ../dnn/models.yml | Path to models.yml file }"
"{ input i | chicky_512.png | Path to input image }"
"{ model | | Path to model file }";
const string backend_keys = format(
"{ backend | default | Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN }");
const string target_keys = format(
"{ target | cpu | Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"vpu: VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess) }");
static Mat postprocessOutput(const Mat &output, const Size &originalSize)
{
Mat squeezed;
if (output.dims == 4 && output.size[0] == 1)
{
vector<int> newShape = {output.size[1], output.size[2], output.size[3]};
squeezed = output.reshape(0, newShape);
}
else
{
squeezed = output.clone();
}
Mat outputImage;
vector<Mat> channels(3);
for (int i = 0; i < 3; i++)
{
channels[2-i] = Mat(squeezed.size[1], squeezed.size[2], CV_32F,
squeezed.ptr<float>(i));
}
merge(channels, outputImage);
outputImage = max(0.0, min(1.0, outputImage));
outputImage.convertTo(outputImage, CV_8UC3, 255.0);
Size targetSize(originalSize.width * 4, originalSize.height * 4);
Mat result;
resize(outputImage, result, targetSize);
return result;
}
static Mat applySuperResolution(Net &net, const Mat &image, float scale, const Scalar &mean, bool swapRB, int width, int height)
{
Mat blob = blobFromImage(image, scale, Size(width, height), mean, swapRB, false, CV_32F);
net.setInput(blob);
Mat output;
net.forward(output);
return postprocessOutput(output, Size(image.cols, image.rows));
}
static double calculateFontScale(const Mat &image)
{
double baseScale = min(image.cols, image.rows) / 800.0;
return max(0.5, baseScale);
}
static void processFrame(Net &net, Mat &frame, float scale, const Scalar &mean, bool swapRB, int width, int height)
{
Mat result = applySuperResolution(net, frame, scale, mean, swapRB, width, height);
double fontScale = calculateFontScale(frame);
int thickness = max(1, (int)(fontScale * 2));
putText(frame, "Original", Point(10, 30),
FONT_HERSHEY_SIMPLEX, fontScale, Scalar(0, 255, 0), thickness);
double resultFontScale = calculateFontScale(result);
int resultThickness = max(1, (int)(resultFontScale * 2));
putText(result, "Super-Resolution 4x", Point(20, 50),
FONT_HERSHEY_SIMPLEX, resultFontScale, Scalar(0, 255, 0), resultThickness);
imshow("Input", frame);
imshow("Super-Resolution", result);
}
int main(int argc, char **argv)
{
const string about =
"This sample demonstrates super-resolution using the SeeMore model.\n"
"The model upscales images by 4x while enhancing details.\n\n"
"Usage examples:\n"
"\t./super_resolution\n"
"\t./super_resolution --input=image.jpg\n"
"\t./super_resolution --input=../data/chicky_512.png\n";
string keys = param_keys + backend_keys + target_keys;
CommandLineParser parser(argc, argv, keys);
if (parser.has("help"))
{
cout << about << endl;
parser.printMessage();
return 0;
}
string modelName = parser.get<String>("@alias");
string zooFile = samples::findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile);
parser = CommandLineParser(argc, argv, keys);
float scale = parser.get<float>("scale");
Scalar mean = parser.get<Scalar>("mean");
bool swapRB = parser.get<bool>("rgb");
String backend = parser.get<String>("backend");
String target = parser.get<String>("target");
String sha1 = parser.get<String>("sha1");
string model = findModel(parser.get<String>("model"), sha1);
int width = parser.get<int>("width");
int height = parser.get<int>("height");
string inputPath = findFile(parser.get<String>("input"));
if (model.empty())
{
cerr << "Model file not found" << endl;
return -1;
}
Net net;
try
{
net = readNetFromONNX(model);
net.setPreferableBackend(getBackendID(backend));
net.setPreferableTarget(getTargetID(target));
}
catch (const Exception &e)
{
cerr << "Error loading model: " << e.what() << endl;
return -1;
}
Mat testImage = imread(inputPath);
if (testImage.empty())
{
cerr << "Cannot load image: " << inputPath << endl;
return -1;
}
namedWindow("Input", WINDOW_NORMAL);
namedWindow("Super-Resolution", WINDOW_NORMAL);
moveWindow("Input", WINDOW_OFFSET_X, WINDOW_OFFSET_Y);
moveWindow("Super-Resolution", WINDOW_OFFSET_X + testImage.cols + WINDOW_SPACING, WINDOW_OFFSET_Y);
processFrame(net, testImage, scale, mean, swapRB, width, height);
waitKey(0);
destroyAllWindows();
return 0;
}
+176
View File
@@ -0,0 +1,176 @@
"""
This file is part of OpenCV project.
It is subject to the license terms in the LICENSE file found in the top-level directory
of this distribution and at http://opencv.org/license.html.
Copyright (C) 2025, Bigvision LLC.
This sample demonstrates super-resolution using the SeeMoreDetails model.
The model upscales images by 4x while enhancing details and reducing noise.
Supports image inputs only.
SeeMoreDetails Repo: https://github.com/eduardzamfir/seemoredetails
"""
import cv2 as cv
import argparse
import numpy as np
import os
from common import *
def get_args_parser(func_args):
backends = ("default", "openvino", "opencv", "vkcom", "cuda")
targets = (
"cpu",
"opencl",
"opencl_fp16",
"ncs2_vpu",
"hddl_vpu",
"vulkan",
"cuda",
"cuda_fp16",
)
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument(
"--zoo",
default=os.path.join(os.path.dirname(os.path.abspath(__file__)), "models.yml"),
help="An optional path to file with preprocessing parameters.",
)
parser.add_argument(
"--input", help="Path to input image file.", default="chicky_512.png", required=False
)
parser.add_argument(
"--backend",
default="default",
type=str,
choices=backends,
help="Choose one of computation backends: "
"default: automatically (by default), "
"openvino: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"opencv: OpenCV implementation, "
"vkcom: VKCOM, "
"cuda: CUDA, "
"webnn: WebNN",
)
parser.add_argument(
"--target",
default="cpu",
type=str,
choices=targets,
help="Choose one of target computation devices: "
"cpu: CPU target (by default), "
"opencl: OpenCL, "
"opencl_fp16: OpenCL fp16 (half-float precision), "
"ncs2_vpu: NCS2 VPU, "
"hddl_vpu: HDDL VPU, "
"vulkan: Vulkan, "
"cuda: CUDA, "
"cuda_fp16: CUDA fp16 (half-float preprocess)",
)
args, _ = parser.parse_known_args()
model_name = "seemoredetails"
add_preproc_args(args.zoo, parser, "super_resolution", model_name)
parser = argparse.ArgumentParser(
parents=[parser],
description="""
To run:
Default image:
python super_resolution.py
Image processing:
python super_resolution.py --input=path/to/your/input/image.jpg
The model performs 4x super-resolution on input images.
""",
formatter_class=argparse.RawTextHelpFormatter,
)
return parser.parse_args(func_args)
def load_model(args):
"""Load the super-resolution model"""
try:
model_path = findModel(args.model, args.sha1)
net = cv.dnn.readNetFromONNX(model_path)
net.setPreferableBackend(get_backend_id(args.backend))
net.setPreferableTarget(get_target_id(args.target))
return net
except Exception as e:
print(f"Error loading model: {e}")
return None
def postprocess_output(output, args, original_shape=None):
"""Postprocess model output to displayable image"""
output = np.squeeze(output, axis=0)
output = np.clip(output, 0, 1)
output = np.transpose(output, (1, 2, 0))
output = (output * 255).astype(np.uint8)
output = cv.cvtColor(output, cv.COLOR_RGB2BGR)
if original_shape is not None:
target_height, target_width = original_shape
upscaled_height, upscaled_width = target_height * 4, target_width * 4
output = cv.resize(output, (upscaled_width, upscaled_height))
return output
def apply_super_resolution(net, image, args):
"""Apply super-resolution to a single image"""
original_shape = image.shape[:2]
blob = cv.dnn.blobFromImage(
image,
scalefactor=args.scale,
size=(args.width, args.height),
mean=args.mean,
swapRB=args.rgb,
crop=False,
)
net.setInput(blob)
t0 = cv.getTickCount()
output = net.forward()
t = (cv.getTickCount() - t0) / cv.getTickFrequency()
result = postprocess_output(output, args, original_shape)
label = "Inference time: %.2f ms" % (t * 1000.0)
cv.putText(result, label, (10, 30), cv.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
return result
def main(func_args=None):
args = get_args_parser(func_args)
net = load_model(args)
if net is None:
print("Failed to load model.")
return -1
input_path = cv.samples.findFile(args.input)
image = cv.imread(input_path)
if image is None:
print(f"Cannot load image: {input_path}")
return -1
print(f"Processing image: {input_path}")
result = apply_super_resolution(net, image, args)
cv.namedWindow("Input", cv.WINDOW_NORMAL)
cv.namedWindow("Super-Resolution Result", cv.WINDOW_NORMAL)
cv.imshow("Input", image)
cv.imshow("Super-Resolution Result", result)
print("Press 'q' to quit...")
while True:
key = cv.waitKey(0) & 0xFF
if key == ord("q"):
break
cv.destroyAllWindows()
return 0
if __name__ == "__main__":
main()
+262
View File
@@ -0,0 +1,262 @@
/*
Text detection model (EAST): https://github.com/argman/EAST
Download link for EAST model: https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1
DB detector model:
https://drive.google.com/uc?export=download&id=17_ABp79PlFt9yPCxSaarVc_DKTmrSGGf
CRNN Text recognition model sourced from: https://github.com/meijieru/crnn.pytorch
How to convert from .pb to .onnx:
Using classes from: https://github.com/meijieru/crnn.pytorch/blob/master/models/crnn.py
Additional converted ONNX text recognition models available for direct download:
Download link: https://drive.google.com/drive/folders/1cTbQ3nuZG-EKWak6emD_s8_hHXWz7lAr?usp=sharing
These models are taken from: https://github.com/clovaai/deep-text-recognition-benchmark
Importing and using the CRNN model in PyTorch:
import torch
from models.crnn import CRNN
model = CRNN(32, 1, 37, 256)
model.load_state_dict(torch.load('crnn.pth'))
dummy_input = torch.randn(1, 1, 32, 100)
torch.onnx.export(model, dummy_input, "crnn.onnx", verbose=True)
Usage: ./example_dnn_text_detection DB
*/
#include <iostream>
#include <fstream>
#include <opencv2/geometry.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/dnn.hpp>
#include "common.hpp"
using namespace cv;
using namespace std;
using namespace cv::dnn;
const string about = "Use this script for Text Detection and Recognition using OpenCV. \n\n"
"Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to point to the directory where models are downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.\n"
"To run:\n"
"\t Example: ./example_dnn_text_detection modelName(i.e. DB or East) --ocr_model=<path to VGG_CTC.onnx>\n\n"
"Detection model path can also be specified using --model argument. \n\n"
"Download ocr model using: python download_models.py OCR \n\n";
// Command-line keys to parse the input arguments
string keys =
"{ help h | | Print help message. }"
"{ input i | right.jpg | Path to an input image. }"
"{ @alias | | An alias name of model to extract preprocessing parameters from models.yml file. }"
"{ zoo | ../dnn/models.yml | An optional path to file with preprocessing parameters }"
"{ ocr_model | | Path to a binary .onnx model for recognition. }"
"{ model | | Path to detection model file. }"
"{ thr | 0.5 | Confidence threshold for EAST detector. }"
"{ nms | 0.4 | Non-maximum suppression threshold for EAST detector. }"
"{ binaryThreshold bt | 0.3 | Confidence threshold for the binary map in DB detector. }"
"{ polygonThreshold pt | 0.5 | Confidence threshold for polygons in DB detector. }"
"{ maxCandidate max | 200 | Max candidates for polygons in DB detector. }"
"{ unclipRatio ratio | 2.0 | Unclip ratio for DB detector. }"
"{ vocabularyPath vp | alphabet_36.txt | Path to vocabulary file. }";
// Function prototype for the four-point perspective transform
static void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result);
static void processFrame(
const Mat& frame,
const vector<vector<Point>>& detResults,
const std::string& ocr_model,
bool imreadRGB,
Mat& board,
FontFace& fontFace,
int fontSize,
int fontWeight,
const vector<std::string>& vocabulary
);
int main(int argc, char** argv) {
// Setting up command-line parser with the specified keys
CommandLineParser parser(argc, argv, keys);
if (!parser.has("@alias") || parser.has("help"))
{
cout << about << endl;
parser.printMessage();
return -1;
}
const string modelName = parser.get<String>("@alias");
const string zooFile = findFile(parser.get<String>("zoo"));
keys += genPreprocArguments(modelName, zooFile, "");
keys += genPreprocArguments(modelName, zooFile, "ocr_");
parser = CommandLineParser(argc, argv, keys);
parser.about(about);
// Parsing command-line arguments
String sha1 = parser.get<String>("sha1");
String ocr_sha1 = parser.get<String>("ocr_sha1");
String detModelPath = findModel(parser.get<String>("model"), sha1);
String ocr = findModel(parser.get<String>("ocr_model"), ocr_sha1);
int height = parser.get<int>("height");
int width = parser.get<int>("width");
bool imreadRGB = parser.get<bool>("rgb");
String vocPath = parser.get<String>("vocabularyPath");
float binThresh = parser.get<float>("binaryThreshold");
float polyThresh = parser.get<float>("polygonThreshold");
double unclipRatio = parser.get<double>("unclipRatio");
uint maxCandidates = parser.get<uint>("maxCandidate");
float confThreshold = parser.get<float>("thr");
float nmsThreshold = parser.get<float>("nms");
Scalar mean = parser.get<Scalar>("mean");
// Ensuring the provided arguments are valid
if (!parser.check()) {
parser.printErrors();
return 1;
}
// Asserting detection model path is provided
CV_Assert(!detModelPath.empty());
vector<vector<Point>> detResults;
// Reading the input image
Mat frame = imread(samples::findFile(parser.get<String>("input")));
Mat board(frame.size(), frame.type(), Scalar(255, 255, 255));
int stdSize = 20;
int stdWeight = 400;
int stdImgSize = 512;
int imgWidth = min(frame.rows, frame.cols);
int size = (stdSize*imgWidth)/stdImgSize;
int weight = (stdWeight*imgWidth)/stdImgSize;
FontFace fontFace("sans");
// Initializing and configuring the text detection model based on the provided config
if (modelName == "East") {
// EAST Detector initialization
TextDetectionModel_EAST detector(detModelPath);
detector.setConfidenceThreshold(confThreshold)
.setNMSThreshold(nmsThreshold);
// Setting input parameters specific to EAST model
detector.setInputParams(1.0, Size(width, height), mean, true);
// Performing text detection
detector.detect(frame, detResults);
}
else if (modelName == "DB") {
// DB Detector initialization
TextDetectionModel_DB detector(detModelPath);
detector.setBinaryThreshold(binThresh)
.setPolygonThreshold(polyThresh)
.setUnclipRatio(unclipRatio)
.setMaxCandidates(maxCandidates);
// Setting input parameters specific to DB model
detector.setInputParams(1.0 / 255.0, Size(width, height), mean);
// Performing text detection
detector.detect(frame, detResults);
}
else {
cout << "[ERROR]: Unsupported file config for the detector model. Valid values: east/db" << endl;
return 1;
}
// Reading and storing vocabulary for text recognition
CV_Assert(!vocPath.empty());
ifstream vocFile;
vocFile.open(samples::findFile(vocPath));
CV_Assert(vocFile.is_open());
std::string vocLine;
vector<std::string> vocabulary;
while (getline(vocFile, vocLine)) {
vocabulary.push_back(vocLine);
}
processFrame(frame, detResults, ocr, imreadRGB, board, fontFace, size, weight, vocabulary);
return 0;
}
// Performs a perspective transform for a four-point region
static void fourPointsTransform(const Mat& frame, const Point2f vertices[], Mat& result) {
const Size outputSize = Size(100, 32);
// Defining target vertices for the perspective transform
Point2f targetVertices[4] = {
Point(0, outputSize.height - 1),
Point(0, 0),
Point(outputSize.width - 1, 0),
Point(outputSize.width - 1, outputSize.height - 1)
};
// Computing the perspective transform matrix
Mat rotationMatrix = getPerspectiveTransform(vertices, targetVertices);
// Applying the perspective transform to the region
warpPerspective(frame, result, rotationMatrix, outputSize);
}
void processFrame(
const Mat& frame,
const vector<vector<Point>>& detResults,
const std::string& ocr_model,
bool imreadRGB,
Mat& board,
FontFace& fontFace,
int fontSize,
int fontWeight,
const vector<std::string>& vocabulary
) {
if (detResults.size() > 0) {
// Text Recognition
Mat recInput;
if (!imreadRGB) {
cvtColor(frame, recInput, cv::COLOR_BGR2GRAY);
} else {
recInput = frame;
}
vector<vector<Point>> contours;
for (uint i = 0; i < detResults.size(); i++) {
const auto& quadrangle = detResults[i];
CV_CheckEQ(quadrangle.size(), (size_t)4, "");
contours.emplace_back(quadrangle);
vector<Point2f> quadrangle_2f;
for (int j = 0; j < 4; j++)
quadrangle_2f.emplace_back(detResults[i][j]);
// Cropping the detected text region using a four-point transform
Mat cropped;
fourPointsTransform(recInput, &quadrangle_2f[0], cropped);
if(!ocr_model.empty()){
TextRecognitionModel recognizer(ocr_model);
recognizer.setVocabulary(vocabulary);
recognizer.setDecodeType("CTC-greedy");
// Setting input parameters for the recognition model
double recScale = 1.0 / 127.5;
Scalar recMean = Scalar(127.5);
Size recInputSize = Size(100, 32);
recognizer.setInputParams(recScale, recInputSize, recMean);
// Recognizing text from the cropped image
string recognitionResult = recognizer.recognize(cropped);
cout << i << ": '" << recognitionResult << "'" << endl;
// Displaying the recognized text on the image
putText(board, recognitionResult, Point(detResults[i][1].x, detResults[i][0].y), Scalar(0, 0, 0), fontFace, fontSize, fontWeight);
}
else{
cout << "[WARN] Please pass the path to the ocr model using --ocr_model to get the recognised text." << endl;
}
}
// Drawing detected text regions on the image
polylines(board, contours, true, Scalar(200, 255, 200), 1);
polylines(frame, contours, true, Scalar(0, 255, 0), 1);
} else {
cout << "No Text Detected." << endl;
}
// Displaying the final image with detected and recognized text
Mat stacked;
hconcat(frame, board, stacked);
imshow("Text Detection and Recognition", stacked);
waitKey(0);
}
+194
View File
@@ -0,0 +1,194 @@
'''
Text detection model (EAST): https://github.com/argman/EAST
Download link for EAST model: https://www.dropbox.com/s/r2ingd0l3zt8hxs/frozen_east_text_detection.tar.gz?dl=1
DB detector model:
https://drive.google.com/uc?export=download&id=17_ABp79PlFt9yPCxSaarVc_DKTmrSGGf
CRNN Text recognition model sourced from: https://github.com/meijieru/crnn.pytorch
How to convert from .pb to .onnx:
Using classes from: https://github.com/meijieru/crnn.pytorch/blob/master/models/crnn.py
Additional converted ONNX text recognition models available for direct download:
Download link: https://drive.google.com/drive/folders/1cTbQ3nuZG-EKWak6emD_s8_hHXWz7lAr?usp=sharing
These models are taken from: https://github.com/clovaai/deep-text-recognition-benchmark
Importing and using the CRNN model in PyTorch:
import torch
from models.crnn import CRNN
model = CRNN(32, 1, 37, 256)
model.load_state_dict(torch.load('crnn.pth'))
dummy_input = torch.randn(1, 1, 32, 100)
torch.onnx.export(model, dummy_input, "crnn.onnx", verbose=True)
Usage: python text_detection.py DB --ocr_model=<path to recognition model>
'''
import os
import cv2
import argparse
import numpy as np
from common import *
def help():
print(
'''
Use this script for Text Detection and Recognition using OpenCV.
Firstly, download required models using `download_models.py` (if not already done). Set environment variable OPENCV_DOWNLOAD_CACHE_DIR to specify where models should be downloaded. Also, point OPENCV_SAMPLES_DATA_PATH to opencv/samples/data.
Example: python download_models.py East
python download_models.py OCR
To run:
Example: python text_detection.py modelName(i.e. DB or East)
Detection model path can also be specified using --model argument and ocr model can be specified using --ocr_model.
'''
)
############ Add argument parser for command line arguments ############
def get_args_parser():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--input', default='right.jpg',
help='Path to input image or video file. Skip this argument to capture frames from a camera.')
parser.add_argument('--zoo', default=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'models.yml'),
help='An optional path to file with preprocessing parameters.')
parser.add_argument('--thr', type=float, default=0.5,
help='Confidence threshold.')
parser.add_argument('--nms', type=float, default=0.4,
help='Non-maximum suppression threshold.')
parser.add_argument('--binary_threshold', type=float, default=0.3,
help='Confidence threshold for the binary map in DB detector. ')
parser.add_argument('--polygon_threshold', type=float, default=0.5,
help='Confidence threshold for polygons in DB detector.')
parser.add_argument('--max_candidate', type=int, default=200,
help='Max candidates for polygons in DB detector.')
parser.add_argument('--unclip_ratio', type=float, default=2.0,
help='Unclip ratio for DB detector.')
parser.add_argument('--vocabulary_path', default='alphabet_36.txt',
help='Path to vocabulary file.')
args, _ = parser.parse_known_args()
add_preproc_args(args.zoo, parser, 'text_detection', prefix="")
add_preproc_args(args.zoo, parser, 'text_recognition', prefix="ocr_")
parser = argparse.ArgumentParser(parents=[parser],
description='Text Detection and Recognition using OpenCV.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
return parser.parse_args()
def fourPointsTransform(frame, vertices):
vertices = np.asarray(vertices)
outputSize = (100, 32)
targetVertices = np.array([
[0, outputSize[1] - 1],
[0, 0],
[outputSize[0] - 1, 0],
[outputSize[0] - 1, outputSize[1] - 1]], dtype="float32")
rotationMatrix = cv2.getPerspectiveTransform(vertices, targetVertices)
result = cv2.warpPerspective(frame, rotationMatrix, outputSize)
return result
def main():
args = get_args_parser()
if args.alias is None or hasattr(args, 'help'):
help()
exit(1)
args.model = findModel(args.model, args.sha1)
args.ocr_model = findModel(args.ocr_model, args.ocr_sha1)
args.input = findFile(args.input)
args.vocabulary_path = findFile(args.vocabulary_path)
frame = cv2.imread(args.input)
board = np.ones_like(frame)*255
stdSize = 0.8
stdWeight = 2
stdImgSize = 512
imgWidth = min(frame.shape[:2])
fontSize = (stdSize*imgWidth)/stdImgSize
fontThickness = max(1,(stdWeight*imgWidth)//stdImgSize)
if(args.alias == "DB"):
# DB Detector initialization
detector = cv2.dnn_TextDetectionModel_DB(args.model)
detector.setBinaryThreshold(args.binary_threshold)
detector.setPolygonThreshold(args.polygon_threshold)
detector.setUnclipRatio(args.unclip_ratio)
detector.setMaxCandidates(args.max_candidate)
# Setting input parameters specific to the DB model
detector.setInputParams(scale=args.scale, size=(args.width, args.height), mean=args.mean)
# Performing text detection
detResults = detector.detect(frame)
elif(args.alias == "East"):
# EAST Detector initialization
detector = cv2.dnn_TextDetectionModel_EAST(args.model)
detector.setConfidenceThreshold(args.thr)
detector.setNMSThreshold(args.nms)
# Setting input parameters specific to EAST model
detector.setInputParams(scale=args.scale, size=(args.width, args.height), mean=args.mean, swapRB=True)
# Perfroming text detection
detResults = detector.detect(frame)
# Open the vocabulary file and read lines into a list
with open(args.vocabulary_path, 'r') as voc_file:
vocabulary = [line.strip() for line in voc_file]
if args.ocr_model is None:
print("[ERROR] Please pass the path to the ocr model using --ocr_model to run the sample")
exit(1)
# Initialize the text recognition model with the specified model path
recognizer = cv2.dnn_TextRecognitionModel(args.ocr_model)
# Set the vocabulary for the model
recognizer.setVocabulary(vocabulary)
# Set the decoding method to 'CTC-greedy'
recognizer.setDecodeType("CTC-greedy")
recScale = 1.0 / 127.5
recMean = (127.5, 127.5, 127.5)
recInputSize = (100, 32)
recognizer.setInputParams(scale=recScale, size=recInputSize, mean=recMean)
if len(detResults) > 0:
recInput = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) if not args.rgb else frame.copy()
contours = []
for i, (quadrangle, _) in enumerate(zip(detResults[0], detResults[1])):
if isinstance(quadrangle, np.ndarray):
quadrangle = np.array(quadrangle).astype(np.float32)
if quadrangle is None or len(quadrangle) != 4:
print("Skipping a quadrangle with incorrect points or transformation failed.")
continue
contours.append(np.array(quadrangle, dtype=np.int32))
cropped = fourPointsTransform(recInput, quadrangle)
recognitionResult = recognizer.recognize(cropped)
print(f"{i}: '{recognitionResult}'")
try:
text_origin = (int(quadrangle[1][0]), int(quadrangle[0][1]))
cv2.putText(board, recognitionResult, text_origin, cv2.FONT_HERSHEY_SIMPLEX, fontSize, (0, 0, 0), fontThickness)
except Exception as e:
print("Failed to write text on the frame:", e)
else:
print("Skipping a detection with invalid format:", quadrangle)
cv2.polylines(frame, contours, True, (0, 255, 0), 1)
cv2.polylines(board, contours, True, (200, 255, 200), 1)
else:
print("No Text Detected.")
stacked = cv2.hconcat([frame, board])
cv2.imshow("Text Detection and Recognition", stacked)
cv2.waitKey(0)
if __name__ == "__main__":
main()
+333
View File
@@ -0,0 +1,333 @@
def tokenize(s):
tokens = []
token = ""
isString = False
isComment = False
for symbol in s:
isComment = (isComment and symbol != '\n') or (not isString and symbol == '#')
if isComment:
continue
if symbol == ' ' or symbol == '\t' or symbol == '\r' or symbol == '\'' or \
symbol == '\n' or symbol == ':' or symbol == '\"' or symbol == ';' or \
symbol == ',':
if (symbol == '\"' or symbol == '\'') and isString:
tokens.append(token)
token = ""
else:
if isString:
token += symbol
elif token:
tokens.append(token)
token = ""
isString = (symbol == '\"' or symbol == '\'') ^ isString
elif symbol == '{' or symbol == '}' or symbol == '[' or symbol == ']':
if token:
tokens.append(token)
token = ""
tokens.append(symbol)
else:
token += symbol
if token:
tokens.append(token)
return tokens
def parseMessage(tokens, idx):
msg = {}
assert(tokens[idx] == '{')
isArray = False
while True:
if not isArray:
idx += 1
if idx < len(tokens):
fieldName = tokens[idx]
else:
return None
if fieldName == '}':
break
idx += 1
fieldValue = tokens[idx]
if fieldValue == '{':
embeddedMsg, idx = parseMessage(tokens, idx)
if fieldName in msg:
msg[fieldName].append(embeddedMsg)
else:
msg[fieldName] = [embeddedMsg]
elif fieldValue == '[':
isArray = True
elif fieldValue == ']':
isArray = False
else:
if fieldName in msg:
msg[fieldName].append(fieldValue)
else:
msg[fieldName] = [fieldValue]
return msg, idx
def readTextMessage(filePath):
if not filePath:
return {}
with open(filePath, 'rt') as f:
content = f.read()
tokens = tokenize('{' + content + '}')
msg = parseMessage(tokens, 0)
return msg[0] if msg else {}
def listToTensor(values):
if all([isinstance(v, float) for v in values]):
dtype = 'DT_FLOAT'
field = 'float_val'
elif all([isinstance(v, int) for v in values]):
dtype = 'DT_INT32'
field = 'int_val'
else:
raise Exception('Wrong values types')
msg = {
'tensor': {
'dtype': dtype,
'tensor_shape': {
'dim': {
'size': len(values)
}
}
}
}
msg['tensor'][field] = values
return msg
def addConstNode(name, values, graph_def):
node = NodeDef()
node.name = name
node.op = 'Const'
node.addAttr('value', values)
graph_def.node.extend([node])
def addSlice(inp, out, begins, sizes, graph_def):
beginsNode = NodeDef()
beginsNode.name = out + '/begins'
beginsNode.op = 'Const'
beginsNode.addAttr('value', begins)
graph_def.node.extend([beginsNode])
sizesNode = NodeDef()
sizesNode.name = out + '/sizes'
sizesNode.op = 'Const'
sizesNode.addAttr('value', sizes)
graph_def.node.extend([sizesNode])
sliced = NodeDef()
sliced.name = out
sliced.op = 'Slice'
sliced.input.append(inp)
sliced.input.append(beginsNode.name)
sliced.input.append(sizesNode.name)
graph_def.node.extend([sliced])
def addReshape(inp, out, shape, graph_def):
shapeNode = NodeDef()
shapeNode.name = out + '/shape'
shapeNode.op = 'Const'
shapeNode.addAttr('value', shape)
graph_def.node.extend([shapeNode])
reshape = NodeDef()
reshape.name = out
reshape.op = 'Reshape'
reshape.input.append(inp)
reshape.input.append(shapeNode.name)
graph_def.node.extend([reshape])
def addSoftMax(inp, out, graph_def):
softmax = NodeDef()
softmax.name = out
softmax.op = 'Softmax'
softmax.addAttr('axis', -1)
softmax.input.append(inp)
graph_def.node.extend([softmax])
def addFlatten(inp, out, graph_def):
flatten = NodeDef()
flatten.name = out
flatten.op = 'Flatten'
flatten.input.append(inp)
graph_def.node.extend([flatten])
class NodeDef:
def __init__(self):
self.input = []
self.name = ""
self.op = ""
self.attr = {}
def addAttr(self, key, value):
assert(not key in self.attr)
if isinstance(value, bool):
self.attr[key] = {'b': value}
elif isinstance(value, int):
self.attr[key] = {'i': value}
elif isinstance(value, float):
self.attr[key] = {'f': value}
elif isinstance(value, str):
self.attr[key] = {'s': value}
elif isinstance(value, list):
self.attr[key] = listToTensor(value)
else:
raise Exception('Unknown type of attribute ' + key)
def Clear(self):
self.input = []
self.name = ""
self.op = ""
self.attr = {}
class GraphDef:
def __init__(self):
self.node = []
def save(self, filePath):
with open(filePath, 'wt') as f:
def printAttr(d, indent):
indent = ' ' * indent
for key, value in sorted(d.items(), key=lambda x:x[0].lower()):
value = value if isinstance(value, list) else [value]
for v in value:
if isinstance(v, dict):
f.write(indent + key + ' {\n')
printAttr(v, len(indent) + 2)
f.write(indent + '}\n')
else:
isString = False
if isinstance(v, str) and not v.startswith('DT_'):
try:
float(v)
except:
isString = True
if isinstance(v, bool):
printed = 'true' if v else 'false'
elif v == 'true' or v == 'false':
printed = 'true' if v == 'true' else 'false'
elif isString:
printed = '\"%s\"' % v
else:
printed = str(v)
f.write(indent + key + ': ' + printed + '\n')
for node in self.node:
f.write('node {\n')
f.write(' name: \"%s\"\n' % node.name)
f.write(' op: \"%s\"\n' % node.op)
for inp in node.input:
f.write(' input: \"%s\"\n' % inp)
for key, value in sorted(node.attr.items(), key=lambda x:x[0].lower()):
f.write(' attr {\n')
f.write(' key: \"%s\"\n' % key)
f.write(' value {\n')
printAttr(value, 6)
f.write(' }\n')
f.write(' }\n')
f.write('}\n')
def parseTextGraph(filePath):
msg = readTextMessage(filePath)
graph = GraphDef()
for node in msg['node']:
graphNode = NodeDef()
graphNode.name = node['name'][0]
graphNode.op = node['op'][0]
graphNode.input = node['input'] if 'input' in node else []
if 'attr' in node:
for attr in node['attr']:
graphNode.attr[attr['key'][0]] = attr['value'][0]
graph.node.append(graphNode)
return graph
# Removes Identity nodes
def removeIdentity(graph_def):
identities = {}
for node in graph_def.node:
if node.op == 'Identity' or node.op == 'IdentityN':
inp = node.input[0]
if inp in identities:
identities[node.name] = identities[inp]
else:
identities[node.name] = inp
graph_def.node.remove(node)
for node in graph_def.node:
for i in range(len(node.input)):
if node.input[i] in identities:
node.input[i] = identities[node.input[i]]
def removeUnusedNodesAndAttrs(to_remove, graph_def):
unusedAttrs = ['T', 'Tshape', 'N', 'Tidx', 'Tdim', 'use_cudnn_on_gpu',
'Index', 'Tperm', 'is_training', 'Tpaddings']
removedNodes = []
for i in reversed(range(len(graph_def.node))):
op = graph_def.node[i].op
name = graph_def.node[i].name
if to_remove(name, op):
if op != 'Const':
removedNodes.append(name)
del graph_def.node[i]
else:
for attr in unusedAttrs:
if attr in graph_def.node[i].attr:
del graph_def.node[i].attr[attr]
# Remove references to removed nodes except Const nodes.
for node in graph_def.node:
for i in reversed(range(len(node.input))):
if node.input[i] in removedNodes:
del node.input[i]
def writeTextGraph(modelPath, outputPath, outNodes):
try:
import cv2 as cv
cv.dnn.writeTextGraph(modelPath, outputPath)
except:
import tensorflow as tf
from tensorflow.tools.graph_transforms import TransformGraph
with tf.gfile.FastGFile(modelPath, 'rb') as f:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
graph_def = TransformGraph(graph_def, ['image_tensor'], outNodes, ['sort_by_execution_order'])
for node in graph_def.node:
if node.op == 'Const':
if 'value' in node.attr and node.attr['value'].tensor.tensor_content:
node.attr['value'].tensor.tensor_content = b''
tf.train.write_graph(graph_def, "", outputPath, as_text=True)
+236
View File
@@ -0,0 +1,236 @@
# This file is a part of OpenCV project.
# It is a subject to the license terms in the LICENSE file found in the top-level directory
# of this distribution and at http://opencv.org/license.html.
#
# Copyright (C) 2020, Intel Corporation, all rights reserved.
# Third party copyrights are property of their respective owners.
#
# Use this script to get the text graph representation (.pbtxt) of EfficientDet
# deep learning network trained in https://github.com/google/automl.
# Then you can import it with a binary frozen graph (.pb) using readNetFromTensorflow() function.
# See details and examples on the following wiki page: https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API
import argparse
import re
from math import sqrt
from tf_text_graph_common import *
class AnchorGenerator:
def __init__(self, min_level, aspect_ratios, num_scales, anchor_scale):
self.min_level = min_level
self.aspect_ratios = aspect_ratios
self.anchor_scale = anchor_scale
self.scales = [2**(float(s) / num_scales) for s in range(num_scales)]
def get(self, layer_id):
widths = []
heights = []
for s in self.scales:
for a in self.aspect_ratios:
base_anchor_size = 2**(self.min_level + layer_id) * self.anchor_scale
heights.append(base_anchor_size * s * a[1])
widths.append(base_anchor_size * s * a[0])
return widths, heights
def createGraph(modelPath, outputPath, min_level, aspect_ratios, num_scales,
anchor_scale, num_classes, image_width, image_height):
print('Min level: %d' % min_level)
print('Anchor scale: %f' % anchor_scale)
print('Num scales: %d' % num_scales)
print('Aspect ratios: %s' % str(aspect_ratios))
print('Number of classes: %d' % num_classes)
print('Input image size: %dx%d' % (image_width, image_height))
# Read the graph.
_inpNames = ['image_arrays']
outNames = ['detections']
writeTextGraph(modelPath, outputPath, outNames)
graph_def = parseTextGraph(outputPath)
def getUnconnectedNodes():
unconnected = []
for node in graph_def.node:
if node.op == 'Const':
continue
unconnected.append(node.name)
for inp in node.input:
if inp in unconnected:
unconnected.remove(inp)
return unconnected
nodesToKeep = ['truediv'] # Keep preprocessing nodes
removeIdentity(graph_def)
scopesToKeep = ('image_arrays', 'efficientnet', 'resample_p6', 'resample_p7',
'fpn_cells', 'class_net', 'box_net', 'Reshape', 'concat')
addConstNode('scale_w', [2.0], graph_def)
addConstNode('scale_h', [2.0], graph_def)
nodesToKeep += ['scale_w', 'scale_h']
for node in graph_def.node:
if re.match('efficientnet-(.*)/blocks_\d+/se/mul_1', node.name):
node.input[0], node.input[1] = node.input[1], node.input[0]
if re.match('fpn_cells/cell_\d+/fnode\d+/resample(.*)/nearest_upsampling/Reshape_1$', node.name):
node.op = 'ResizeNearestNeighbor'
node.input[1] = 'scale_w'
node.input.append('scale_h')
for inpNode in graph_def.node:
if inpNode.name == node.name[:node.name.rfind('_')]:
node.input[0] = inpNode.input[0]
if re.match('box_net/box-predict(_\d)*/separable_conv2d$', node.name):
node.addAttr('loc_pred_transposed', True)
# Replace RealDiv to Mul with inversed scale for compatibility
if node.op == 'RealDiv':
for inpNode in graph_def.node:
if inpNode.name != node.input[1] or not 'value' in inpNode.attr:
continue
tensor = inpNode.attr['value']['tensor'][0]
if not 'float_val' in tensor:
continue
scale = float(inpNode.attr['value']['tensor'][0]['float_val'][0])
addConstNode(inpNode.name + '/inv', [1.0 / scale], graph_def)
nodesToKeep.append(inpNode.name + '/inv')
node.input[1] = inpNode.name + '/inv'
node.op = 'Mul'
break
def to_remove(name, op):
if name in nodesToKeep:
return False
return op == 'Const' or not name.startswith(scopesToKeep)
removeUnusedNodesAndAttrs(to_remove, graph_def)
# Attach unconnected preprocessing
assert(graph_def.node[1].name == 'truediv' and graph_def.node[1].op == 'RealDiv')
graph_def.node[1].input.insert(0, 'image_arrays')
graph_def.node[2].input.insert(0, 'truediv')
priors_generator = AnchorGenerator(min_level, aspect_ratios, num_scales, anchor_scale)
priorBoxes = []
for i in range(5):
inpName = ''
for node in graph_def.node:
if node.name == 'Reshape_%d' % (i * 2 + 1):
inpName = node.input[0]
break
priorBox = NodeDef()
priorBox.name = 'PriorBox_%d' % i
priorBox.op = 'PriorBox'
priorBox.input.append(inpName)
priorBox.input.append(graph_def.node[0].name) # image_tensor
priorBox.addAttr('flip', False)
priorBox.addAttr('clip', False)
widths, heights = priors_generator.get(i)
priorBox.addAttr('width', widths)
priorBox.addAttr('height', heights)
priorBox.addAttr('variance', [1.0, 1.0, 1.0, 1.0])
graph_def.node.extend([priorBox])
priorBoxes.append(priorBox.name)
addConstNode('concat/axis_flatten', [-1], graph_def)
def addConcatNode(name, inputs, axisNodeName):
concat = NodeDef()
concat.name = name
concat.op = 'ConcatV2'
for inp in inputs:
concat.input.append(inp)
concat.input.append(axisNodeName)
graph_def.node.extend([concat])
addConcatNode('PriorBox/concat', priorBoxes, 'concat/axis_flatten')
sigmoid = NodeDef()
sigmoid.name = 'concat/sigmoid'
sigmoid.op = 'Sigmoid'
sigmoid.input.append('concat')
graph_def.node.extend([sigmoid])
addFlatten(sigmoid.name, sigmoid.name + '/Flatten', graph_def)
addFlatten('concat_1', 'concat_1/Flatten', graph_def)
detectionOut = NodeDef()
detectionOut.name = 'detection_out'
detectionOut.op = 'DetectionOutput'
detectionOut.input.append('concat_1/Flatten')
detectionOut.input.append(sigmoid.name + '/Flatten')
detectionOut.input.append('PriorBox/concat')
detectionOut.addAttr('num_classes', num_classes)
detectionOut.addAttr('share_location', True)
detectionOut.addAttr('background_label_id', num_classes + 1)
detectionOut.addAttr('nms_threshold', 0.6)
detectionOut.addAttr('confidence_threshold', 0.2)
detectionOut.addAttr('top_k', 100)
detectionOut.addAttr('keep_top_k', 100)
detectionOut.addAttr('code_type', "CENTER_SIZE")
graph_def.node.extend([detectionOut])
graph_def.node[0].attr['shape'] = {
'shape': {
'dim': [
{'size': -1},
{'size': image_height},
{'size': image_width},
{'size': 3}
]
}
}
while True:
unconnectedNodes = getUnconnectedNodes()
unconnectedNodes.remove(detectionOut.name)
if not unconnectedNodes:
break
for name in unconnectedNodes:
for i in range(len(graph_def.node)):
if graph_def.node[i].name == name:
del graph_def.node[i]
break
# Save as text
graph_def.save(outputPath)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Run this script to get a text graph of '
'SSD model from TensorFlow Object Detection API. '
'Then pass it with .pb file to cv::dnn::readNetFromTensorflow function.')
parser.add_argument('--input', required=True, help='Path to frozen TensorFlow graph.')
parser.add_argument('--output', required=True, help='Path to output text graph.')
parser.add_argument('--min_level', default=3, type=int, help='Parameter from training config')
parser.add_argument('--num_scales', default=3, type=int, help='Parameter from training config')
parser.add_argument('--anchor_scale', default=4.0, type=float, help='Parameter from training config')
parser.add_argument('--aspect_ratios', default=[1.0, 1.0, 1.4, 0.7, 0.7, 1.4],
nargs='+', type=float, help='Parameter from training config')
parser.add_argument('--num_classes', default=90, type=int, help='Number of classes to detect')
parser.add_argument('--width', default=512, type=int, help='Network input width')
parser.add_argument('--height', default=512, type=int, help='Network input height')
args = parser.parse_args()
ar = args.aspect_ratios
assert(len(ar) % 2 == 0)
ar = list(zip(ar[::2], ar[1::2]))
createGraph(args.input, args.output, args.min_level, ar, args.num_scales,
args.anchor_scale, args.num_classes, args.width, args.height)
+299
View File
@@ -0,0 +1,299 @@
import argparse
import numpy as np
from tf_text_graph_common import *
def createFasterRCNNGraph(modelPath, configPath, outputPath):
scopesToKeep = ('FirstStageFeatureExtractor', 'Conv',
'FirstStageBoxPredictor/BoxEncodingPredictor',
'FirstStageBoxPredictor/ClassPredictor',
'CropAndResize',
'MaxPool2D',
'SecondStageFeatureExtractor',
'SecondStageBoxPredictor',
'Preprocessor/sub',
'Preprocessor/mul',
'image_tensor')
scopesToIgnore = ('FirstStageFeatureExtractor/Assert',
'FirstStageFeatureExtractor/Shape',
'FirstStageFeatureExtractor/strided_slice',
'FirstStageFeatureExtractor/GreaterEqual',
'FirstStageFeatureExtractor/LogicalAnd')
# Load a config file.
config = readTextMessage(configPath)
config = config['model'][0]['faster_rcnn'][0]
num_classes = int(config['num_classes'][0])
grid_anchor_generator = config['first_stage_anchor_generator'][0]['grid_anchor_generator'][0]
scales = [float(s) for s in grid_anchor_generator['scales']]
aspect_ratios = [float(ar) for ar in grid_anchor_generator['aspect_ratios']]
width_stride = float(grid_anchor_generator['width_stride'][0])
height_stride = float(grid_anchor_generator['height_stride'][0])
feature_extractor = config['feature_extractor'][0]
if 'type' in feature_extractor and feature_extractor['type'][0] == 'faster_rcnn_nas':
features_stride = 16.0
else:
features_stride = float(feature_extractor['first_stage_features_stride'][0])
first_stage_nms_iou_threshold = float(config['first_stage_nms_iou_threshold'][0])
first_stage_max_proposals = int(config['first_stage_max_proposals'][0])
print('Number of classes: %d' % num_classes)
print('Scales: %s' % str(scales))
print('Aspect ratios: %s' % str(aspect_ratios))
print('Width stride: %f' % width_stride)
print('Height stride: %f' % height_stride)
print('Features stride: %f' % features_stride)
# Read the graph.
writeTextGraph(modelPath, outputPath, ['num_detections', 'detection_scores', 'detection_boxes', 'detection_classes'])
graph_def = parseTextGraph(outputPath)
removeIdentity(graph_def)
nodesToKeep = []
def to_remove(name, op):
if name in nodesToKeep:
return False
return op == 'Const' or name.startswith(scopesToIgnore) or not name.startswith(scopesToKeep) or \
(name.startswith('CropAndResize') and op != 'CropAndResize')
# Fuse atrous convolutions (with dilations).
nodesMap = {node.name: node for node in graph_def.node}
for node in reversed(graph_def.node):
if node.op == 'BatchToSpaceND':
del node.input[2]
conv = nodesMap[node.input[0]]
spaceToBatchND = nodesMap[conv.input[0]]
# Extract paddings
stridedSlice = nodesMap[spaceToBatchND.input[2]]
assert(stridedSlice.op == 'StridedSlice')
pack = nodesMap[stridedSlice.input[0]]
assert(pack.op == 'Pack')
padNodeH = nodesMap[nodesMap[pack.input[0]].input[0]]
padNodeW = nodesMap[nodesMap[pack.input[1]].input[0]]
padH = int(padNodeH.attr['value']['tensor'][0]['int_val'][0])
padW = int(padNodeW.attr['value']['tensor'][0]['int_val'][0])
paddingsNode = NodeDef()
paddingsNode.name = conv.name + '/paddings'
paddingsNode.op = 'Const'
paddingsNode.addAttr('value', [padH, padH, padW, padW])
graph_def.node.insert(graph_def.node.index(spaceToBatchND), paddingsNode)
nodesToKeep.append(paddingsNode.name)
spaceToBatchND.input[2] = paddingsNode.name
removeUnusedNodesAndAttrs(to_remove, graph_def)
# Connect input node to the first layer
assert(graph_def.node[0].op == 'Placeholder')
graph_def.node[1].input.insert(0, graph_def.node[0].name)
# Temporarily remove top nodes.
topNodes = []
while True:
node = graph_def.node.pop()
topNodes.append(node)
if node.op == 'CropAndResize':
break
addReshape('FirstStageBoxPredictor/ClassPredictor/BiasAdd',
'FirstStageBoxPredictor/ClassPredictor/reshape_1', [0, -1, 2], graph_def)
addSoftMax('FirstStageBoxPredictor/ClassPredictor/reshape_1',
'FirstStageBoxPredictor/ClassPredictor/softmax', graph_def) # Compare with Reshape_4
addFlatten('FirstStageBoxPredictor/ClassPredictor/softmax',
'FirstStageBoxPredictor/ClassPredictor/softmax/flatten', graph_def)
# Compare with FirstStageBoxPredictor/BoxEncodingPredictor/BiasAdd
addFlatten('FirstStageBoxPredictor/BoxEncodingPredictor/BiasAdd',
'FirstStageBoxPredictor/BoxEncodingPredictor/flatten', graph_def)
proposals = NodeDef()
proposals.name = 'proposals' # Compare with ClipToWindow/Gather/Gather (NOTE: normalized)
proposals.op = 'PriorBox'
proposals.input.append('FirstStageBoxPredictor/BoxEncodingPredictor/BiasAdd')
proposals.input.append(graph_def.node[0].name) # image_tensor
proposals.addAttr('flip', False)
proposals.addAttr('clip', True)
proposals.addAttr('step', features_stride)
proposals.addAttr('offset', 0.0)
proposals.addAttr('variance', [0.1, 0.1, 0.2, 0.2])
widths = []
heights = []
for a in aspect_ratios:
for s in scales:
ar = np.sqrt(a)
heights.append((height_stride**2) * s / ar)
widths.append((width_stride**2) * s * ar)
proposals.addAttr('width', widths)
proposals.addAttr('height', heights)
graph_def.node.extend([proposals])
# Compare with Reshape_5
detectionOut = NodeDef()
detectionOut.name = 'detection_out'
detectionOut.op = 'DetectionOutput'
detectionOut.input.append('FirstStageBoxPredictor/BoxEncodingPredictor/flatten')
detectionOut.input.append('FirstStageBoxPredictor/ClassPredictor/softmax/flatten')
detectionOut.input.append('proposals')
detectionOut.addAttr('num_classes', 2)
detectionOut.addAttr('share_location', True)
detectionOut.addAttr('background_label_id', 0)
detectionOut.addAttr('nms_threshold', first_stage_nms_iou_threshold)
detectionOut.addAttr('top_k', 6000)
detectionOut.addAttr('code_type', "CENTER_SIZE")
detectionOut.addAttr('keep_top_k', first_stage_max_proposals)
detectionOut.addAttr('clip', False)
graph_def.node.extend([detectionOut])
addConstNode('clip_by_value/lower', [0.0], graph_def)
addConstNode('clip_by_value/upper', [1.0], graph_def)
clipByValueNode = NodeDef()
clipByValueNode.name = 'detection_out/clip_by_value'
clipByValueNode.op = 'ClipByValue'
clipByValueNode.input.append('detection_out')
clipByValueNode.input.append('clip_by_value/lower')
clipByValueNode.input.append('clip_by_value/upper')
graph_def.node.extend([clipByValueNode])
# Save as text.
for node in reversed(topNodes):
graph_def.node.extend([node])
addSoftMax('SecondStageBoxPredictor/Reshape_1', 'SecondStageBoxPredictor/Reshape_1/softmax', graph_def)
addSlice('SecondStageBoxPredictor/Reshape_1/softmax',
'SecondStageBoxPredictor/Reshape_1/slice',
[0, 0, 1], [-1, -1, -1], graph_def)
addReshape('SecondStageBoxPredictor/Reshape_1/slice',
'SecondStageBoxPredictor/Reshape_1/Reshape', [1, -1], graph_def)
# Replace Flatten subgraph onto a single node.
cropAndResizeNodeName = ''
for i in reversed(range(len(graph_def.node))):
if graph_def.node[i].op == 'CropAndResize':
graph_def.node[i].input.insert(1, 'detection_out/clip_by_value')
cropAndResizeNodeName = graph_def.node[i].name
if graph_def.node[i].name == 'SecondStageBoxPredictor/Reshape':
addConstNode('SecondStageBoxPredictor/Reshape/shape2', [1, -1, 4], graph_def)
graph_def.node[i].input.pop()
graph_def.node[i].input.append('SecondStageBoxPredictor/Reshape/shape2')
if graph_def.node[i].name in ['SecondStageBoxPredictor/Flatten/flatten/Shape',
'SecondStageBoxPredictor/Flatten/flatten/strided_slice',
'SecondStageBoxPredictor/Flatten/flatten/Reshape/shape',
'SecondStageBoxPredictor/Flatten_1/flatten/Shape',
'SecondStageBoxPredictor/Flatten_1/flatten/strided_slice',
'SecondStageBoxPredictor/Flatten_1/flatten/Reshape/shape']:
del graph_def.node[i]
for node in graph_def.node:
if node.name == 'SecondStageBoxPredictor/Flatten/flatten/Reshape' or \
node.name == 'SecondStageBoxPredictor/Flatten_1/flatten/Reshape':
node.op = 'Flatten'
node.input.pop()
if node.name in ['FirstStageBoxPredictor/BoxEncodingPredictor/Conv2D',
'SecondStageBoxPredictor/BoxEncodingPredictor/MatMul']:
node.addAttr('loc_pred_transposed', True)
if node.name.startswith('MaxPool2D'):
assert(node.op == 'MaxPool')
assert(cropAndResizeNodeName)
node.input = [cropAndResizeNodeName]
################################################################################
### Postprocessing
################################################################################
addSlice('detection_out/clip_by_value', 'detection_out/slice', [0, 0, 0, 3], [-1, -1, -1, 4], graph_def)
variance = NodeDef()
variance.name = 'proposals/variance'
variance.op = 'Const'
variance.addAttr('value', [0.1, 0.1, 0.2, 0.2])
graph_def.node.extend([variance])
varianceEncoder = NodeDef()
varianceEncoder.name = 'variance_encoded'
varianceEncoder.op = 'Mul'
varianceEncoder.input.append('SecondStageBoxPredictor/Reshape')
varianceEncoder.input.append(variance.name)
varianceEncoder.addAttr('axis', 2)
graph_def.node.extend([varianceEncoder])
addReshape('detection_out/slice', 'detection_out/slice/reshape', [1, 1, -1], graph_def)
addFlatten('variance_encoded', 'variance_encoded/flatten', graph_def)
detectionOut = NodeDef()
detectionOut.name = 'detection_out_final'
detectionOut.op = 'DetectionOutput'
detectionOut.input.append('variance_encoded/flatten')
detectionOut.input.append('SecondStageBoxPredictor/Reshape_1/Reshape')
detectionOut.input.append('detection_out/slice/reshape')
detectionOut.addAttr('num_classes', num_classes)
detectionOut.addAttr('share_location', False)
detectionOut.addAttr('background_label_id', num_classes + 1)
detectionOut.addAttr('nms_threshold', 0.6)
detectionOut.addAttr('code_type', "CENTER_SIZE")
detectionOut.addAttr('keep_top_k', 100)
detectionOut.addAttr('clip', True)
detectionOut.addAttr('variance_encoded_in_target', True)
graph_def.node.extend([detectionOut])
def getUnconnectedNodes():
unconnected = [node.name for node in graph_def.node]
for node in graph_def.node:
for inp in node.input:
if inp in unconnected:
unconnected.remove(inp)
return unconnected
while True:
unconnectedNodes = getUnconnectedNodes()
unconnectedNodes.remove(detectionOut.name)
if not unconnectedNodes:
break
for name in unconnectedNodes:
for i in range(len(graph_def.node)):
if graph_def.node[i].name == name:
del graph_def.node[i]
break
# Save as text.
graph_def.save(outputPath)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Run this script to get a text graph of '
'Faster-RCNN model from TensorFlow Object Detection API. '
'Then pass it with .pb file to cv::dnn::readNetFromTensorflow function.')
parser.add_argument('--input', required=True, help='Path to frozen TensorFlow graph.')
parser.add_argument('--output', required=True, help='Path to output text graph.')
parser.add_argument('--config', required=True, help='Path to a *.config file is used for training.')
args = parser.parse_args()
createFasterRCNNGraph(args.input, args.config, args.output)
+297
View File
@@ -0,0 +1,297 @@
import argparse
import numpy as np
from tf_text_graph_common import *
parser = argparse.ArgumentParser(description='Run this script to get a text graph of '
'Mask-RCNN model from TensorFlow Object Detection API. '
'Then pass it with .pb file to cv::dnn::readNetFromTensorflow function.')
parser.add_argument('--input', required=True, help='Path to frozen TensorFlow graph.')
parser.add_argument('--output', required=True, help='Path to output text graph.')
parser.add_argument('--config', required=True, help='Path to a *.config file is used for training.')
args = parser.parse_args()
scopesToKeep = ('FirstStageFeatureExtractor', 'Conv',
'FirstStageBoxPredictor/BoxEncodingPredictor',
'FirstStageBoxPredictor/ClassPredictor',
'CropAndResize',
'MaxPool2D',
'SecondStageFeatureExtractor',
'SecondStageBoxPredictor',
'Preprocessor/sub',
'Preprocessor/mul',
'image_tensor')
scopesToIgnore = ('FirstStageFeatureExtractor/Assert',
'FirstStageFeatureExtractor/Shape',
'FirstStageFeatureExtractor/strided_slice',
'FirstStageFeatureExtractor/GreaterEqual',
'FirstStageFeatureExtractor/LogicalAnd',
'Conv/required_space_to_batch_paddings')
# Load a config file.
config = readTextMessage(args.config)
config = config['model'][0]['faster_rcnn'][0]
num_classes = int(config['num_classes'][0])
grid_anchor_generator = config['first_stage_anchor_generator'][0]['grid_anchor_generator'][0]
scales = [float(s) for s in grid_anchor_generator['scales']]
aspect_ratios = [float(ar) for ar in grid_anchor_generator['aspect_ratios']]
width_stride = float(grid_anchor_generator['width_stride'][0])
height_stride = float(grid_anchor_generator['height_stride'][0])
features_stride = float(config['feature_extractor'][0]['first_stage_features_stride'][0])
first_stage_nms_iou_threshold = float(config['first_stage_nms_iou_threshold'][0])
first_stage_max_proposals = int(config['first_stage_max_proposals'][0])
print('Number of classes: %d' % num_classes)
print('Scales: %s' % str(scales))
print('Aspect ratios: %s' % str(aspect_ratios))
print('Width stride: %f' % width_stride)
print('Height stride: %f' % height_stride)
print('Features stride: %f' % features_stride)
# Read the graph.
writeTextGraph(args.input, args.output, ['num_detections', 'detection_scores', 'detection_boxes', 'detection_classes', 'detection_masks'])
graph_def = parseTextGraph(args.output)
removeIdentity(graph_def)
nodesToKeep = []
def to_remove(name, op):
if name in nodesToKeep:
return False
return op == 'Const' or name.startswith(scopesToIgnore) or not name.startswith(scopesToKeep) or \
(name.startswith('CropAndResize') and op != 'CropAndResize')
# Fuse atrous convolutions (with dilations).
nodesMap = {node.name: node for node in graph_def.node}
for node in reversed(graph_def.node):
if node.op == 'BatchToSpaceND':
del node.input[2]
conv = nodesMap[node.input[0]]
spaceToBatchND = nodesMap[conv.input[0]]
paddingsNode = NodeDef()
paddingsNode.name = conv.name + '/paddings'
paddingsNode.op = 'Const'
paddingsNode.addAttr('value', [2, 2, 2, 2])
graph_def.node.insert(graph_def.node.index(spaceToBatchND), paddingsNode)
nodesToKeep.append(paddingsNode.name)
spaceToBatchND.input[2] = paddingsNode.name
removeUnusedNodesAndAttrs(to_remove, graph_def)
# Connect input node to the first layer
assert(graph_def.node[0].op == 'Placeholder')
graph_def.node[1].input.insert(0, graph_def.node[0].name)
# Temporarily remove top nodes.
topNodes = []
numCropAndResize = 0
while True:
node = graph_def.node.pop()
topNodes.append(node)
if node.op == 'CropAndResize':
numCropAndResize += 1
if numCropAndResize == 2:
break
addReshape('FirstStageBoxPredictor/ClassPredictor/BiasAdd',
'FirstStageBoxPredictor/ClassPredictor/reshape_1', [0, -1, 2], graph_def)
addSoftMax('FirstStageBoxPredictor/ClassPredictor/reshape_1',
'FirstStageBoxPredictor/ClassPredictor/softmax', graph_def) # Compare with Reshape_4
addFlatten('FirstStageBoxPredictor/ClassPredictor/softmax',
'FirstStageBoxPredictor/ClassPredictor/softmax/flatten', graph_def)
# Compare with FirstStageBoxPredictor/BoxEncodingPredictor/BiasAdd
addFlatten('FirstStageBoxPredictor/BoxEncodingPredictor/BiasAdd',
'FirstStageBoxPredictor/BoxEncodingPredictor/flatten', graph_def)
proposals = NodeDef()
proposals.name = 'proposals' # Compare with ClipToWindow/Gather/Gather (NOTE: normalized)
proposals.op = 'PriorBox'
proposals.input.append('FirstStageBoxPredictor/BoxEncodingPredictor/BiasAdd')
proposals.input.append(graph_def.node[0].name) # image_tensor
proposals.addAttr('flip', False)
proposals.addAttr('clip', True)
proposals.addAttr('step', features_stride)
proposals.addAttr('offset', 0.0)
proposals.addAttr('variance', [0.1, 0.1, 0.2, 0.2])
widths = []
heights = []
for a in aspect_ratios:
for s in scales:
ar = np.sqrt(a)
heights.append((height_stride**2) * s / ar)
widths.append((width_stride**2) * s * ar)
proposals.addAttr('width', widths)
proposals.addAttr('height', heights)
graph_def.node.extend([proposals])
# Compare with Reshape_5
detectionOut = NodeDef()
detectionOut.name = 'detection_out'
detectionOut.op = 'DetectionOutput'
detectionOut.input.append('FirstStageBoxPredictor/BoxEncodingPredictor/flatten')
detectionOut.input.append('FirstStageBoxPredictor/ClassPredictor/softmax/flatten')
detectionOut.input.append('proposals')
detectionOut.addAttr('num_classes', 2)
detectionOut.addAttr('share_location', True)
detectionOut.addAttr('background_label_id', 0)
detectionOut.addAttr('nms_threshold', first_stage_nms_iou_threshold)
detectionOut.addAttr('top_k', 6000)
detectionOut.addAttr('code_type', "CENTER_SIZE")
detectionOut.addAttr('keep_top_k', first_stage_max_proposals)
detectionOut.addAttr('clip', True)
graph_def.node.extend([detectionOut])
# Save as text.
cropAndResizeNodesNames = []
for node in reversed(topNodes):
if node.op != 'CropAndResize':
graph_def.node.extend([node])
topNodes.pop()
else:
cropAndResizeNodesNames.append(node.name)
if numCropAndResize == 1:
break
else:
graph_def.node.extend([node])
topNodes.pop()
numCropAndResize -= 1
addSoftMax('SecondStageBoxPredictor/Reshape_1', 'SecondStageBoxPredictor/Reshape_1/softmax', graph_def)
addSlice('SecondStageBoxPredictor/Reshape_1/softmax',
'SecondStageBoxPredictor/Reshape_1/slice',
[0, 0, 1], [-1, -1, -1], graph_def)
addReshape('SecondStageBoxPredictor/Reshape_1/slice',
'SecondStageBoxPredictor/Reshape_1/Reshape', [1, -1], graph_def)
# Replace Flatten subgraph onto a single node.
for i in reversed(range(len(graph_def.node))):
if graph_def.node[i].op == 'CropAndResize':
graph_def.node[i].input.insert(1, 'detection_out')
if graph_def.node[i].name == 'SecondStageBoxPredictor/Reshape':
addConstNode('SecondStageBoxPredictor/Reshape/shape2', [1, -1, 4], graph_def)
graph_def.node[i].input.pop()
graph_def.node[i].input.append('SecondStageBoxPredictor/Reshape/shape2')
if graph_def.node[i].name in ['SecondStageBoxPredictor/Flatten/flatten/Shape',
'SecondStageBoxPredictor/Flatten/flatten/strided_slice',
'SecondStageBoxPredictor/Flatten/flatten/Reshape/shape',
'SecondStageBoxPredictor/Flatten_1/flatten/Shape',
'SecondStageBoxPredictor/Flatten_1/flatten/strided_slice',
'SecondStageBoxPredictor/Flatten_1/flatten/Reshape/shape']:
del graph_def.node[i]
for node in graph_def.node:
if node.name == 'SecondStageBoxPredictor/Flatten/flatten/Reshape' or \
node.name == 'SecondStageBoxPredictor/Flatten_1/flatten/Reshape':
node.op = 'Flatten'
node.input.pop()
if node.name in ['FirstStageBoxPredictor/BoxEncodingPredictor/Conv2D',
'SecondStageBoxPredictor/BoxEncodingPredictor/MatMul']:
node.addAttr('loc_pred_transposed', True)
if node.name.startswith('MaxPool2D'):
assert(node.op == 'MaxPool')
assert(len(cropAndResizeNodesNames) == 2)
node.input = [cropAndResizeNodesNames[0]]
del cropAndResizeNodesNames[0]
################################################################################
### Postprocessing
################################################################################
addSlice('detection_out', 'detection_out/slice', [0, 0, 0, 3], [-1, -1, -1, 4], graph_def)
variance = NodeDef()
variance.name = 'proposals/variance'
variance.op = 'Const'
variance.addAttr('value', [0.1, 0.1, 0.2, 0.2])
graph_def.node.extend([variance])
varianceEncoder = NodeDef()
varianceEncoder.name = 'variance_encoded'
varianceEncoder.op = 'Mul'
varianceEncoder.input.append('SecondStageBoxPredictor/Reshape')
varianceEncoder.input.append(variance.name)
varianceEncoder.addAttr('axis', 2)
graph_def.node.extend([varianceEncoder])
addReshape('detection_out/slice', 'detection_out/slice/reshape', [1, 1, -1], graph_def)
addFlatten('variance_encoded', 'variance_encoded/flatten', graph_def)
detectionOut = NodeDef()
detectionOut.name = 'detection_out_final'
detectionOut.op = 'DetectionOutput'
detectionOut.input.append('variance_encoded/flatten')
detectionOut.input.append('SecondStageBoxPredictor/Reshape_1/Reshape')
detectionOut.input.append('detection_out/slice/reshape')
detectionOut.addAttr('num_classes', num_classes)
detectionOut.addAttr('share_location', False)
detectionOut.addAttr('background_label_id', num_classes + 1)
detectionOut.addAttr('nms_threshold', 0.6)
detectionOut.addAttr('code_type', "CENTER_SIZE")
detectionOut.addAttr('keep_top_k',100)
detectionOut.addAttr('clip', True)
detectionOut.addAttr('variance_encoded_in_target', True)
detectionOut.addAttr('confidence_threshold', 0.3)
detectionOut.addAttr('group_by_classes', False)
graph_def.node.extend([detectionOut])
for node in reversed(topNodes):
graph_def.node.extend([node])
if node.name.startswith('MaxPool2D'):
assert(node.op == 'MaxPool')
assert(len(cropAndResizeNodesNames) == 1)
node.input = [cropAndResizeNodesNames[0]]
for i in reversed(range(len(graph_def.node))):
if graph_def.node[i].op == 'CropAndResize':
graph_def.node[i].input.insert(1, 'detection_out_final')
break
graph_def.node[-1].name = 'detection_masks'
graph_def.node[-1].op = 'Sigmoid'
graph_def.node[-1].input.pop()
def getUnconnectedNodes():
unconnected = [node.name for node in graph_def.node]
for node in graph_def.node:
for inp in node.input:
if inp in unconnected:
unconnected.remove(inp)
return unconnected
while True:
unconnectedNodes = getUnconnectedNodes()
unconnectedNodes.remove(graph_def.node[-1].name)
if not unconnectedNodes:
break
for name in unconnectedNodes:
for i in range(len(graph_def.node)):
if graph_def.node[i].name == name:
del graph_def.node[i]
break
# Save as text.
graph_def.save(args.output)
+413
View File
@@ -0,0 +1,413 @@
# This file is a part of OpenCV project.
# It is a subject to the license terms in the LICENSE file found in the top-level directory
# of this distribution and at http://opencv.org/license.html.
#
# Copyright (C) 2018, Intel Corporation, all rights reserved.
# Third party copyrights are property of their respective owners.
#
# Use this script to get the text graph representation (.pbtxt) of SSD-based
# deep learning network trained in TensorFlow Object Detection API.
# Then you can import it with a binary frozen graph (.pb) using readNetFromTensorflow() function.
# See details and examples on the following wiki page: https://github.com/opencv/opencv/wiki/TensorFlow-Object-Detection-API
import argparse
import re
from math import sqrt
from tf_text_graph_common import *
class SSDAnchorGenerator:
def __init__(self, min_scale, max_scale, num_layers, aspect_ratios,
reduce_boxes_in_lowest_layer, image_width, image_height):
self.min_scale = min_scale
self.aspect_ratios = aspect_ratios
self.reduce_boxes_in_lowest_layer = reduce_boxes_in_lowest_layer
self.image_width = image_width
self.image_height = image_height
self.scales = [min_scale + (max_scale - min_scale) * i / (num_layers - 1)
for i in range(num_layers)] + [1.0]
def get(self, layer_id):
if layer_id == 0 and self.reduce_boxes_in_lowest_layer:
widths = [0.1, self.min_scale * sqrt(2.0), self.min_scale * sqrt(0.5)]
heights = [0.1, self.min_scale / sqrt(2.0), self.min_scale / sqrt(0.5)]
else:
widths = [self.scales[layer_id] * sqrt(ar) for ar in self.aspect_ratios]
heights = [self.scales[layer_id] / sqrt(ar) for ar in self.aspect_ratios]
widths += [sqrt(self.scales[layer_id] * self.scales[layer_id + 1])]
heights += [sqrt(self.scales[layer_id] * self.scales[layer_id + 1])]
min_size = min(self.image_width, self.image_height)
widths = [w * min_size for w in widths]
heights = [h * min_size for h in heights]
return widths, heights
class MultiscaleAnchorGenerator:
def __init__(self, min_level, aspect_ratios, scales_per_octave, anchor_scale):
self.min_level = min_level
self.aspect_ratios = aspect_ratios
self.anchor_scale = anchor_scale
self.scales = [2**(float(s) / scales_per_octave) for s in range(scales_per_octave)]
def get(self, layer_id):
widths = []
heights = []
for a in self.aspect_ratios:
for s in self.scales:
base_anchor_size = 2**(self.min_level + layer_id) * self.anchor_scale
ar = sqrt(a)
heights.append(base_anchor_size * s / ar)
widths.append(base_anchor_size * s * ar)
return widths, heights
def createSSDGraph(modelPath, configPath, outputPath):
# Nodes that should be kept.
keepOps = ['Conv2D', 'BiasAdd', 'Add', 'AddV2', 'Relu', 'Relu6', 'Placeholder', 'FusedBatchNorm',
'DepthwiseConv2dNative', 'ConcatV2', 'Mul', 'MaxPool', 'AvgPool', 'Identity',
'Sub', 'ResizeNearestNeighbor', 'Pad', 'FusedBatchNormV3', 'Mean']
# Node with which prefixes should be removed
prefixesToRemove = ('MultipleGridAnchorGenerator/', 'Concatenate/', 'Postprocessor/', 'Preprocessor/map')
# Load a config file.
config = readTextMessage(configPath)
config = config['model'][0]['ssd'][0]
num_classes = int(config['num_classes'][0])
fixed_shape_resizer = config['image_resizer'][0]['fixed_shape_resizer'][0]
image_width = int(fixed_shape_resizer['width'][0])
image_height = int(fixed_shape_resizer['height'][0])
box_predictor = 'convolutional' if 'convolutional_box_predictor' in config['box_predictor'][0] else 'weight_shared_convolutional'
anchor_generator = config['anchor_generator'][0]
if 'ssd_anchor_generator' in anchor_generator:
ssd_anchor_generator = anchor_generator['ssd_anchor_generator'][0]
min_scale = float(ssd_anchor_generator['min_scale'][0])
max_scale = float(ssd_anchor_generator['max_scale'][0])
num_layers = int(ssd_anchor_generator['num_layers'][0])
aspect_ratios = [float(ar) for ar in ssd_anchor_generator['aspect_ratios']]
reduce_boxes_in_lowest_layer = True
if 'reduce_boxes_in_lowest_layer' in ssd_anchor_generator:
reduce_boxes_in_lowest_layer = ssd_anchor_generator['reduce_boxes_in_lowest_layer'][0] == 'true'
priors_generator = SSDAnchorGenerator(min_scale, max_scale, num_layers,
aspect_ratios, reduce_boxes_in_lowest_layer,
image_width, image_height)
print('Scale: [%f-%f]' % (min_scale, max_scale))
print('Aspect ratios: %s' % str(aspect_ratios))
print('Reduce boxes in the lowest layer: %s' % str(reduce_boxes_in_lowest_layer))
elif 'multiscale_anchor_generator' in anchor_generator:
multiscale_anchor_generator = anchor_generator['multiscale_anchor_generator'][0]
min_level = int(multiscale_anchor_generator['min_level'][0])
max_level = int(multiscale_anchor_generator['max_level'][0])
anchor_scale = float(multiscale_anchor_generator['anchor_scale'][0])
aspect_ratios = [float(ar) for ar in multiscale_anchor_generator['aspect_ratios']]
scales_per_octave = int(multiscale_anchor_generator['scales_per_octave'][0])
num_layers = max_level - min_level + 1
priors_generator = MultiscaleAnchorGenerator(min_level, aspect_ratios,
scales_per_octave, anchor_scale)
print('Levels: [%d-%d]' % (min_level, max_level))
print('Anchor scale: %f' % anchor_scale)
print('Scales per octave: %d' % scales_per_octave)
print('Aspect ratios: %s' % str(aspect_ratios))
else:
print('Unknown anchor_generator')
exit(0)
print('Number of classes: %d' % num_classes)
print('Number of layers: %d' % num_layers)
print('box predictor: %s' % box_predictor)
print('Input image size: %dx%d' % (image_width, image_height))
# Read the graph.
outNames = ['num_detections', 'detection_scores', 'detection_boxes', 'detection_classes']
writeTextGraph(modelPath, outputPath, outNames)
graph_def = parseTextGraph(outputPath)
def getUnconnectedNodes():
unconnected = []
for node in graph_def.node:
unconnected.append(node.name)
for inp in node.input:
if inp in unconnected:
unconnected.remove(inp)
return unconnected
def fuse_nodes(nodesToKeep):
# Detect unfused batch normalization nodes and fuse them.
# Add_0 <-- moving_variance, add_y
# Rsqrt <-- Add_0
# Mul_0 <-- Rsqrt, gamma
# Mul_1 <-- input, Mul_0
# Mul_2 <-- moving_mean, Mul_0
# Sub_0 <-- beta, Mul_2
# Add_1 <-- Mul_1, Sub_0
nodesMap = {node.name: node for node in graph_def.node}
subgraphBatchNorm = ['Add',
['Mul', 'input', ['Mul', ['Rsqrt', ['Add', 'moving_variance', 'add_y']], 'gamma']],
['Sub', 'beta', ['Mul', 'moving_mean', 'Mul_0']]]
subgraphBatchNormV2 = ['AddV2',
['Mul', 'input', ['Mul', ['Rsqrt', ['AddV2', 'moving_variance', 'add_y']], 'gamma']],
['Sub', 'beta', ['Mul', 'moving_mean', 'Mul_0']]]
# Detect unfused nearest neighbor resize.
subgraphResizeNN = ['Reshape',
['Mul', ['Reshape', 'input', ['Pack', 'shape_1', 'shape_2', 'shape_3', 'shape_4', 'shape_5']],
'ones'],
['Pack', ['StridedSlice', ['Shape', 'input'], 'stack', 'stack_1', 'stack_2'],
'out_height', 'out_width', 'out_channels']]
def checkSubgraph(node, targetNode, inputs, fusedNodes):
op = targetNode[0]
if node.op == op and (len(node.input) >= len(targetNode) - 1):
fusedNodes.append(node)
for i, inpOp in enumerate(targetNode[1:]):
if isinstance(inpOp, list):
if not node.input[i] in nodesMap or \
not checkSubgraph(nodesMap[node.input[i]], inpOp, inputs, fusedNodes):
return False
else:
inputs[inpOp] = node.input[i]
return True
else:
return False
nodesToRemove = []
for node in graph_def.node:
inputs = {}
fusedNodes = []
if checkSubgraph(node, subgraphBatchNorm, inputs, fusedNodes) or \
checkSubgraph(node, subgraphBatchNormV2, inputs, fusedNodes):
name = node.name
node.Clear()
node.name = name
node.op = 'FusedBatchNorm'
node.input.append(inputs['input'])
node.input.append(inputs['gamma'])
node.input.append(inputs['beta'])
node.input.append(inputs['moving_mean'])
node.input.append(inputs['moving_variance'])
node.addAttr('epsilon', 0.001)
nodesToRemove += fusedNodes[1:]
inputs = {}
fusedNodes = []
if checkSubgraph(node, subgraphResizeNN, inputs, fusedNodes):
name = node.name
node.Clear()
node.name = name
node.op = 'ResizeNearestNeighbor'
node.input.append(inputs['input'])
node.input.append(name + '/output_shape')
out_height_node = nodesMap[inputs['out_height']]
out_width_node = nodesMap[inputs['out_width']]
out_height = int(out_height_node.attr['value']['tensor'][0]['int_val'][0])
out_width = int(out_width_node.attr['value']['tensor'][0]['int_val'][0])
shapeNode = NodeDef()
shapeNode.name = name + '/output_shape'
shapeNode.op = 'Const'
shapeNode.addAttr('value', [out_height, out_width])
graph_def.node.insert(graph_def.node.index(node), shapeNode)
nodesToKeep.append(shapeNode.name)
nodesToRemove += fusedNodes[1:]
for node in nodesToRemove:
graph_def.node.remove(node)
nodesToKeep = []
fuse_nodes(nodesToKeep)
removeIdentity(graph_def)
def to_remove(name, op):
return (not name in nodesToKeep) and \
(op == 'Const' or (not op in keepOps) or name.startswith(prefixesToRemove))
removeUnusedNodesAndAttrs(to_remove, graph_def)
# Connect input node to the first layer
assert(graph_def.node[0].op == 'Placeholder')
try:
input_shape = graph_def.node[0].attr['shape']['shape'][0]['dim']
input_shape[1]['size'] = image_height
input_shape[2]['size'] = image_width
except:
print("Input shapes are undefined")
# assert(graph_def.node[1].op == 'Conv2D')
weights = graph_def.node[1].input[-1]
for i in range(len(graph_def.node[1].input)):
graph_def.node[1].input.pop()
graph_def.node[1].input.append(graph_def.node[0].name)
graph_def.node[1].input.append(weights)
# check and correct the case when preprocessing block is after input
preproc_id = "Preprocessor/"
if graph_def.node[2].name.startswith(preproc_id) and \
graph_def.node[2].input[0].startswith(preproc_id):
if not any(preproc_id in inp for inp in graph_def.node[3].input):
graph_def.node[3].input.insert(0, graph_def.node[2].name)
# Create SSD postprocessing head ###############################################
# Concatenate predictions of classes, predictions of bounding boxes and proposals.
def addConcatNode(name, inputs, axisNodeName):
concat = NodeDef()
concat.name = name
concat.op = 'ConcatV2'
for inp in inputs:
concat.input.append(inp)
concat.input.append(axisNodeName)
graph_def.node.extend([concat])
addConstNode('concat/axis_flatten', [-1], graph_def)
addConstNode('PriorBox/concat/axis', [-2], graph_def)
for label in ['ClassPredictor', 'BoxEncodingPredictor' if box_predictor == 'convolutional' else 'BoxPredictor']:
concatInputs = []
for i in range(num_layers):
# Flatten predictions
flatten = NodeDef()
if box_predictor == 'convolutional':
inpName = 'BoxPredictor_%d/%s/BiasAdd' % (i, label)
else:
if i == 0:
inpName = 'WeightSharedConvolutionalBoxPredictor/%s/BiasAdd' % label
else:
inpName = 'WeightSharedConvolutionalBoxPredictor_%d/%s/BiasAdd' % (i, label)
flatten.input.append(inpName)
flatten.name = inpName + '/Flatten'
flatten.op = 'Flatten'
concatInputs.append(flatten.name)
graph_def.node.extend([flatten])
addConcatNode('%s/concat' % label, concatInputs, 'concat/axis_flatten')
num_matched_layers = 0
for node in graph_def.node:
if re.match('BoxPredictor_\d/BoxEncodingPredictor/convolution', node.name) or \
re.match('BoxPredictor_\d/BoxEncodingPredictor/Conv2D', node.name) or \
re.match('WeightSharedConvolutionalBoxPredictor(_\d)*/BoxPredictor/Conv2D', node.name):
node.addAttr('loc_pred_transposed', True)
num_matched_layers += 1
assert(num_matched_layers == num_layers)
# Add layers that generate anchors (bounding boxes proposals).
priorBoxes = []
boxCoder = config['box_coder'][0]
fasterRcnnBoxCoder = boxCoder['faster_rcnn_box_coder'][0]
boxCoderVariance = [1.0/float(fasterRcnnBoxCoder['x_scale'][0]), 1.0/float(fasterRcnnBoxCoder['y_scale'][0]), 1.0/float(fasterRcnnBoxCoder['width_scale'][0]), 1.0/float(fasterRcnnBoxCoder['height_scale'][0])]
for i in range(num_layers):
priorBox = NodeDef()
priorBox.name = 'PriorBox_%d' % i
priorBox.op = 'PriorBox'
if box_predictor == 'convolutional':
priorBox.input.append('BoxPredictor_%d/BoxEncodingPredictor/BiasAdd' % i)
else:
if i == 0:
priorBox.input.append('WeightSharedConvolutionalBoxPredictor/BoxPredictor/Conv2D')
else:
priorBox.input.append('WeightSharedConvolutionalBoxPredictor_%d/BoxPredictor/BiasAdd' % i)
priorBox.input.append(graph_def.node[0].name) # image_tensor
priorBox.addAttr('flip', False)
priorBox.addAttr('clip', False)
widths, heights = priors_generator.get(i)
priorBox.addAttr('width', widths)
priorBox.addAttr('height', heights)
priorBox.addAttr('variance', boxCoderVariance)
graph_def.node.extend([priorBox])
priorBoxes.append(priorBox.name)
# Compare this layer's output with Postprocessor/Reshape
addConcatNode('PriorBox/concat', priorBoxes, 'concat/axis_flatten')
# Sigmoid for classes predictions and DetectionOutput layer
addReshape('ClassPredictor/concat', 'ClassPredictor/concat3d', [0, -1, num_classes + 1], graph_def)
sigmoid = NodeDef()
sigmoid.name = 'ClassPredictor/concat/sigmoid'
sigmoid.op = 'Sigmoid'
sigmoid.input.append('ClassPredictor/concat3d')
graph_def.node.extend([sigmoid])
addFlatten(sigmoid.name, sigmoid.name + '/Flatten', graph_def)
detectionOut = NodeDef()
detectionOut.name = 'detection_out'
detectionOut.op = 'DetectionOutput'
if box_predictor == 'convolutional':
detectionOut.input.append('BoxEncodingPredictor/concat')
else:
detectionOut.input.append('BoxPredictor/concat')
detectionOut.input.append(sigmoid.name + '/Flatten')
detectionOut.input.append('PriorBox/concat')
detectionOut.addAttr('num_classes', num_classes + 1)
detectionOut.addAttr('share_location', True)
detectionOut.addAttr('background_label_id', 0)
postProcessing = config['post_processing'][0]
batchNMS = postProcessing['batch_non_max_suppression'][0]
if 'iou_threshold' in batchNMS:
detectionOut.addAttr('nms_threshold', float(batchNMS['iou_threshold'][0]))
else:
detectionOut.addAttr('nms_threshold', 0.6)
if 'score_threshold' in batchNMS:
detectionOut.addAttr('confidence_threshold', float(batchNMS['score_threshold'][0]))
else:
detectionOut.addAttr('confidence_threshold', 0.01)
if 'max_detections_per_class' in batchNMS:
detectionOut.addAttr('top_k', int(batchNMS['max_detections_per_class'][0]))
else:
detectionOut.addAttr('top_k', 100)
if 'max_total_detections' in batchNMS:
detectionOut.addAttr('keep_top_k', int(batchNMS['max_total_detections'][0]))
else:
detectionOut.addAttr('keep_top_k', 100)
detectionOut.addAttr('code_type', "CENTER_SIZE")
graph_def.node.extend([detectionOut])
while True:
unconnectedNodes = getUnconnectedNodes()
unconnectedNodes.remove(detectionOut.name)
if not unconnectedNodes:
break
for name in unconnectedNodes:
for i in range(len(graph_def.node)):
if graph_def.node[i].name == name:
del graph_def.node[i]
break
# Save as text.
graph_def.save(outputPath)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Run this script to get a text graph of '
'SSD model from TensorFlow Object Detection API. '
'Then pass it with .pb file to cv::dnn::readNetFromTensorflow function.')
parser.add_argument('--input', required=True, help='Path to frozen TensorFlow graph.')
parser.add_argument('--output', required=True, help='Path to output text graph.')
parser.add_argument('--config', required=True, help='Path to a *.config file is used for training.')
args = parser.parse_args()
createSSDGraph(args.input, args.config, args.output)
+471
View File
@@ -0,0 +1,471 @@
#!/usr/bin/env python3
'''
You can download the Geometric Matching Module model from https://www.dropbox.com/s/tyhc73xa051grjp/cp_vton_gmm.onnx?dl=0
You can download the Try-On Module model from https://www.dropbox.com/s/q2x97ve2h53j66k/cp_vton_tom.onnx?dl=0
You can download the cloth segmentation model from https://www.dropbox.com/s/qag9vzambhhkvxr/lip_jppnet_384.pb?dl=0
You can find the OpenPose proto in opencv_extra/testdata/dnn/openpose_pose_coco.prototxt
and get .caffemodel using opencv_extra/testdata/dnn/download_models.py
'''
import argparse
import os.path
import numpy as np
import cv2 as cv
from numpy import linalg
from common import findFile
from human_parsing import parse_human
backends = (cv.dnn.DNN_BACKEND_DEFAULT, cv.dnn.DNN_BACKEND_INFERENCE_ENGINE, cv.dnn.DNN_BACKEND_OPENCV,
cv.dnn.DNN_BACKEND_VKCOM, cv.dnn.DNN_BACKEND_CUDA)
targets = (cv.dnn.DNN_TARGET_CPU, cv.dnn.DNN_TARGET_OPENCL, cv.dnn.DNN_TARGET_OPENCL_FP16, cv.dnn.DNN_TARGET_MYRIAD, cv.dnn.DNN_TARGET_HDDL,
cv.dnn.DNN_TARGET_VULKAN, cv.dnn.DNN_TARGET_CUDA, cv.dnn.DNN_TARGET_CUDA_FP16)
parser = argparse.ArgumentParser(description='Use this script to run virtial try-on using CP-VTON',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--input_image', '-i', required=True, help='Path to image with person.')
parser.add_argument('--input_cloth', '-c', required=True, help='Path to target cloth image')
parser.add_argument('--gmm_model', '-gmm', default='cp_vton_gmm.onnx', help='Path to Geometric Matching Module .onnx model.')
parser.add_argument('--tom_model', '-tom', default='cp_vton_tom.onnx', help='Path to Try-On Module .onnx model.')
parser.add_argument('--segmentation_model', default='lip_jppnet_384.pb', help='Path to cloth segmentation .pb model.')
parser.add_argument('--openpose_proto', default='openpose_pose_coco.prototxt', help='Path to OpenPose .prototxt model was trained on COCO dataset.')
parser.add_argument('--openpose_model', default='openpose_pose_coco.caffemodel', help='Path to OpenPose .caffemodel model was trained on COCO dataset.')
parser.add_argument('--backend', choices=backends, default=cv.dnn.DNN_BACKEND_DEFAULT, type=int,
help="Choose one of computation backends: "
"%d: automatically (by default), "
"%d: Intel's Deep Learning Inference Engine (https://software.intel.com/openvino-toolkit), "
"%d: OpenCV implementation, "
"%d: VKCOM, "
"%d: CUDA" % backends)
parser.add_argument('--target', choices=targets, default=cv.dnn.DNN_TARGET_CPU, type=int,
help='Choose one of target computation devices: '
'%d: CPU target (by default), '
'%d: OpenCL, '
'%d: OpenCL fp16 (half-float precision), '
'%d: NCS2 VPU, '
'%d: HDDL VPU, '
'%d: Vulkan, '
'%d: CUDA, '
'%d: CUDA fp16 (half-float preprocess)'% targets)
args, _ = parser.parse_known_args()
def get_pose_map(image, proto_path, model_path, backend, target, height=256, width=192):
radius = 5
inp = cv.dnn.blobFromImage(image, 1.0 / 255, (width, height))
net = cv.dnn.readNet(proto_path, model_path)
net.setPreferableBackend(backend)
net.setPreferableTarget(target)
net.setInput(inp)
out = net.forward()
threshold = 0.1
_, out_c, out_h, out_w = out.shape
pose_map = np.zeros((height, width, out_c - 1))
# last label: Background
for i in range(0, out.shape[1] - 1):
heatMap = out[0, i, :, :]
keypoint = np.full((height, width), -1)
_, conf, _, point = cv.minMaxLoc(heatMap)
x = width * point[0] // out_w
y = height * point[1] // out_h
if conf > threshold and x > 0 and y > 0:
keypoint[y - radius:y + radius, x - radius:x + radius] = 1
pose_map[:, :, i] = keypoint
pose_map = pose_map.transpose(2, 0, 1)
return pose_map
class BilinearFilter(object):
"""
PIL bilinear resize implementation
image = image.resize((image_width // 16, image_height // 16), Image.BILINEAR)
"""
def _precompute_coeffs(self, inSize, outSize):
filterscale = max(1.0, inSize / outSize)
ksize = int(np.ceil(filterscale)) * 2 + 1
kk = np.zeros(shape=(outSize * ksize, ), dtype=np.float32)
bounds = np.empty(shape=(outSize * 2, ), dtype=np.int32)
centers = (np.arange(outSize) + 0.5) * filterscale + 0.5
bounds[::2] = np.where(centers - filterscale < 0, 0, centers - filterscale)
bounds[1::2] = np.where(centers + filterscale > inSize, inSize, centers + filterscale) - bounds[::2]
xmins = bounds[::2] - centers + 1
points = np.array([np.arange(row) + xmins[i] for i, row in enumerate(bounds[1::2])]) / filterscale
for xx in range(0, outSize):
point = points[xx]
bilinear = np.where(point < 1.0, 1.0 - abs(point), 0.0)
ww = np.sum(bilinear)
kk[xx * ksize : xx * ksize + bilinear.size] = np.where(ww == 0.0, bilinear, bilinear / ww)
return bounds, kk, ksize
def _resample_horizontal(self, out, img, ksize, bounds, kk):
for yy in range(0, out.shape[0]):
for xx in range(0, out.shape[1]):
xmin = bounds[xx * 2 + 0]
xmax = bounds[xx * 2 + 1]
k = kk[xx * ksize : xx * ksize + xmax]
out[yy, xx] = np.round(np.sum(img[yy, xmin : xmin + xmax] * k))
def _resample_vertical(self, out, img, ksize, bounds, kk):
for yy in range(0, out.shape[0]):
ymin = bounds[yy * 2 + 0]
ymax = bounds[yy * 2 + 1]
k = kk[yy * ksize: yy * ksize + ymax]
out[yy] = np.round(np.sum(img[ymin : ymin + ymax, 0:out.shape[1]] * k[:, np.newaxis], axis=0))
def imaging_resample(self, img, xsize, ysize):
height, width = img.shape[0:2]
bounds_horiz, kk_horiz, ksize_horiz = self._precompute_coeffs(width, xsize)
bounds_vert, kk_vert, ksize_vert = self._precompute_coeffs(height, ysize)
out_hor = np.empty((img.shape[0], xsize), dtype=np.uint8)
self._resample_horizontal(out_hor, img, ksize_horiz, bounds_horiz, kk_horiz)
out = np.empty((ysize, xsize), dtype=np.uint8)
self._resample_vertical(out, out_hor, ksize_vert, bounds_vert, kk_vert)
return out
class CpVton(object):
def __init__(self, gmm_model, tom_model, backend, target):
super(CpVton, self).__init__()
self.gmm_net = cv.dnn.readNet(gmm_model)
self.tom_net = cv.dnn.readNet(tom_model)
self.gmm_net.setPreferableBackend(backend)
self.gmm_net.setPreferableTarget(target)
self.tom_net.setPreferableBackend(backend)
self.tom_net.setPreferableTarget(target)
def prepare_agnostic(self, segm_image, input_image, pose_map, height=256, width=192):
palette = {
'Background' : (0, 0, 0),
'Hat' : (128, 0, 0),
'Hair' : (255, 0, 0),
'Glove' : (0, 85, 0),
'Sunglasses' : (170, 0, 51),
'UpperClothes' : (255, 85, 0),
'Dress' : (0, 0, 85),
'Coat' : (0, 119, 221),
'Socks' : (85, 85, 0),
'Pants' : (0, 85, 85),
'Jumpsuits' : (85, 51, 0),
'Scarf' : (52, 86, 128),
'Skirt' : (0, 128, 0),
'Face' : (0, 0, 255),
'Left-arm' : (51, 170, 221),
'Right-arm' : (0, 255, 255),
'Left-leg' : (85, 255, 170),
'Right-leg' : (170, 255, 85),
'Left-shoe' : (255, 255, 0),
'Right-shoe' : (255, 170, 0)
}
color2label = {val: key for key, val in palette.items()}
head_labels = ['Hat', 'Hair', 'Sunglasses', 'Face', 'Pants', 'Skirt']
segm_image = cv.cvtColor(segm_image, cv.COLOR_BGR2RGB)
phead = np.zeros((1, height, width), dtype=np.float32)
pose_shape = np.zeros((height, width), dtype=np.uint8)
for r in range(height):
for c in range(width):
pixel = tuple(segm_image[r, c])
if tuple(pixel) in color2label:
if color2label[pixel] in head_labels:
phead[0, r, c] = 1
if color2label[pixel] != 'Background':
pose_shape[r, c] = 255
input_image = cv.dnn.blobFromImage(input_image, 1.0 / 127.5, (width, height), mean=(127.5, 127.5, 127.5), swapRB=True)
input_image = input_image.squeeze(0)
img_head = input_image * phead - (1 - phead)
downsample = BilinearFilter()
down = downsample.imaging_resample(pose_shape, width // 16, height // 16)
res_shape = cv.resize(down, (width, height), cv.INTER_LINEAR)
res_shape = cv.dnn.blobFromImage(res_shape, 1.0 / 127.5, mean=(127.5, 127.5, 127.5), swapRB=True)
res_shape = res_shape.squeeze(0)
agnostic = np.concatenate((res_shape, img_head, pose_map), axis=0)
agnostic = np.expand_dims(agnostic, axis=0)
return agnostic.astype(np.float32)
def get_warped_cloth(self, cloth_img, agnostic, height=256, width=192):
cloth = cv.dnn.blobFromImage(cloth_img, 1.0 / 127.5, (width, height), mean=(127.5, 127.5, 127.5), swapRB=True)
self.gmm_net.setInput(agnostic, "input.1")
self.gmm_net.setInput(cloth, "input.18")
theta = self.gmm_net.forward()
grid = self._generate_grid(theta)
warped_cloth = self._bilinear_sampler(cloth, grid).astype(np.float32)
return warped_cloth
def get_tryon(self, agnostic, warp_cloth):
inp = np.concatenate([agnostic, warp_cloth], axis=1)
self.tom_net.setInput(inp)
out = self.tom_net.forward()
p_rendered, m_composite = np.split(out, [3], axis=1)
p_rendered = np.tanh(p_rendered)
m_composite = 1 / (1 + np.exp(-m_composite))
p_tryon = warp_cloth * m_composite + p_rendered * (1 - m_composite)
rgb_p_tryon = cv.cvtColor(p_tryon.squeeze(0).transpose(1, 2, 0), cv.COLOR_BGR2RGB)
rgb_p_tryon = (rgb_p_tryon + 1) / 2
return rgb_p_tryon
def _compute_L_inverse(self, X, Y):
N = X.shape[0]
Xmat = np.tile(X, (1, N))
Ymat = np.tile(Y, (1, N))
P_dist_squared = np.power(Xmat - Xmat.transpose(1, 0), 2) + np.power(Ymat - Ymat.transpose(1, 0), 2)
P_dist_squared[P_dist_squared == 0] = 1
K = np.multiply(P_dist_squared, np.log(P_dist_squared))
O = np.ones([N, 1], dtype=np.float32)
Z = np.zeros([3, 3], dtype=np.float32)
P = np.concatenate([O, X, Y], axis=1)
first = np.concatenate((K, P), axis=1)
second = np.concatenate((P.transpose(1, 0), Z), axis=1)
L = np.concatenate((first, second), axis=0)
Li = linalg.inv(L)
return Li
def _prepare_to_transform(self, out_h=256, out_w=192, grid_size=5):
grid_X, grid_Y = np.meshgrid(np.linspace(-1, 1, out_w), np.linspace(-1, 1, out_h))
grid_X = np.expand_dims(np.expand_dims(grid_X, axis=0), axis=3)
grid_Y = np.expand_dims(np.expand_dims(grid_Y, axis=0), axis=3)
axis_coords = np.linspace(-1, 1, grid_size)
N = grid_size ** 2
P_Y, P_X = np.meshgrid(axis_coords, axis_coords)
P_X = np.reshape(P_X,(-1, 1))
P_Y = np.reshape(P_Y,(-1, 1))
P_X = np.expand_dims(np.expand_dims(np.expand_dims(P_X, axis=2), axis=3), axis=4).transpose(4, 1, 2, 3, 0)
P_Y = np.expand_dims(np.expand_dims(np.expand_dims(P_Y, axis=2), axis=3), axis=4).transpose(4, 1, 2, 3, 0)
return grid_X, grid_Y, N, P_X, P_Y
def _expand_torch(self, X, shape):
if len(X.shape) != len(shape):
return X.flatten().reshape(shape)
else:
axis = [1 if src == dst else dst for src, dst in zip(X.shape, shape)]
return np.tile(X, axis)
def _apply_transformation(self, theta, points, N, P_X, P_Y):
if len(theta.shape) == 2:
theta = np.expand_dims(np.expand_dims(theta, axis=2), axis=3)
batch_size = theta.shape[0]
P_X_base = np.copy(P_X)
P_Y_base = np.copy(P_Y)
Li = self._compute_L_inverse(np.reshape(P_X, (N, -1)), np.reshape(P_Y, (N, -1)))
Li = np.expand_dims(Li, axis=0)
# split theta into point coordinates
Q_X = np.squeeze(theta[:, :N, :, :], axis=3)
Q_Y = np.squeeze(theta[:, N:, :, :], axis=3)
Q_X += self._expand_torch(P_X_base, Q_X.shape)
Q_Y += self._expand_torch(P_Y_base, Q_Y.shape)
points_b = points.shape[0]
points_h = points.shape[1]
points_w = points.shape[2]
P_X = self._expand_torch(P_X, (1, points_h, points_w, 1, N))
P_Y = self._expand_torch(P_Y, (1, points_h, points_w, 1, N))
W_X = self._expand_torch(Li[:,:N,:N], (batch_size, N, N)) @ Q_X
W_Y = self._expand_torch(Li[:,:N,:N], (batch_size, N, N)) @ Q_Y
W_X = np.expand_dims(np.expand_dims(W_X, axis=3), axis=4).transpose(0, 4, 2, 3, 1)
W_X = np.repeat(W_X, points_h, axis=1)
W_X = np.repeat(W_X, points_w, axis=2)
W_Y = np.expand_dims(np.expand_dims(W_Y, axis=3), axis=4).transpose(0, 4, 2, 3, 1)
W_Y = np.repeat(W_Y, points_h, axis=1)
W_Y = np.repeat(W_Y, points_w, axis=2)
A_X = self._expand_torch(Li[:, N:, :N], (batch_size, 3, N)) @ Q_X
A_Y = self._expand_torch(Li[:, N:, :N], (batch_size, 3, N)) @ Q_Y
A_X = np.expand_dims(np.expand_dims(A_X, axis=3), axis=4).transpose(0, 4, 2, 3, 1)
A_X = np.repeat(A_X, points_h, axis=1)
A_X = np.repeat(A_X, points_w, axis=2)
A_Y = np.expand_dims(np.expand_dims(A_Y, axis=3), axis=4).transpose(0, 4, 2, 3, 1)
A_Y = np.repeat(A_Y, points_h, axis=1)
A_Y = np.repeat(A_Y, points_w, axis=2)
points_X_for_summation = np.expand_dims(np.expand_dims(points[:, :, :, 0], axis=3), axis=4)
points_X_for_summation = self._expand_torch(points_X_for_summation, points[:, :, :, 0].shape + (1, N))
points_Y_for_summation = np.expand_dims(np.expand_dims(points[:, :, :, 1], axis=3), axis=4)
points_Y_for_summation = self._expand_torch(points_Y_for_summation, points[:, :, :, 0].shape + (1, N))
if points_b == 1:
delta_X = points_X_for_summation - P_X
delta_Y = points_Y_for_summation - P_Y
else:
delta_X = points_X_for_summation - self._expand_torch(P_X, points_X_for_summation.shape)
delta_Y = points_Y_for_summation - self._expand_torch(P_Y, points_Y_for_summation.shape)
dist_squared = np.power(delta_X, 2) + np.power(delta_Y, 2)
dist_squared[dist_squared == 0] = 1
U = np.multiply(dist_squared, np.log(dist_squared))
points_X_batch = np.expand_dims(points[:,:,:,0], axis=3)
points_Y_batch = np.expand_dims(points[:,:,:,1], axis=3)
if points_b == 1:
points_X_batch = self._expand_torch(points_X_batch, (batch_size, ) + points_X_batch.shape[1:])
points_Y_batch = self._expand_torch(points_Y_batch, (batch_size, ) + points_Y_batch.shape[1:])
points_X_prime = A_X[:,:,:,:,0]+ \
np.multiply(A_X[:,:,:,:,1], points_X_batch) + \
np.multiply(A_X[:,:,:,:,2], points_Y_batch) + \
np.sum(np.multiply(W_X, self._expand_torch(U, W_X.shape)), 4)
points_Y_prime = A_Y[:,:,:,:,0]+ \
np.multiply(A_Y[:,:,:,:,1], points_X_batch) + \
np.multiply(A_Y[:,:,:,:,2], points_Y_batch) + \
np.sum(np.multiply(W_Y, self._expand_torch(U, W_Y.shape)), 4)
return np.concatenate((points_X_prime, points_Y_prime), 3)
def _generate_grid(self, theta):
grid_X, grid_Y, N, P_X, P_Y = self._prepare_to_transform()
warped_grid = self._apply_transformation(theta, np.concatenate((grid_X, grid_Y), axis=3), N, P_X, P_Y)
return warped_grid
def _bilinear_sampler(self, img, grid):
x, y = grid[:,:,:,0], grid[:,:,:,1]
H = img.shape[2]
W = img.shape[3]
max_y = H - 1
max_x = W - 1
# rescale x and y to [0, W-1/H-1]
x = 0.5 * (x + 1.0) * (max_x - 1)
y = 0.5 * (y + 1.0) * (max_y - 1)
# grab 4 nearest corner points for each (x_i, y_i)
x0 = np.floor(x).astype(int)
x1 = x0 + 1
y0 = np.floor(y).astype(int)
y1 = y0 + 1
# calculate deltas
wa = (x1 - x) * (y1 - y)
wb = (x1 - x) * (y - y0)
wc = (x - x0) * (y1 - y)
wd = (x - x0) * (y - y0)
# clip to range [0, H-1/W-1] to not violate img boundaries
x0 = np.clip(x0, 0, max_x)
x1 = np.clip(x1, 0, max_x)
y0 = np.clip(y0, 0, max_y)
y1 = np.clip(y1, 0, max_y)
# get pixel value at corner coords
img = img.reshape(-1, H, W)
Ia = img[:, y0, x0].swapaxes(0, 1)
Ib = img[:, y1, x0].swapaxes(0, 1)
Ic = img[:, y0, x1].swapaxes(0, 1)
Id = img[:, y1, x1].swapaxes(0, 1)
wa = np.expand_dims(wa, axis=0)
wb = np.expand_dims(wb, axis=0)
wc = np.expand_dims(wc, axis=0)
wd = np.expand_dims(wd, axis=0)
# compute output
out = wa*Ia + wb*Ib + wc*Ic + wd*Id
return out
class CorrelationLayer(object):
def __init__(self, params, blobs):
super(CorrelationLayer, self).__init__()
def getMemoryShapes(self, inputs):
fetureAShape = inputs[0]
b, _, h, w = fetureAShape
return [[b, h * w, h, w]]
def forward(self, inputs):
feature_A, feature_B = inputs
b, c, h, w = feature_A.shape
feature_A = feature_A.transpose(0, 1, 3, 2)
feature_A = np.reshape(feature_A, (b, c, h * w))
feature_B = np.reshape(feature_B, (b, c, h * w))
feature_B = feature_B.transpose(0, 2, 1)
feature_mul = feature_B @ feature_A
feature_mul= np.reshape(feature_mul, (b, h, w, h * w))
feature_mul = feature_mul.transpose(0, 1, 3, 2)
correlation_tensor = feature_mul.transpose(0, 2, 1, 3)
correlation_tensor = np.ascontiguousarray(correlation_tensor)
return [correlation_tensor]
if __name__ == "__main__":
if not os.path.isfile(args.gmm_model):
raise OSError("GMM model not exist")
if not os.path.isfile(args.tom_model):
raise OSError("TOM model not exist")
if not os.path.isfile(args.segmentation_model):
raise OSError("Segmentation model not exist")
if not os.path.isfile(findFile(args.openpose_proto)):
raise OSError("OpenPose proto not exist")
if not os.path.isfile(findFile(args.openpose_model)):
raise OSError("OpenPose model not exist")
person_img = cv.imread(args.input_image)
ratio = 256 / 192
inp_h, inp_w, _ = person_img.shape
current_ratio = inp_h / inp_w
if current_ratio > ratio:
center_h = inp_h // 2
out_h = inp_w * ratio
start = int(center_h - out_h // 2)
end = int(center_h + out_h // 2)
person_img = person_img[start:end, ...]
else:
center_w = inp_w // 2
out_w = inp_h / ratio
start = int(center_w - out_w // 2)
end = int(center_w + out_w // 2)
person_img = person_img[:, start:end, :]
cloth_img = cv.imread(args.input_cloth)
pose = get_pose_map(person_img, findFile(args.openpose_proto),
findFile(args.openpose_model), args.backend, args.target)
segm_image = parse_human(person_img, args.segmentation_model)
segm_image = cv.resize(segm_image, (192, 256), cv.INTER_LINEAR)
cv.dnn_registerLayer('Correlation', CorrelationLayer)
model = CpVton(args.gmm_model, args.tom_model, args.backend, args.target)
agnostic = model.prepare_agnostic(segm_image, person_img, pose)
warped_cloth = model.get_warped_cloth(cloth_img, agnostic)
output = model.get_tryon(agnostic, warped_cloth)
cv.dnn_unregisterLayer('Correlation')
winName = 'Virtual Try-On'
cv.namedWindow(winName, cv.WINDOW_AUTOSIZE)
cv.imshow(winName, output)
cv.waitKey()
+130
View File
@@ -0,0 +1,130 @@
# This file is part of OpenCV project.
# It is subject to the license terms in the LICENSE file found in the top-level directory
# of this distribution and at http://opencv.org/license.html.
# Copyright (C) 2026, BigVision LLC, all rights reserved.
# Third party copyrights are property of their respective owners.
'''
This is a sample script to run PaliGemma2 vision-language inference in OpenCV using
ONNX models. Given an image and a text prompt, it generates a text response
(e.g. a caption).
The model is split into three ONNX files:
- SigLIP vision encoder : image -> 256 image-feature tokens
- Embedding : prompt token ids -> text embeddings
- Gemma2 language model : [image_features | text_embeds] -> logits
Model: https://huggingface.co/google/paligemma2-3b-pt-224
ONNX: https://huggingface.co/nklskyoy/paligemma2-3b-pt-224-onnx
Run the script:
1. Install the required dependencies:
pip install numpy
2. Run the script:
python vlm_inference.py --siglip=<path-to-vision_model.onnx> \
--embedding=<path-to-embedding.onnx> \
--gemma=<path-to-gemma2_3b.onnx> \
--tokenizer_path=<path-to-opencv-tokenizer-config.json> \
--input=<path-to-image> \
--prompt="cap en\n"
The tokenizer_path should point to an OpenCV-format config.json, NOT the
HuggingFace tokenizer_config.json.
'''
import numpy as np
import argparse
import cv2 as cv
EOS_ID = 1
def parse_args():
parser = argparse.ArgumentParser(description='Use this script to run PaliGemma2 vision-language inference in OpenCV',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--siglip', type=str, required=True, help='Path to SigLIP vision encoder ONNX model file.')
parser.add_argument('--embedding', type=str, required=True, help='Path to embedding ONNX model file.')
parser.add_argument('--gemma', type=str, required=True, help='Path to Gemma2 language model ONNX model file.')
parser.add_argument('--tokenizer_path', type=str, required=True, help='Path to tokenizer config.json.')
parser.add_argument('--input', '-i', type=str, required=True, help='Path to the input image.')
parser.add_argument('--prompt', type=str, default='cap en\n', help='Task prompt (e.g. "cap en\\n" to caption in English).')
parser.add_argument('--max_new_tokens', type=int, default=64, help='Maximum number of new tokens to generate.')
parser.add_argument('--seed', type=int, default=0, help='Random seed.')
return parser.parse_args()
def preprocess_image(image_path):
'''Resize to 224x224 and normalize to [-1, 1] in CHW order (SigLIP: mean=0.5, std=0.5).'''
img = cv.imread(image_path)
if img is None:
raise IOError("Could not read image: " + image_path)
img = cv.resize(img, (224, 224))
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
img = img.astype(np.float32) / 255.0
img = (img - 0.5) / 0.5
img = img.transpose(2, 0, 1)[np.newaxis]
return img
def vlm_inference(siglip_net, embed_net, gemma_net, pixel_values, prompt, max_new_tokens, tokenizer):
print("Inferencing PaliGemma2 model...")
tokens = list(tokenizer.encode(prompt))
input_ids = np.array([tokens], dtype=np.int64)
# SigLIP vision encoder: image -> image-feature tokens
siglip_net.setInput(pixel_values, 'pixel_values')
image_features = siglip_net.forward() # (1, 256, 2304)
# Text embedding: token ids -> text embeddings
embed_net.setInput(input_ids, 'input_ids')
text_embeds = embed_net.forward() # (1, text_len, 2304)
# Combine [image_features | text_embeds]
inputs_embeds = np.concatenate([image_features, text_embeds], axis=1)
generated = []
# Prefill
gemma_net.setInput(inputs_embeds, 'inputs_embeds')
logits = gemma_net.forward()
new_id = int(np.argmax(logits[0, -1, :]))
generated.append(new_id)
# Decode (no KV-cache: feed full growing sequence each step)
for _ in range(max_new_tokens - 1):
if new_id == EOS_ID:
break
embed_net.setInput(np.array([[new_id]], dtype=np.int64), 'input_ids')
new_embed = embed_net.forward()
inputs_embeds = np.concatenate([inputs_embeds, new_embed], axis=1)
gemma_net.setInput(inputs_embeds, 'inputs_embeds')
logits = gemma_net.forward()
new_id = int(np.argmax(logits[0, -1, :]))
generated.append(new_id)
if generated and generated[-1] == EOS_ID:
generated.pop()
return generated
if __name__ == '__main__':
args = parse_args()
np.random.seed(args.seed)
print("Preparing PaliGemma2 model...")
tokenizer = cv.dnn.Tokenizer.load(args.tokenizer_path)
siglip_net = cv.dnn.readNetFromONNX(args.siglip, cv.dnn.ENGINE_NEW)
embed_net = cv.dnn.readNetFromONNX(args.embedding, cv.dnn.ENGINE_NEW)
gemma_net = cv.dnn.readNetFromONNX(args.gemma, cv.dnn.ENGINE_NEW)
print(f"Prompt:\n{args.prompt}")
pixel_values = preprocess_image(args.input)
generated = vlm_inference(siglip_net, embed_net, gemma_net, pixel_values,
args.prompt, args.max_new_tokens, tokenizer)
response = tokenizer.decode(generated)
print(f"Response:\n{response}")