mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
Merge pull request #29220 from omrope79:doc_optimizations_v4
[FOLLOW UP] : Documentation optimizations for the new Sphinx structure #29220 ### Pull Request Readiness Checklist This PR serves as a follow-up to the new documentation system introduced in [#29206](https://github.com/opencv/opencv/pull/29206) Co-authored by: @abhishek-gola @kirtijindal14 @Akansha-977 @Prasadayus @varun-jaiswal17 See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -195,6 +195,9 @@ if(DOXYGEN_FOUND)
|
||||
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/bodybg.png")
|
||||
# list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/mymath.sty")
|
||||
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/tutorial-utils.js")
|
||||
# Version dropdown list (shared single source with the Sphinx 5.0 build). Copied
|
||||
# next to the other doxygen assets so header.html can load it via $relpath^.
|
||||
list(APPEND CMAKE_DOXYGEN_HTML_FILES "${CMAKE_CURRENT_SOURCE_DIR}/../docs_sphinx/_static/version.js")
|
||||
string(REPLACE ";" " \\\n" CMAKE_DOXYGEN_HTML_FILES "${CMAKE_DOXYGEN_HTML_FILES}")
|
||||
|
||||
if (DOXYGEN_DOT_EXECUTABLE)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<!--END FULL_SIDEBAR-->
|
||||
<!--END DISABLE_INDEX-->
|
||||
<script type="text/javascript" src="$relpath^jquery.js"></script>
|
||||
<script type="text/javascript" src="$relpath^version.js"></script>
|
||||
<script type="text/javascript" src="$relpath^dynsections.js"></script>
|
||||
<script type="text/javascript" src="$relpath^tutorial-utils.js"></script>
|
||||
<!--BEGIN COPY_CLIPBOARD-->
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Image Classification Example</h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an image classification example with OpenCV.js.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configFile</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button to upload an ONNX model file.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Try it</b> button to see the result. You can choose any other images.<br>
|
||||
@@ -69,13 +69,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="caption">
|
||||
configFile <input type="file" id="configFile">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -123,7 +116,7 @@ labelsUrl = "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dn
|
||||
main = async function() {
|
||||
const labels = await loadLables(labelsUrl);
|
||||
const input = getBlobFromImage(inputSize, mean, std, swapRB, 'canvasInput');
|
||||
let net = cv.readNet(configPath, modelPath);
|
||||
let net = cv.readNet(modelPath);
|
||||
net.setInput(input);
|
||||
const start = performance.now();
|
||||
const result = net.forward();
|
||||
@@ -201,22 +194,12 @@ softmax = function(result) {
|
||||
loadImageToCanvas(e, 'canvasInput');
|
||||
});
|
||||
|
||||
let configPath = "";
|
||||
let configFile = document.getElementById('configFile');
|
||||
configFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
configPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
|
||||
});
|
||||
|
||||
let modelPath = "";
|
||||
let modelFile = document.getElementById('modelFile');
|
||||
modelFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
modelPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
|
||||
configPath = "";
|
||||
configFile.value = "";
|
||||
});
|
||||
|
||||
utils.loadOpenCv(() => {
|
||||
|
||||
@@ -1,65 +1,72 @@
|
||||
{
|
||||
"caffe": [
|
||||
"onnx": [
|
||||
{
|
||||
"model": "alexnet",
|
||||
"mean": "104, 117, 123",
|
||||
"std": "1",
|
||||
"swapRB": "false",
|
||||
"needSoftmax": "false",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/classification_classes_ILSVRC2012.txt",
|
||||
"modelUrl": "http://dl.caffe.berkeleyvision.org/bvlc_alexnet.caffemodel",
|
||||
"configUrl": "https://raw.githubusercontent.com/BVLC/caffe/master/models/bvlc_alexnet/deploy.prototxt"
|
||||
},
|
||||
{
|
||||
"model": "densenet",
|
||||
"mean": "127.5, 127.5, 127.5",
|
||||
"std": "0.007843",
|
||||
"model": "googlenet",
|
||||
"inputSize": "224, 224",
|
||||
"mean": "103.939, 116.779, 123.675",
|
||||
"std": "1, 1, 1",
|
||||
"scale": "1",
|
||||
"swapRB": "false",
|
||||
"needSoftmax": "true",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/classification_classes_ILSVRC2012.txt",
|
||||
"modelUrl": "https://drive.google.com/open?id=0B7ubpZO7HnlCcHlfNmJkU2VPelE",
|
||||
"configUrl": "https://raw.githubusercontent.com/shicai/DenseNet-Caffe/master/DenseNet_121.prototxt"
|
||||
},
|
||||
{
|
||||
"model": "googlenet",
|
||||
"mean": "104, 117, 123",
|
||||
"std": "1",
|
||||
"swapRB": "false",
|
||||
"needSoftmax": "false",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/classification_classes_ILSVRC2012.txt",
|
||||
"modelUrl": "http://dl.caffe.berkeleyvision.org/bvlc_googlenet.caffemodel",
|
||||
"configUrl": "https://raw.githubusercontent.com/BVLC/caffe/master/models/bvlc_googlenet/deploy.prototxt"
|
||||
"modelUrl": "https://github.com/onnx/models/raw/69c5d3751dda5349fd3fc53f525395d180420c07/vision/classification/inception_and_googlenet/googlenet/model/googlenet-8.onnx"
|
||||
},
|
||||
{
|
||||
"model": "squeezenet",
|
||||
"mean": "104, 117, 123",
|
||||
"std": "1",
|
||||
"swapRB": "false",
|
||||
"needSoftmax": "false",
|
||||
"inputSize": "224, 224",
|
||||
"mean": "0.485, 0.456, 0.406",
|
||||
"std": "0.229, 0.224, 0.225",
|
||||
"scale": "0.003921",
|
||||
"swapRB": "true",
|
||||
"needSoftmax": "true",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/classification_classes_ILSVRC2012.txt",
|
||||
"modelUrl": "https://raw.githubusercontent.com/forresti/SqueezeNet/master/SqueezeNet_v1.0/squeezenet_v1.0.caffemodel",
|
||||
"configUrl": "https://raw.githubusercontent.com/forresti/SqueezeNet/master/SqueezeNet_v1.0/deploy.prototxt"
|
||||
"modelUrl": "https://github.com/onnx/models/raw/main/validated/vision/classification/squeezenet/model/squeezenet1.1-7.onnx?download="
|
||||
},
|
||||
{
|
||||
"model": "VGG",
|
||||
"mean": "104, 117, 123",
|
||||
"std": "1",
|
||||
"swapRB": "false",
|
||||
"needSoftmax": "false",
|
||||
"model": "resnet",
|
||||
"inputSize": "224, 224",
|
||||
"mean": "123.675, 116.28, 103.53",
|
||||
"std": "58.395, 57.12, 57.375",
|
||||
"scale": "1",
|
||||
"swapRB": "true",
|
||||
"needSoftmax": "true",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/classification_classes_ILSVRC2012.txt",
|
||||
"modelUrl": "http://www.robots.ox.ac.uk/~vgg/software/very_deep/caffe/VGG_ILSVRC_19_layers.caffemodel",
|
||||
"configUrl": "https://gist.githubusercontent.com/ksimonyan/3785162f95cd2d5fee77/raw/f02f8769e64494bcd3d7e97d5d747ac275825721/VGG_ILSVRC_19_layers_deploy.prototxt"
|
||||
"modelUrl": "https://github.com/onnx/models/raw/main/validated/vision/classification/resnet/model/resnet50-v2-7.onnx"
|
||||
},
|
||||
{
|
||||
"model": "vgg16",
|
||||
"inputSize": "224, 224",
|
||||
"mean": "103.939, 116.779, 123.68",
|
||||
"std": "1, 1, 1",
|
||||
"scale": "1",
|
||||
"swapRB": "false",
|
||||
"needSoftmax": "true",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/classification_classes_ILSVRC2012.txt",
|
||||
"modelUrl": "https://github.com/onnx/models/raw/f884b33c3e2371952aad7ea091898f418c830fe5/vision/classification/vgg/model/vgg16-bn-7.onnx"
|
||||
},
|
||||
{
|
||||
"model": "densenet121",
|
||||
"inputSize": "224, 224",
|
||||
"mean": "123.675, 116.28, 103.53",
|
||||
"std": "0.229, 0.224, 0.225",
|
||||
"scale": "0.003921",
|
||||
"swapRB": "true",
|
||||
"needSoftmax": "true",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/classification_classes_ILSVRC2012.txt",
|
||||
"modelUrl": "https://github.com/onnx/models/raw/4eff8f9b9189672de28d087684e7085ad977747c/vision/classification/densenet-121/model/densenet-8.onnx"
|
||||
}
|
||||
],
|
||||
"tensorflow": [
|
||||
{
|
||||
"model": "inception",
|
||||
"inputSize": "224, 224",
|
||||
"mean": "123, 117, 104",
|
||||
"std": "1",
|
||||
"scale": "1",
|
||||
"swapRB": "true",
|
||||
"needSoftmax": "false",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/petewarden/tf_ios_makefile_example/master/data/imagenet_comp_graph_label_strings.txt",
|
||||
"modelUrl": "https://raw.githubusercontent.com/petewarden/tf_ios_makefile_example/master/data/tensorflow_inception_graph.pb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<h2>Image Classification Example</h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an image classification example with OpenCV.js.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configFile</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button to upload an ONNX model file.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Try it</b> button to see the result. You can choose any other images.<br>
|
||||
@@ -70,13 +70,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="caption">
|
||||
configFile <input type="file" id="configFile">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -124,7 +117,7 @@ labelsUrl = "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dn
|
||||
main = async function() {
|
||||
const labels = await loadLables(labelsUrl);
|
||||
const input = getBlobFromImage(inputSize, mean, std, swapRB, 'canvasInput');
|
||||
let net = cv.readNet(configPath, modelPath);
|
||||
let net = cv.readNet(modelPath);
|
||||
net.setPreferableBackend(6);
|
||||
net.setInput(input);
|
||||
let result = net.forward();
|
||||
@@ -207,22 +200,12 @@ softmax = function(result) {
|
||||
loadImageToCanvas(e, 'canvasInput');
|
||||
});
|
||||
|
||||
let configPath = "";
|
||||
let configFile = document.getElementById('configFile');
|
||||
configFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
configPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
|
||||
});
|
||||
|
||||
let modelPath = "";
|
||||
let modelFile = document.getElementById('modelFile');
|
||||
modelFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
modelPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
|
||||
configPath = "";
|
||||
configFile.value = "";
|
||||
});
|
||||
|
||||
utils.loadOpenCv(() => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Image Classification Example with Camera</h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an image classification example with camera.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configFile</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button to upload an ONNX model file.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Start/Stop</b> button to start or stop the camera capture.<br>
|
||||
@@ -69,13 +69,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="caption">
|
||||
configFile <input type="file" id="configFile">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -126,7 +119,7 @@ let cap = new cv.VideoCapture(video);
|
||||
main = async function(frame) {
|
||||
const labels = await loadLables(labelsUrl);
|
||||
const input = getBlobFromImage(inputSize, mean, std, swapRB, frame);
|
||||
let net = cv.readNet(configPath, modelPath);
|
||||
let net = cv.readNet(modelPath);
|
||||
net.setInput(input);
|
||||
const start = performance.now();
|
||||
const result = net.forward();
|
||||
@@ -203,22 +196,12 @@ softmax = function(result) {
|
||||
}
|
||||
});
|
||||
|
||||
let configPath = "";
|
||||
let configFile = document.getElementById('configFile');
|
||||
configFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
configPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
|
||||
});
|
||||
|
||||
let modelPath = "";
|
||||
let modelFile = document.getElementById('modelFile');
|
||||
modelFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
modelPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
|
||||
configPath = "";
|
||||
configFile.value = "";
|
||||
});
|
||||
|
||||
utils.loadOpenCv(() => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Object Detection Example</h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an object detection example with OpenCV.js.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configFile</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button to upload an ONNX model file.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Try it</b> button to see the result. You can choose any other images.<br>
|
||||
@@ -45,13 +45,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="caption">
|
||||
configFile <input type="file" id="configFile">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -84,8 +77,8 @@
|
||||
|
||||
<script id="codeSnippet" type="text/code-snippet">
|
||||
inputSize = [300, 300];
|
||||
mean = [127.5, 127.5, 127.5];
|
||||
std = 0.007843;
|
||||
mean = [0, 0, 0];
|
||||
std = 1;
|
||||
swapRB = false;
|
||||
confThreshold = 0.5;
|
||||
nmsThreshold = 0.4;
|
||||
@@ -94,14 +87,14 @@ nmsThreshold = 0.4;
|
||||
outType = "SSD";
|
||||
|
||||
// url for label file, can from local or Internet
|
||||
labelsUrl = "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_pascal_voc.txt";
|
||||
labelsUrl = "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_coco.txt";
|
||||
</script>
|
||||
|
||||
<script id="codeSnippet1" type="text/code-snippet">
|
||||
main = async function() {
|
||||
const labels = await loadLables(labelsUrl);
|
||||
const input = getBlobFromImage(inputSize, mean, std, swapRB, 'canvasInput');
|
||||
let net = cv.readNet(configPath, modelPath);
|
||||
let net = cv.readNet(modelPath);
|
||||
net.setInput(input);
|
||||
const start = performance.now();
|
||||
const result = net.forward();
|
||||
@@ -305,7 +298,7 @@ postProcess = function(result, labels) {
|
||||
let ctx = canvas.getContext('2d');
|
||||
let img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.src = 'lena.png';
|
||||
img.src = 'lena.jpg';
|
||||
img.onload = function() {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
};
|
||||
@@ -330,22 +323,12 @@ postProcess = function(result, labels) {
|
||||
loadImageToCanvas(e, 'canvasInput');
|
||||
});
|
||||
|
||||
let configPath = "";
|
||||
let configFile = document.getElementById('configFile');
|
||||
configFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
configPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
|
||||
});
|
||||
|
||||
let modelPath = "";
|
||||
let modelFile = document.getElementById('modelFile');
|
||||
modelFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
modelPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
|
||||
configPath = "";
|
||||
configFile.value = "";
|
||||
});
|
||||
|
||||
utils.loadOpenCv(() => {
|
||||
|
||||
@@ -1,39 +1,44 @@
|
||||
{
|
||||
"caffe": [
|
||||
"onnx": [
|
||||
{
|
||||
"model": "mobilenet_SSD",
|
||||
"model": "ssd_mobilenet_v1",
|
||||
"inputSize": "300, 300",
|
||||
"mean": "127.5, 127.5, 127.5",
|
||||
"std": "0.007843",
|
||||
"mean": "0, 0, 0",
|
||||
"std": "1",
|
||||
"swapRB": "false",
|
||||
"outType": "SSD",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_pascal_voc.txt",
|
||||
"modelUrl": "https://raw.githubusercontent.com/chuanqi305/MobileNet-SSD/master/mobilenet_iter_73000.caffemodel",
|
||||
"configUrl": "https://raw.githubusercontent.com/chuanqi305/MobileNet-SSD/master/deploy.prototxt"
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_coco.txt",
|
||||
"modelUrl": "https://huggingface.co/onnxmodelzoo/ssd_mobilenet_v1_12/resolve/main/ssd_mobilenet_v1_12.onnx"
|
||||
},
|
||||
{
|
||||
"model": "VGG_SSD",
|
||||
"model": "ssd_vgg16",
|
||||
"inputSize": "300, 300",
|
||||
"mean": "104, 117, 123",
|
||||
"std": "1",
|
||||
"swapRB": "false",
|
||||
"outType": "SSD",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_pascal_voc.txt",
|
||||
"modelUrl": "https://drive.google.com/uc?id=0BzKzrI_SkD1_WVVTSmQxU0dVRzA&export=download",
|
||||
"configUrl": "https://drive.google.com/uc?id=0BzKzrI_SkD1_WVVTSmQxU0dVRzA&export=download"
|
||||
}
|
||||
],
|
||||
"darknet": [
|
||||
"modelUrl": "https://github.com/omrope79/opencv-test-models/releases/download/v1.2.0/ssd_vgg16.onnx"
|
||||
},
|
||||
{
|
||||
"model": "yolov2_tiny",
|
||||
"model": "yolov4-tiny",
|
||||
"inputSize": "416, 416",
|
||||
"mean": "0, 0, 0",
|
||||
"std": "0.00392",
|
||||
"swapRB": "false",
|
||||
"swapRB": "true",
|
||||
"outType": "YOLO",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_yolov3.txt",
|
||||
"modelUrl": "https://pjreddie.com/media/files/yolov2-tiny.weights",
|
||||
"configUrl": "https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov2-tiny.cfg"
|
||||
"modelUrl": "https://huggingface.co/opencv/opencv_contribution/resolve/main/yolov4/yolov4-tiny.onnx"
|
||||
},
|
||||
{
|
||||
"model": "yolov3",
|
||||
"inputSize": "416, 416",
|
||||
"mean": "0, 0, 0",
|
||||
"std": "0.00392",
|
||||
"swapRB": "true",
|
||||
"outType": "YOLO",
|
||||
"labelsUrl": "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_yolov3.txt",
|
||||
"modelUrl": "https://huggingface.co/qualcomm/Yolo-v3/resolve/226ada6de9dcb32eebad7f74bf526714e2af6136/Yolo-v3.onnx"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Object Detection Example with Camera </h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an object detection example with camera.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configInput</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button to upload an ONNX model file.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Start/Stop</b> button to start or stop the camera capture.<br>
|
||||
@@ -45,13 +45,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="caption">
|
||||
configFile <input type="file" id="configFile">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -84,17 +77,17 @@
|
||||
|
||||
<script id="codeSnippet" type="text/code-snippet">
|
||||
inputSize = [300, 300];
|
||||
mean = [127.5, 127.5, 127.5];
|
||||
std = 0.007843;
|
||||
mean = [0, 0, 0];
|
||||
std = 1;
|
||||
swapRB = false;
|
||||
confThreshold = 0.5;
|
||||
nmsThreshold = 0.4;
|
||||
|
||||
// the type of output, can be YOLO or SSD
|
||||
// The type of output, can be YOLO or SSD
|
||||
outType = "SSD";
|
||||
|
||||
// url for label file, can from local or Internet
|
||||
labelsUrl = "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_pascal_voc.txt";
|
||||
labelsUrl = "https://raw.githubusercontent.com/opencv/opencv/5.x/samples/data/dnn/object_detection_classes_coco.txt";
|
||||
</script>
|
||||
|
||||
<script id="codeSnippet1" type="text/code-snippet">
|
||||
@@ -104,7 +97,7 @@ let cap = new cv.VideoCapture(videoInput);
|
||||
main = async function(frame) {
|
||||
const labels = await loadLables(labelsUrl);
|
||||
const input = getBlobFromImage(inputSize, mean, std, swapRB, frame);
|
||||
let net = cv.readNet(configPath, modelPath);
|
||||
let net = cv.readNet(modelPath);
|
||||
net.setInput(input);
|
||||
const start = performance.now();
|
||||
const result = net.forward();
|
||||
@@ -331,22 +324,12 @@ postProcess = function(result, labels, frame) {
|
||||
}
|
||||
});
|
||||
|
||||
let configPath = "";
|
||||
let configFile = document.getElementById('configFile');
|
||||
configFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
configPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
|
||||
});
|
||||
|
||||
let modelPath = "";
|
||||
let modelFile = document.getElementById('modelFile');
|
||||
modelFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
modelPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
|
||||
configPath = "";
|
||||
configFile.value = "";
|
||||
});
|
||||
|
||||
utils.loadOpenCv(() => {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Pose Estimation Example</h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an pose estimation example with OpenCV.js.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configInput</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button to upload an ONNX model file.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Try it</b> button to see the result. You can choose any other images.<br>
|
||||
@@ -45,13 +45,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="caption">
|
||||
configFile <input type="file" id="configFile">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -96,7 +89,7 @@ dataset = "COCO";
|
||||
<script id="codeSnippet1" type="text/code-snippet">
|
||||
main = async function() {
|
||||
const input = getBlobFromImage(inputSize, mean, std, swapRB, 'canvasInput');
|
||||
let net = cv.readNet(configPath, modelPath);
|
||||
let net = cv.readNet(modelPath);
|
||||
net.setInput(input);
|
||||
const start = performance.now();
|
||||
const result = net.forward();
|
||||
@@ -268,22 +261,12 @@ postProcess = function(result) {
|
||||
loadImageToCanvas(e, 'canvasInput');
|
||||
});
|
||||
|
||||
let configPath = "";
|
||||
let configFile = document.getElementById('configFile');
|
||||
configFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
configPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
|
||||
});
|
||||
|
||||
let modelPath = "";
|
||||
let modelFile = document.getElementById('modelFile');
|
||||
modelFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
modelPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
|
||||
configPath = "";
|
||||
configFile.value = "";
|
||||
});
|
||||
|
||||
utils.loadOpenCv(() => {
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
{
|
||||
"caffe": [
|
||||
"onnx": [
|
||||
{
|
||||
"model": "body_25",
|
||||
"inputSize": "368, 368",
|
||||
"mean": "0, 0, 0",
|
||||
"std": "0.00392",
|
||||
"swapRB": "false",
|
||||
"dataset": "BODY_25",
|
||||
"modelUrl": "http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/body_25/pose_iter_584000.caffemodel",
|
||||
"configUrl": "https://raw.githubusercontent.com/CMU-Perceptual-Computing-Lab/openpose/master/models/pose/body_25/pose_deploy.prototxt"
|
||||
},
|
||||
{
|
||||
"model": "coco",
|
||||
"model": "openpose_coco",
|
||||
"inputSize": "368, 368",
|
||||
"mean": "0, 0, 0",
|
||||
"std": "0.00392",
|
||||
"swapRB": "false",
|
||||
"dataset": "COCO",
|
||||
"modelUrl": "http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/coco/pose_iter_440000.caffemodel",
|
||||
"configUrl": "https://raw.githubusercontent.com/CMU-Perceptual-Computing-Lab/openpose/master/models/pose/coco/pose_deploy_linevec.prototxt"
|
||||
"modelUrl": "https://github.com/omrope79/opencv-test-models/releases/download/v1.3.0/openpose_pose_coco.onnx"
|
||||
},
|
||||
{
|
||||
"model": "mpi",
|
||||
"model": "openpose_mpi",
|
||||
"inputSize": "368, 368",
|
||||
"mean": "0, 0, 0",
|
||||
"std": "0.00392",
|
||||
"swapRB": "false",
|
||||
"dataset": "MPI",
|
||||
"modelUrl": "http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/mpi/pose_iter_160000.caffemodel",
|
||||
"configUrl": "https://raw.githubusercontent.com/CMU-Perceptual-Computing-Lab/openpose/master/models/pose/mpi/pose_deploy_linevec.prototxt"
|
||||
"modelUrl": "https://github.com/omrope79/opencv-test-models/releases/download/v1.3.0/openpose_pose_mpi.onnx"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Semantic Segmentation Example</h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an semantic segmentation example with OpenCV.js.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configInput</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configFile</b> button if needed) to upload inference model.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Try it</b> button to see the result. You can choose any other images.<br>
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
"modelUrl": "https://drive.google.com/uc?id=1v-hfGenaE9tiGOzo5qdgMNG_gqQ5-Xn4&export=download"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Style Transfer Example</h2>
|
||||
<p>
|
||||
This tutorial shows you how to write an style transfer example with OpenCV.js.<br>
|
||||
To try the example you should click the <b>modelFile</b> button(and <b>configFile</b> button if needed) to upload inference model.
|
||||
To try the example you should click the <b>modelFile</b> button to upload an ONNX model file.
|
||||
You can find the model URLs and parameters in the <a href="#appendix">model info</a> section.
|
||||
Then You should change the parameters in the first code snippet according to the uploaded model.
|
||||
Finally click <b>Try it</b> button to see the result. You can choose any other images.<br>
|
||||
@@ -45,13 +45,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="caption">
|
||||
configFile <input type="file" id="configFile">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -90,7 +83,7 @@ swapRB = false;
|
||||
<script id="codeSnippet1" type="text/code-snippet">
|
||||
main = async function() {
|
||||
const input = getBlobFromImage(inputSize, mean, std, swapRB, 'canvasInput');
|
||||
let net = cv.readNet(configPath, modelPath);
|
||||
let net = cv.readNet(modelPath);
|
||||
net.setInput(input);
|
||||
const start = performance.now();
|
||||
const result = net.forward();
|
||||
@@ -146,7 +139,7 @@ postProcess = function(result) {
|
||||
let ctx = canvas.getContext('2d');
|
||||
let img = new Image();
|
||||
img.crossOrigin = 'anonymous';
|
||||
img.src = 'lena.png';
|
||||
img.src = 'lena.jpg';
|
||||
img.onload = function() {
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
};
|
||||
@@ -171,22 +164,12 @@ postProcess = function(result) {
|
||||
loadImageToCanvas(e, 'canvasInput');
|
||||
});
|
||||
|
||||
let configPath = "";
|
||||
let configFile = document.getElementById('configFile');
|
||||
configFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
configPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The config file '${configPath}' is created successfully.`;
|
||||
});
|
||||
|
||||
let modelPath = "";
|
||||
let modelFile = document.getElementById('modelFile');
|
||||
modelFile.addEventListener('change', async (e) => {
|
||||
initStatus();
|
||||
modelPath = await loadModel(e);
|
||||
document.getElementById('status').innerHTML = `The model file '${modelPath}' is created successfully.`;
|
||||
configPath = "";
|
||||
configFile.value = "";
|
||||
});
|
||||
|
||||
utils.loadOpenCv(() => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"mean": "0, 0, 0",
|
||||
"std": "1",
|
||||
"swapRB": "true",
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/vision/style_transfer/fast_neural_style/model/mosaic-9.onnx"
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/validated/vision/style_transfer/fast_neural_style/model/mosaic-9.onnx"
|
||||
},
|
||||
{
|
||||
"model": "candy-9.onnx",
|
||||
@@ -14,7 +14,7 @@
|
||||
"mean": "0, 0, 0",
|
||||
"std": "1",
|
||||
"swapRB": "true",
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/vision/style_transfer/fast_neural_style/model/candy-9.onnx"
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/validated/vision/style_transfer/fast_neural_style/model/candy-9.onnx"
|
||||
},
|
||||
{
|
||||
"model": "rain-princess-9.onnx",
|
||||
@@ -22,7 +22,7 @@
|
||||
"mean": "0, 0, 0",
|
||||
"std": "1",
|
||||
"swapRB": "true",
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/vision/style_transfer/fast_neural_style/model/rain-princess-9.onnx"
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/validated/vision/style_transfer/fast_neural_style/model/rain-princess-9.onnx"
|
||||
},
|
||||
{
|
||||
"model": "udnie-9.onnx",
|
||||
@@ -30,7 +30,7 @@
|
||||
"mean": "0, 0, 0",
|
||||
"std": "1",
|
||||
"swapRB": "true",
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/vision/style_transfer/fast_neural_style/model/udnie-9.onnx"
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/validated/vision/style_transfer/fast_neural_style/model/udnie-9.onnx"
|
||||
},
|
||||
{
|
||||
"model": "pointilism-9.onnx",
|
||||
@@ -38,7 +38,7 @@
|
||||
"mean": "0, 0, 0",
|
||||
"std": "1",
|
||||
"swapRB": "true",
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/vision/style_transfer/fast_neural_style/model/pointilism-9.onnx"
|
||||
"modelUrl": "https://media.githubusercontent.com/media/onnx/models/main/validated/vision/style_transfer/fast_neural_style/model/pointilism-9.onnx"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -96,38 +96,9 @@ After network was initialized only `forward` method is called for every network'
|
||||
reallocate all the internal memory. That leads to efficiency gaps. Try to initialize
|
||||
and deploy models using a fixed batch size and image's dimensions.
|
||||
|
||||
## Example: custom layer from Caffe
|
||||
Let's create a custom layer `Interp` from https://github.com/cdmh/deeplab-public.
|
||||
It's just a simple resize that takes an input blob of size `N x C x Hi x Wi` and returns
|
||||
an output blob of size `N x C x Ho x Wo` where `N` is a batch size, `C` is a number of channels,
|
||||
`Hi x Wi` and `Ho x Wo` are input and output `height x width` correspondingly.
|
||||
This layer has no trainable weights but it has hyper-parameters to specify an output size.
|
||||
|
||||
In example,
|
||||
~~~~~~~~~~~~~
|
||||
layer {
|
||||
name: "output"
|
||||
type: "Interp"
|
||||
bottom: "input"
|
||||
top: "output"
|
||||
interp_param {
|
||||
height: 9
|
||||
width: 8
|
||||
}
|
||||
}
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This way our implementation can look like:
|
||||
|
||||
@snippet dnn/custom_layers.hpp InterpLayer
|
||||
|
||||
Next we need to register a new layer type and try to import the model.
|
||||
|
||||
@snippet dnn/custom_layers.hpp Register InterpLayer
|
||||
|
||||
## Example: custom layer from TensorFlow
|
||||
This is an example of how to import a network with [tf.image.resize_bilinear](https://www.tensorflow.org/versions/master/api_docs/python/tf/image/resize_bilinear)
|
||||
operation. This is also a resize but with an implementation different from OpenCV's or `Interp` above.
|
||||
operation. This is also a resize but with an implementation different from OpenCV's built-in resize.
|
||||
|
||||
Let's create a single layer network:
|
||||
~~~~~~~~~~~~~{.py}
|
||||
@@ -235,13 +206,11 @@ in the opencv_extra repository.
|
||||
## Define a custom layer in Python
|
||||
The following example shows how to customize OpenCV's layers in Python.
|
||||
|
||||
Let's consider [Holistically-Nested Edge Detection](https://arxiv.org/abs/1504.06375)
|
||||
deep learning model. That was trained with one and only difference comparing to
|
||||
a current version of [Caffe framework](http://caffe.berkeleyvision.org/). `Crop`
|
||||
layers that receive two input blobs and crop the first one to match spatial dimensions
|
||||
of the second one used to crop from the center. Nowadays Caffe's layer does it
|
||||
from the top-left corner. So using the latest version of Caffe or OpenCV you will
|
||||
get shifted results with filled borders.
|
||||
Let's consider the [Holistically-Nested Edge Detection](https://arxiv.org/abs/1504.06375)
|
||||
model. Its `Crop` layers receive two input blobs and crop the first one to match the
|
||||
spatial dimensions of the second. OpenCV's built-in `Crop` layer trims from the
|
||||
top-left corner, whereas this model expects cropping from the center, so using the
|
||||
built-in behaviour directly would produce shifted results with filled borders.
|
||||
|
||||
Next we're going to replace OpenCV's `Crop` layer that makes top-left cropping by
|
||||
a centric one.
|
||||
|
||||
@@ -26,9 +26,9 @@ Tutorial was written for Android Studio 2022.2.1.
|
||||
- Get the latest pre-built OpenCV for Android release from https://github.com/opencv/opencv/releases
|
||||
and unpack it (for example, `opencv-4.X.Y-android-sdk.zip`, minimum version 4.9 is required).
|
||||
|
||||
- Download MobileNet object detection model from https://github.com/chuanqi305/MobileNet-SSD.
|
||||
Configuration file `MobileNetSSD_deploy.prototxt` and model weights `MobileNetSSD_deploy.caffemodel`
|
||||
are required.
|
||||
- Download the MobileNet-SSD object detection model in ONNX format and save it as
|
||||
`mobilenet.onnx`. This single file contains both the network topology and its
|
||||
trained weights.
|
||||
|
||||
## Create an empty Android Studio project and add OpenCV dependency
|
||||
|
||||
@@ -59,7 +59,7 @@ a correct screen orientation and allow to use a camera.
|
||||
@snippet android/mobilenet-objdetect/src/org/opencv/samples/opencv_mobilenet/MainActivity.java mobilenet_tutorial_package
|
||||
@snippet android/mobilenet-objdetect/src/org/opencv/samples/opencv_mobilenet/MainActivity.java mobilenet_tutorial
|
||||
|
||||
- Put downloaded `deploy.prototxt` and `mobilenet_iter_73000.caffemodel`
|
||||
- Put the downloaded `mobilenet.onnx`
|
||||
into `app/src/main/res/raw` folder. OpenCV DNN model is mainly designed to load ML and DNN models
|
||||
from file. Modern Android does not allow it without extra permissions, but provides Java API to load
|
||||
bytes from resources. The sample uses alternative DNN API that initializes a model from in-memory
|
||||
|
||||
@@ -522,7 +522,7 @@ OpenCV have own DNN inference module which have own build-in engine, but can als
|
||||
| `OPENCV_DNN_OPENCL` | _ON_ | Enable built-in OpenCL inference backend. |
|
||||
| `WITH_INF_ENGINE` | _OFF_ | **Deprecated since OpenVINO 2022.1** Enables [Intel Inference Engine (IE)](https://github.com/openvinotoolkit/openvino) backend. Allows to execute networks in IE format (.xml + .bin). Inference Engine must be installed either as part of [OpenVINO toolkit](https://en.wikipedia.org/wiki/OpenVINO), either as a standalone library built from sources. |
|
||||
| `INF_ENGINE_RELEASE` | _2020040000_ | **Deprecated since OpenVINO 2022.1** Defines version of Inference Engine library which is tied to OpenVINO toolkit version. Must be a 10-digit string, e.g. _2020040000_ for OpenVINO 2020.4. |
|
||||
| `WITH_NGRAPH` | _OFF_ | **Deprecated since OpenVINO 2022.1** Enables Intel NGraph library support. This library is part of Inference Engine backend which allows executing arbitrary networks read from files in multiple formats supported by OpenCV: Caffe, TensorFlow, PyTorch, Darknet, etc.. NGraph library must be installed, it is included into Inference Engine. |
|
||||
| `WITH_NGRAPH` | _OFF_ | **Deprecated since OpenVINO 2022.1** Enables Intel NGraph library support. This library is part of Inference Engine backend which allows executing arbitrary networks read from files in multiple formats supported by OpenCV: ONNX, TensorFlow, PyTorch, etc.. NGraph library must be installed, it is included into Inference Engine. |
|
||||
| `WITH_OPENVINO` | _OFF_ | Enable Intel OpenVINO Toolkit support. Should be used for OpenVINO>=2022.1 instead of `WITH_INF_ENGINE` and `WITH_NGRAPH`. |
|
||||
| `WITH_ONNXRUNTIME` | _OFF_ | Enable Microsoft ONNX Runtime backend support for OpenCV DNN. |
|
||||
| `DOWNLOAD_ONNXRUNTIME` | _OFF_ | Download official ONNX Runtime prebuilt binaries when enabled (or when ONNX Runtime is not available in system paths). |
|
||||
|
||||
+34
-13
@@ -101,23 +101,28 @@ set(_LEGACY_DOXYFILE "${CMAKE_BINARY_DIR}/doc/Doxyfile")
|
||||
set(_DOXYGEN_XML_DIR "${CMAKE_BINARY_DIR}/doc/doxygen/xml")
|
||||
|
||||
if(NOT DEFINED SPHINX_API_MODULES)
|
||||
# Any module whose include tree declares an @defgroup, keyed on dir name; matlab excluded.
|
||||
set(SPHINX_API_MODULES "")
|
||||
file(GLOB _umbrella_headers "${CMAKE_SOURCE_DIR}/modules/*/include/opencv2/*.hpp")
|
||||
# Also scan opencv_contrib modules when present.
|
||||
set(_api_module_roots "${CMAKE_SOURCE_DIR}/modules")
|
||||
if(OPENCV_EXTRA_MODULES_PATH AND EXISTS "${OPENCV_EXTRA_MODULES_PATH}")
|
||||
file(GLOB _contrib_umbrellas "${OPENCV_EXTRA_MODULES_PATH}/*/include/opencv2/*.hpp")
|
||||
list(APPEND _umbrella_headers ${_contrib_umbrellas})
|
||||
list(APPEND _api_module_roots "${OPENCV_EXTRA_MODULES_PATH}")
|
||||
endif()
|
||||
foreach(_hdr ${_umbrella_headers})
|
||||
get_filename_component(_stem "${_hdr}" NAME_WE)
|
||||
# Only the umbrella header modules/<m>/include/opencv2/<m>.hpp.
|
||||
if(_hdr MATCHES "/modules/${_stem}/include/opencv2/${_stem}\\.hpp$")
|
||||
file(READ "${_hdr}" _hdr_contents)
|
||||
if(_hdr_contents MATCHES "@defgroup")
|
||||
list(APPEND SPHINX_API_MODULES "${_stem}")
|
||||
foreach(_root ${_api_module_roots})
|
||||
file(GLOB _api_module_dirs RELATIVE "${_root}" "${_root}/*")
|
||||
foreach(_m ${_api_module_dirs})
|
||||
if(IS_DIRECTORY "${_root}/${_m}/include/opencv2" AND NOT _m STREQUAL "matlab")
|
||||
file(GLOB_RECURSE _api_hdrs "${_root}/${_m}/include/opencv2/*.hpp")
|
||||
foreach(_hdr ${_api_hdrs})
|
||||
file(READ "${_hdr}" _hdr_contents)
|
||||
if(_hdr_contents MATCHES "@defgroup")
|
||||
list(APPEND SPHINX_API_MODULES "${_m}")
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
list(REMOVE_DUPLICATES SPHINX_API_MODULES)
|
||||
list(SORT SPHINX_API_MODULES)
|
||||
message(STATUS "docs_sphinx: discovered API modules: ${SPHINX_API_MODULES}")
|
||||
endif()
|
||||
@@ -141,7 +146,6 @@ if(EXISTS "${_LEGACY_DOXYFILE}")
|
||||
"XML_OUTPUT = xml\n"
|
||||
"XML_PROGRAMLISTING = NO\n"
|
||||
"CREATE_SUBDIRS = NO\n"
|
||||
"EXCLUDE_PATTERNS += cap_ios.h\n"
|
||||
"CLASS_GRAPH = NO\n"
|
||||
"COLLABORATION_GRAPH = NO\n"
|
||||
"GROUP_GRAPHS = NO\n"
|
||||
@@ -191,6 +195,23 @@ if(NOT SPHINX_JOBS)
|
||||
set(SPHINX_JOBS "auto")
|
||||
endif()
|
||||
set(_SPHINX_WARNINGS "${CMAKE_CURRENT_BINARY_DIR}/sphinx-warnings.log")
|
||||
# opencv.js (JS Try-it): download once into the build dir, bundled into html output.
|
||||
set(_OPENCV_JS "${CMAKE_CURRENT_BINARY_DIR}/opencv.js")
|
||||
if(NOT EXISTS "${_OPENCV_JS}")
|
||||
message(STATUS "docs_sphinx: downloading opencv.js (one-time)")
|
||||
file(DOWNLOAD "https://docs.opencv.org/5.x/opencv.js" "${_OPENCV_JS}"
|
||||
SHOW_PROGRESS STATUS _DL_STATUS)
|
||||
list(GET _DL_STATUS 0 _DL_OK)
|
||||
if(NOT _DL_OK EQUAL 0)
|
||||
message(WARNING "docs_sphinx: opencv.js download failed — Try-it won't run")
|
||||
file(REMOVE "${_OPENCV_JS}")
|
||||
set(_OPENCV_JS "")
|
||||
endif()
|
||||
endif()
|
||||
if(_OPENCV_JS)
|
||||
list(APPEND _SPHINX_ENV "OPENCV_JS_PATH=${_OPENCV_JS}")
|
||||
endif()
|
||||
|
||||
add_custom_target(sphinx
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${_SPHINX_OUTDIR}
|
||||
COMMAND ${CMAKE_COMMAND} -E env ${_SPHINX_ENV}
|
||||
|
||||
+384
-15
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/*
|
||||
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.
|
||||
@@ -55,7 +55,16 @@ html[data-theme="dark"] .bd-article-container a { color: #539bf5 !important; }
|
||||
text-decoration-thickness: 1px;
|
||||
color: #0550ae;
|
||||
}
|
||||
html[data-theme="dark"] .bd-article-container a:hover { color: #6cb6ff !important; }
|
||||
/* Hover keeps the SAME #0969da — no separate hover-blue shade in dark
|
||||
* mode (per "no hover blue, everything at rest"). */
|
||||
html[data-theme="dark"] .bd-article-container a:hover,
|
||||
html[data-theme="dark"] .bd-article-container a.reference:hover,
|
||||
html[data-theme="dark"] .bd-article-container a.reference.internal:hover,
|
||||
html[data-theme="dark"] .bd-article-container a.reference.external:hover,
|
||||
html[data-theme="dark"] .bd-article-container .toctree-wrapper a:hover,
|
||||
html[data-theme="dark"] .bd-article-container li > a:hover {
|
||||
color: #0969da !important;
|
||||
}
|
||||
.bd-content a > code, .bd-content code > a { color: inherit; }
|
||||
|
||||
.bd-content a.opencv-enum-link,
|
||||
@@ -237,8 +246,36 @@ html[data-theme="dark"] table.opencv-meta-table td:last-child { border-right: no
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
.bd-header .navbar { min-height: 3.25rem; padding-top: 0.25rem; padding-bottom: 0.25rem; }
|
||||
.bd-header .navbar-header-items__start { gap: 1rem; align-items: center; }
|
||||
.bd-header img.logo__image { max-height: 36px; }
|
||||
.bd-header .navbar-header-items__start { gap: 15px; align-items: center; padding-left: 3rem; }
|
||||
/* OpenCV SVG logo: scale the vector to the official proportions. */
|
||||
.bd-header img.logo__image {
|
||||
height: 46px;
|
||||
width: auto;
|
||||
max-width: 50px;
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.navbar-brand .logo__title {
|
||||
font-weight: 600;
|
||||
font-size: 1.45rem;
|
||||
color: inherit;
|
||||
}
|
||||
/* "OpenCV" wordmark + "Open Source Computer Vision" subtitle stacked in a
|
||||
column to the right of the SVG mark (see _templates/navbar-logo.html). */
|
||||
.navbar-brand.logo .logo__textwrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.navbar-brand.logo .logo__textwrap .logo__title { margin: 0; }
|
||||
.navbar-brand.logo .logo__subtitle {
|
||||
margin: 0;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 400;
|
||||
color: var(--pst-color-text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.version-badge {
|
||||
display: inline-block;
|
||||
@@ -283,17 +320,73 @@ html[data-theme="dark"] table.opencv-meta-table td:last-child { border-right: no
|
||||
.bd-header button.theme-switch-button:hover,
|
||||
.bd-header .navbar-icon-links a.nav-link:hover { color: var(--opencv-accent); }
|
||||
|
||||
/* --- Version switcher dropdown (navbar) -------------------------------- *
|
||||
* Sits in the `navbar_start` slot right after `navbar-logo`. The custom
|
||||
* template `_templates/opencv-version-switcher.html` renders a plain
|
||||
* `<select>`; styles below give it a compact pill shape that reads as a
|
||||
* peer of the wordmark in both light and dark themes. */
|
||||
.opencv-version-switcher {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.opencv-version-switcher #opencv-version-select {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
font: 500 0.85rem/1.2 "Inter", sans-serif;
|
||||
color: var(--pst-color-text-base);
|
||||
background-color: var(--pst-color-surface);
|
||||
border: 1px solid var(--pst-color-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.2rem 1.8rem 0.2rem 0.6rem;
|
||||
/* Dropdown arrow drawn via background SVG so we can colour it via
|
||||
* CSS variables (light/dark) instead of relying on the UA default. */
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='%23656d76' d='M0 0l5 6 5-6z'/></svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 0.55rem center;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
.opencv-version-switcher #opencv-version-select:hover {
|
||||
border-color: var(--opencv-accent, #0969da);
|
||||
}
|
||||
.opencv-version-switcher #opencv-version-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--opencv-accent, #0969da);
|
||||
box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.25);
|
||||
}
|
||||
html[data-theme="dark"] .opencv-version-switcher #opencv-version-select {
|
||||
color: #e6edf3;
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.18);
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='%238b949e' d='M0 0l5 6 5-6z'/></svg>");
|
||||
}
|
||||
html[data-theme="dark"] .opencv-version-switcher #opencv-version-select option {
|
||||
background-color: #1c2128;
|
||||
color: #e6edf3;
|
||||
}
|
||||
|
||||
/* --- Wider reading column ---------------------------------------------- */
|
||||
@media (min-width: 960px) {
|
||||
.bd-page-width,
|
||||
.bd-container__inner.bd-page-width { max-width: none !important; }
|
||||
.bd-container { max-width: none !important; }
|
||||
.bd-sidebar-primary { padding-left: 1rem !important; padding-right: 1rem !important; }
|
||||
.bd-sidebar-primary { padding-left: 0.1rem !important; padding-right: 0.75rem !important; }
|
||||
.bd-main .bd-content,
|
||||
.bd-main .bd-article-container { padding-left: 2rem; padding-right: 2rem; }
|
||||
.bd-main .bd-article { padding-left: 0; padding-right: 0; }
|
||||
}
|
||||
|
||||
/* Narrower left section-nav sidebar — the theme default is width:25%, which is
|
||||
~480px on a wide screen (very intrusive). Pin it to a sensible fixed width so
|
||||
the content and the (often wide) diagrams get more room. Desktop only; the
|
||||
mobile off-canvas drawer keeps the theme's own sizing. */
|
||||
@media (min-width: 960px) {
|
||||
.bd-sidebar-primary { width: 16rem; flex: 0 0 16rem; }
|
||||
}
|
||||
|
||||
.bd-sidebar-primary nav.bd-links { margin-right: 0 !important; }
|
||||
.bd-sidebar-primary nav.bd-docs-nav p.bd-links__title {
|
||||
font-size: 0.9rem !important;
|
||||
@@ -356,6 +449,35 @@ div.highlight pre {
|
||||
.highlight .c, .highlight .ch, .highlight .cm,
|
||||
.highlight .cpf, .highlight .c1, .highlight .cs { font-style: normal !important; }
|
||||
|
||||
/* Clickable tokens inside code blocks: force link blue on every
|
||||
* descendant of `<a>` inside `<pre>`. Pygments wraps identifiers in
|
||||
* `<span class="n">…</span>` (or `nc`/`nf`/…) and colours them per
|
||||
* the theme stylesheet — those rules win over the inherited `<a>`
|
||||
* color, so the link text renders as ordinary dark-grey code, not
|
||||
* blue. This rule (high specificity, !important) restores the
|
||||
* theme-link blue across both modes, the same blue we use everywhere
|
||||
* else clickable tokens appear. */
|
||||
div.highlight pre a,
|
||||
div.highlight pre a *,
|
||||
div.highlight pre a span {
|
||||
color: #0969da !important;
|
||||
}
|
||||
html[data-theme="dark"] div.highlight pre a,
|
||||
html[data-theme="dark"] div.highlight pre a *,
|
||||
html[data-theme="dark"] div.highlight pre a span {
|
||||
color: #0969da !important;
|
||||
}
|
||||
div.highlight pre a:hover,
|
||||
div.highlight pre a:hover *,
|
||||
div.highlight pre a:hover span,
|
||||
html[data-theme="dark"] div.highlight pre a:hover,
|
||||
html[data-theme="dark"] div.highlight pre a:hover *,
|
||||
html[data-theme="dark"] div.highlight pre a:hover span {
|
||||
color: #0969da !important;
|
||||
text-decoration: underline !important;
|
||||
text-underline-offset: 0.18em;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] { color-scheme: dark; }
|
||||
html[data-theme="dark"] div.highlight span,
|
||||
html[data-theme="dark"] div.highlight * { color: unset !important; }
|
||||
@@ -388,7 +510,7 @@ html[data-theme="dark"] .highlight .mo { color: #6cb6ff !important; }
|
||||
html[data-theme="dark"] div.highlight pre {
|
||||
background: #2d333b !important;
|
||||
color: #cdd9e5 !important;
|
||||
border-left: 3px solid #539bf5 !important;
|
||||
border-left: 3px solid #0969da !important;
|
||||
}
|
||||
html[data-theme="dark"] code,
|
||||
html[data-theme="dark"] .sig {
|
||||
@@ -566,7 +688,7 @@ html[data-theme="dark"] section[id$="-documentation"] > section > h3 {
|
||||
background: #111622;
|
||||
border-color: #2d3748;
|
||||
}
|
||||
html[data-theme="dark"] section[id$="-documentation"] > section > h3::before { color: #58a6ff; }
|
||||
html[data-theme="dark"] section[id$="-documentation"] > section > h3::before { color: #0969da; }
|
||||
|
||||
section[id$="-documentation"] > section > p,
|
||||
section[id$="-documentation"] > section > ul,
|
||||
@@ -592,6 +714,21 @@ section[id$="-documentation"] > section > p > code {
|
||||
section[id$="-documentation"] > section > p.opencv-api-sig > code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
/* Clickable type / enum-value tokens inside a member-detail signature: light
|
||||
blue, no underline. The ID-qualified selectors out-specify the per-section
|
||||
`#…-documentation a` colour rules so signature links win everywhere. */
|
||||
.bd-content a.opencv-sig-link,
|
||||
#function-documentation a.opencv-sig-link,
|
||||
#typedef-documentation a.opencv-sig-link {
|
||||
color: #4ea1ff !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.bd-content a.opencv-sig-link:hover { color: #6cb6ff !important; }
|
||||
html[data-theme="dark"] .bd-content a.opencv-sig-link,
|
||||
html[data-theme="dark"] #function-documentation a.opencv-sig-link,
|
||||
html[data-theme="dark"] #typedef-documentation a.opencv-sig-link {
|
||||
color: #6cb6ff !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] section[id$="-documentation"] > section code,
|
||||
html[data-theme="dark"] section[id$="-documentation"] > section code span {
|
||||
@@ -702,6 +839,38 @@ html[data-theme="dark"] button.copybtn.success {
|
||||
button.copybtn { opacity: 1 !important; }
|
||||
}
|
||||
|
||||
/* --- Keep the copy button reachable on tall code blocks ---------------- */
|
||||
/* sphinx-copybutton pins the button (`position: absolute; top`) to the top of
|
||||
its code block, so on a block taller than the viewport — e.g. the long C++
|
||||
sample under group functions like cv::imwrite — it scrolls out of sight the
|
||||
moment you read past the first lines, and the whole button looks "missing".
|
||||
Make it `position: sticky` so it rides along at the top-right of the visible
|
||||
area instead. Hover-reveal behaviour is untouched (no opacity change); the
|
||||
button is still a child of `.highlight`, so `.highlight:hover .copybtn` still
|
||||
fires.
|
||||
Sticky needs NO non-`visible` overflow between the button and the scrolling
|
||||
viewport (the window). Two ancestors set overflow and silently disabled it:
|
||||
the member-detail card (rounded-corner clip) and the theme's
|
||||
`.bd-article-container`. Neither actually scrolls — the window does — so
|
||||
relaxing them is safe; the secondary "On this page" TOC stays sticky
|
||||
(verified). `display:flex` + `order:-1` put the button above <pre> in flow so
|
||||
`top` has something to pin to; the negative bottom margin cancels the
|
||||
button's own box so it overlays the code instead of pushing it down. */
|
||||
div.bd-article-container { overflow: visible !important; }
|
||||
section[id$="-documentation"] > section { overflow: visible !important; }
|
||||
div.highlight { display: flex !important; flex-direction: column; }
|
||||
div.highlight > pre { min-width: 0; } /* let the flex item shrink so overflow-x: auto still scrolls */
|
||||
div.highlight > button.copybtn {
|
||||
order: -1; /* render above <pre> so sticky `top` can pin it */
|
||||
position: sticky;
|
||||
align-self: flex-end; /* hold it at the right edge */
|
||||
margin: 0.5rem 0.5rem -2.35rem 0; /* negative bottom overlays the code, no extra height */
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* --- Class collaboration diagram (.opencv-coll-graph) ------------------ */
|
||||
/* Inlined as <svg> at build-finished (so links work); two theme variants carry
|
||||
the theme's .only-light/.only-dark classes. Size only — no display/filter. */
|
||||
.opencv-coll-graph {
|
||||
display: block;
|
||||
margin: 1rem auto 1.5rem;
|
||||
@@ -716,6 +885,66 @@ html[data-theme="dark"] button.copybtn.success {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Scroll box wrapping every inlined diagram (class collaboration graphs AND the
|
||||
large file include-dependency graphs). The whole graph stays reachable —
|
||||
scrolls when wider/taller than the content area, fits otherwise, never cut. */
|
||||
.opencv-graph-scroll {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
margin: 1rem 0 1.5rem;
|
||||
text-align: center; /* center a graph narrower than the content area */
|
||||
}
|
||||
.opencv-graph-scroll > svg.opencv-coll-graph {
|
||||
display: block;
|
||||
margin: 0 auto; /* centered when it fits; auto-margins collapse on pan */
|
||||
max-width: none; /* keep natural size so the box can pan a big graph */
|
||||
}
|
||||
|
||||
/* File-page member declaration boxes (parsed-literal). Look like a code block
|
||||
but contain a clickable member name. The `:class:` lands on the <pre>
|
||||
(pre.opencv-decl); guard against a wrapper too (.opencv-decl pre). */
|
||||
pre.opencv-decl,
|
||||
.opencv-decl pre,
|
||||
.opencv-decl {
|
||||
font-family: var(--pst-font-family-monospace);
|
||||
font-size: 0.85em;
|
||||
line-height: 1.5;
|
||||
padding: 0.6rem 0.9rem;
|
||||
margin: 0.25rem 0 0.5rem;
|
||||
border-radius: 0.35rem;
|
||||
overflow-x: auto; /* long signatures scroll, never clip */
|
||||
background-color: var(--pst-color-surface);
|
||||
border: 1px solid var(--pst-color-border);
|
||||
}
|
||||
/* The one real link in the box (the member name): blue, no underline. */
|
||||
pre.opencv-decl a,
|
||||
.opencv-decl a,
|
||||
.opencv-decl a.reference {
|
||||
color: #0969da !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
html[data-theme="dark"] pre.opencv-decl a,
|
||||
html[data-theme="dark"] .opencv-decl a,
|
||||
html[data-theme="dark"] .opencv-decl a.reference { color: #539bf5 !important; }
|
||||
/* --- Clickable enum synopsis (.opencv-enum-synopsis) ------------------- */
|
||||
/* Link colour + no-underline handled by the central enum-links rule near
|
||||
* the top of this file; this block layers explicit dark-mode coverage so
|
||||
* the unified `#0969da` (no hover differentiation) applies even if the
|
||||
* central rule changes. */
|
||||
.opencv-enum-synopsis a.opencv-enum-link {
|
||||
color: #0969da !important;
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.opencv-enum-synopsis a.opencv-enum-link:hover {
|
||||
color: #0550ae !important;
|
||||
}
|
||||
html[data-theme="dark"] .opencv-enum-synopsis a.opencv-enum-link,
|
||||
html[data-theme="dark"] .opencv-enum-synopsis a.opencv-enum-link:hover {
|
||||
color: #0969da !important;
|
||||
}
|
||||
|
||||
section#member-enumeration-documentation .highlight-cpp .n {
|
||||
color: #0969da !important;
|
||||
}
|
||||
@@ -904,7 +1133,10 @@ html[data-theme="dark"] table.api-reference-table tbody tr td:first-child {
|
||||
|
||||
table.api-reference-table tbody tr td a,
|
||||
table.api-reference-table tbody tr td a:link,
|
||||
table.api-reference-table tbody tr td a:visited {
|
||||
table.api-reference-table tbody tr td a:visited,
|
||||
table.api-reference-table tbody tr td a *,
|
||||
table.api-reference-table tbody tr td a:link *,
|
||||
table.api-reference-table tbody tr td a:visited * {
|
||||
color: var(--pst-color-link, #0969da) !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 500 !important;
|
||||
@@ -918,12 +1150,22 @@ html[data-theme="dark"] table.api-reference-table tbody tr td a,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:link,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:visited,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:hover,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:active {
|
||||
color: #ffffff !important;
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:active,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a *,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:link *,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:visited *,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:hover *,
|
||||
html[data-theme="dark"] table.api-reference-table tbody tr td a:active * {
|
||||
color: #0969da !important;
|
||||
}
|
||||
|
||||
table.api-reference-table tbody tr td a code,
|
||||
table.api-reference-table tbody tr td a code *,
|
||||
table.api-reference-table tbody tr td a span,
|
||||
table.api-reference-table tbody tr td a .std,
|
||||
table.api-reference-table tbody tr td a .std-doc,
|
||||
table.api-reference-table tbody tr td a .xref,
|
||||
table.api-reference-table tbody tr td a .pre,
|
||||
table.api-reference-table tbody tr td:not(:first-child) a code,
|
||||
table.api-reference-table tbody tr td:not(:first-child) a code * {
|
||||
color: inherit !important;
|
||||
@@ -946,6 +1188,43 @@ html[data-theme="dark"] table.api-reference-table tbody tr td:not(:first-child)
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Functions-table signature cell: the row is rendered as
|
||||
* `<a><code>cv::name</code></a> <code>(<a>Type</a> arg, …)</code>`
|
||||
* The unanchored `<code>` wrapping the parameter list (and any `<code>`
|
||||
* around inline tokens) picks up the default code-chip background +
|
||||
* border via the global `code` rule, which renders as a "grey box"
|
||||
* around the clickables. Strip the chip styling AND the 500 weight
|
||||
* that makes the link text read as bold — clickables here should be
|
||||
* plain blue text, not boxed-and-bold. Applies to both the cell
|
||||
* `<code>` and any inner `<span class="pre">` Sphinx wraps long
|
||||
* tokens in. */
|
||||
table.api-function-table tbody tr td code,
|
||||
table.api-function-table tbody tr td code *,
|
||||
table.api-function-table tbody tr td a,
|
||||
table.api-function-table tbody tr td a code,
|
||||
table.api-function-table tbody tr td a code * {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
font-weight: normal !important;
|
||||
font-size: inherit !important;
|
||||
}
|
||||
html[data-theme="dark"] table.api-function-table tbody tr td code,
|
||||
html[data-theme="dark"] table.api-function-table tbody tr td code *,
|
||||
html[data-theme="dark"] table.api-function-table tbody tr td a code,
|
||||
html[data-theme="dark"] table.api-function-table tbody tr td a code * {
|
||||
background: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Functions summary cards: the signature is split one parameter per line, each
|
||||
line its own <code> span joined by <br>, with a padded type column built from
|
||||
spaces (à la `_signature_lines`). Inline <code> collapses runs of spaces,
|
||||
which would wreck that alignment — preserve them and keep wrapping for long
|
||||
lists, so the card signature reads like the detail-block declaration. */
|
||||
table.api-function-table tbody tr td:not(:first-child) a code,
|
||||
table.api-function-table tbody tr td:not(:first-child) code {
|
||||
white-space: pre-wrap;
|
||||
@@ -1102,12 +1381,15 @@ table.api-typedef-table tbody tr td:nth-child(2) {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2),
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a,
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a:link,
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a:visited,
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a:hover {
|
||||
color: #ffffff !important;
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a:hover,
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a *,
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a:link *,
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a:visited *,
|
||||
html[data-theme="dark"] table.api-typedef-table tbody tr td:nth-child(2) a:hover * {
|
||||
color: #0969da !important;
|
||||
}
|
||||
|
||||
table.api-typedef-table tbody tr td:nth-child(2) a,
|
||||
@@ -1175,8 +1457,8 @@ a.opencv-class-more:hover {
|
||||
color: #0550ae;
|
||||
text-decoration: underline;
|
||||
}
|
||||
html[data-theme="dark"] a.opencv-class-more { color: #539bf5; }
|
||||
html[data-theme="dark"] a.opencv-class-more:hover { color: #6cb6ff; }
|
||||
html[data-theme="dark"] a.opencv-class-more { color: #0969da; }
|
||||
html[data-theme="dark"] a.opencv-class-more:hover { color: #0969da; }
|
||||
|
||||
.opencv-class-files { margin-top: 1.75rem; }
|
||||
.opencv-class-files > h2 { display: none; }
|
||||
@@ -1412,3 +1694,90 @@ a.SRScope {
|
||||
color: var(--pst-color-text-muted) !important;
|
||||
}
|
||||
.SRPage .SRStatus { color: var(--pst-color-text-muted); padding: 8px 16px; }
|
||||
|
||||
/* ============================================================
|
||||
* GLOBAL DARK-MODE CLICKABLE-BLUE OVERRIDE
|
||||
* ------------------------------------------------------------
|
||||
* One rule to keep every clickable a consistent blue in dark
|
||||
* mode. Light mode already paints links `#0969da`; pst-sphinx
|
||||
* repaints them lighter (or grey, or visited-purple) in dark
|
||||
* mode through dozens of class-specific rules. Rather than
|
||||
* chase each one, this final block forces every `<a>` (and
|
||||
* every descendant of `<a>`) inside `html[data-theme="dark"]`
|
||||
* back to the same `#0969da` link blue used in light mode —
|
||||
* body content, sidebar, TOC, tables, code chips, code-block
|
||||
* tokens, See-Also lines, breadcrumbs, footers. Visited /
|
||||
* hover / active / focused all collapse to the same hex per
|
||||
* the "no hover-blue, everything at rest" rule.
|
||||
*
|
||||
* Specificity: `html[data-theme="dark"] a` is (0,0,1,2); the
|
||||
* `* { color: unset }` rule earlier in the file is (0,0,1,2)
|
||||
* too but ours runs LATER and uses `!important`, so it wins.
|
||||
* ============================================================ */
|
||||
html[data-theme="dark"] a,
|
||||
html[data-theme="dark"] a:link,
|
||||
html[data-theme="dark"] a:visited,
|
||||
html[data-theme="dark"] a:focus,
|
||||
html[data-theme="dark"] a *,
|
||||
html[data-theme="dark"] a:link *,
|
||||
html[data-theme="dark"] a:visited *,
|
||||
html[data-theme="dark"] a:focus * {
|
||||
color: #0969da !important;
|
||||
}
|
||||
html[data-theme="dark"] a:hover,
|
||||
html[data-theme="dark"] a:hover *,
|
||||
html[data-theme="dark"] a:active,
|
||||
html[data-theme="dark"] a:active * {
|
||||
color: #0969da !important;
|
||||
}
|
||||
|
||||
/* Pygments-tokens-inside-link override.
|
||||
* The dark-mode Pygments rule `html[data-theme="dark"] .highlight .n
|
||||
* { color:#cdd9e5 !important }` (3-class specificity) was beating the
|
||||
* global `html[data-theme="dark"] a *` rule above (1-class) when a
|
||||
* Pygments identifier span (`.n`, `.nc`, `.nf`, `.na`, `.nb`, `.nv`,
|
||||
* `.nn`, `.pre`) sat inside an `<a>` — so enum-synopsis entries like
|
||||
* `cv::NORM_INF` rendered the same grey as plain identifiers. Adding
|
||||
* `.highlight` to the selector raises specificity to (0,0,3,2),
|
||||
* enough to win on every token class Pygments uses for names. */
|
||||
html[data-theme="dark"] .highlight a,
|
||||
html[data-theme="dark"] .highlight a *,
|
||||
html[data-theme="dark"] .highlight a .n,
|
||||
html[data-theme="dark"] .highlight a .na,
|
||||
html[data-theme="dark"] .highlight a .nb,
|
||||
html[data-theme="dark"] .highlight a .nc,
|
||||
html[data-theme="dark"] .highlight a .nf,
|
||||
html[data-theme="dark"] .highlight a .nv,
|
||||
html[data-theme="dark"] .highlight a .nn,
|
||||
html[data-theme="dark"] .highlight a .pre,
|
||||
html[data-theme="dark"] .highlight a .o,
|
||||
html[data-theme="dark"] .highlight a .p {
|
||||
color: #0969da !important;
|
||||
}
|
||||
html[data-theme="dark"] .highlight a:hover,
|
||||
html[data-theme="dark"] .highlight a:hover *,
|
||||
html[data-theme="dark"] .highlight a:hover .n,
|
||||
html[data-theme="dark"] .highlight a:hover .nc,
|
||||
html[data-theme="dark"] .highlight a:hover .nf {
|
||||
color: #0969da !important;
|
||||
}
|
||||
|
||||
/* Parameter-name box in member-detail "Parameters" lists (light + dark). The
|
||||
`section[…-documentation]` selector out-specifies the blanket transparent
|
||||
`<code>` rule so the box survives in dark mode. */
|
||||
code.opencv-param-name,
|
||||
section[id$="-documentation"] code.opencv-param-name {
|
||||
background-color: #eff1f3 !important;
|
||||
border: 1px solid #d0d7de !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 0.1em 0.4em !important;
|
||||
font-size: 0.85em !important;
|
||||
color: #24292f !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
html[data-theme="dark"] code.opencv-param-name,
|
||||
html[data-theme="dark"] section[id$="-documentation"] code.opencv-param-name {
|
||||
background-color: #2d333b !important;
|
||||
border: 1px solid #444c56 !important;
|
||||
color: #adbac7 !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> <svg xmlns="http://www.w3.org/2000/svg" width="164" height="153" viewBox="0 0 164 153" fill="none"><path d="M144.618 79.1998C156.154 85.9868 163.907 98.5221 163.932 112.877C163.969 134.484 146.484 152.031 124.877 152.068C103.269 152.106 85.7225 134.62 85.6847 113.013C85.6597 98.6587 93.3683 86.0964 104.881 79.2691L116.123 98.2666C116.405 98.7431 116.245 99.3554 115.787 99.6669C111.536 102.561 108.748 107.443 108.758 112.973C108.773 121.837 115.972 129.011 124.836 128.995C133.701 128.98 140.874 121.781 140.859 112.917C140.849 107.387 138.044 102.515 133.783 99.6355C133.324 99.3256 133.162 98.7139 133.442 98.2364L144.618 79.1998Z" fill="#128DFF"></path><path d="M58.2668 78.9714C52.6177 75.8052 46.1027 74 39.1662 74C17.5588 74 0.0426025 91.5162 0.0426025 113.124C0.0426025 134.731 17.5588 152.247 39.1662 152.247C60.8798 152.247 78.8229 133.813 78.2771 112.12H56.2529C55.6746 112.12 55.2192 112.609 55.2155 113.188C55.1596 121.833 47.9463 129.174 39.1662 129.174C30.3016 129.174 23.1155 121.988 23.1155 113.124C23.1155 104.259 30.3016 97.0729 39.1662 97.0729C41.4876 97.0729 43.694 97.5657 45.6863 98.4525C46.1732 98.6692 46.7542 98.505 47.0247 98.0459L58.2668 78.9714Z" fill="#8BDA67"></path><path d="M61.431 72.834C49.9062 66.0268 42.1757 53.4779 42.1757 39.1235C42.1757 17.5162 59.6919 0 81.2992 0C102.907 0 120.423 17.5162 120.423 39.1235C120.423 53.4779 112.692 66.0268 101.167 72.834L89.9591 53.8169C89.678 53.3399 89.8386 52.7279 90.2968 52.4171C94.5531 49.5307 97.3499 44.6537 97.3499 39.1235C97.3499 30.259 90.1638 23.0729 81.2992 23.0729C72.4347 23.0729 65.2485 30.259 65.2485 39.1235C65.2485 44.6537 68.0453 49.5307 72.3016 52.4171C72.7599 52.7279 72.9204 53.3399 72.6393 53.8169L61.431 72.834Z" fill="#FF2A44"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,135 @@
|
||||
/* Single source of truth for the documentation version dropdown.
|
||||
*
|
||||
* Consumed by BOTH:
|
||||
* - the legacy Doxygen pages (2.x .. 4.x, 5.0.0-pre): the block below
|
||||
* renders this list into the `#projectnumber` span next to the logo
|
||||
* (needs jQuery, which those pages already load); and
|
||||
* - the new Sphinx (PyData) 5.0 docs: the navbar switcher template reads
|
||||
* `window.OPENCV_DOC_VERSIONS` directly and builds its own <select>.
|
||||
*
|
||||
* To publish a new release, add ONE entry here ['<label>', '<site-path>']
|
||||
* and redeploy this file to the bucket root — the option then appears in
|
||||
* the dropdown on every version's pages at once. Do not maintain a second
|
||||
* list anywhere. */
|
||||
window.OPENCV_DOC_VERSIONS = [
|
||||
['4.13.0', '/4.13.0'],
|
||||
['4.12.0', '/4.12.0'],
|
||||
['4.11.0', '/4.11.0'],
|
||||
['5.0', '/5.0'],
|
||||
['5.0.0alpha', '/5.0.0-alpha'],
|
||||
['4.10.0', '/4.10.0'],
|
||||
['4.9.0', '/4.9.0'],
|
||||
['4.8.0', '/4.8.0'],
|
||||
['4.7.0', '/4.7.0'],
|
||||
['4.6.0', '/4.6.0'],
|
||||
['4.5.5', '/4.5.5'],
|
||||
['4.5.4', '/4.5.4'],
|
||||
['4.5.3', '/4.5.3'],
|
||||
['4.5.2', '/4.5.2'],
|
||||
['4.5.1', '/4.5.1'],
|
||||
['4.5.0', '/4.5.0'],
|
||||
['4.4.0', '/4.4.0'],
|
||||
['4.3.0', '/4.3.0'],
|
||||
['4.2.0', '/4.2.0'],
|
||||
['4.1.2', '/4.1.2'],
|
||||
['4.1.1', '/4.1.1'],
|
||||
['4.1.0', '/4.1.0'],
|
||||
['4.0.1', '/4.0.1'],
|
||||
['4.0.0', '/4.0.0'],
|
||||
// no more 3.4 releases: ['3.4.21-pre', '/3.4'],
|
||||
['3.4.20-dev', '/3.4'],
|
||||
['3.4.20', '/3.4.20'],
|
||||
['3.4.19', '/3.4.19'],
|
||||
['3.4.18', '/3.4.18'],
|
||||
['3.4.17', '/3.4.17'],
|
||||
['3.4.16', '/3.4.16'],
|
||||
['3.4.15', '/3.4.15'],
|
||||
['3.4.14', '/3.4.14'],
|
||||
['3.4.13', '/3.4.13'],
|
||||
['3.4.12', '/3.4.12'],
|
||||
['3.4.11', '/3.4.11'],
|
||||
['3.4.10', '/3.4.10'],
|
||||
['3.4.9', '/3.4.9'],
|
||||
['3.4.8', '/3.4.8'],
|
||||
['3.4.7', '/3.4.7'],
|
||||
['3.4.6', '/3.4.6'],
|
||||
['3.4.5', '/3.4.5'],
|
||||
['3.4.4', '/3.4.4'],
|
||||
['3.4.3', '/3.4.3'],
|
||||
['3.4.2', '/3.4.2'],
|
||||
['3.4.1', '/3.4.1'],
|
||||
['3.4.0', '/3.4.0'],
|
||||
['3.3.1', '/3.3.1'],
|
||||
['3.3.0', '/3.3.0'],
|
||||
['3.2.0', '/3.2.0'],
|
||||
['3.1.0', '/3.1.0'],
|
||||
['3.0.0', '/3.0.0'],
|
||||
];
|
||||
|
||||
// Present the dropdown newest-first regardless of the order entries were added
|
||||
// above, so publishing a release stays a one-line append (no manual re-sorting).
|
||||
// Sort by numeric major.minor.patch descending; suffixes like "-dev"/"-pre"/
|
||||
// "alpha" carry no digits and are ignored by the key, so same-base variants
|
||||
// (e.g. "5.0" / "5.0.0-pre" / "5.0.0alpha") tie — Array.sort is stable, so they
|
||||
// keep the order written above.
|
||||
window.OPENCV_DOC_VERSIONS.sort(function (a, b) {
|
||||
var ka = (a[0].match(/\d+/g) || []).map(Number);
|
||||
var kb = (b[0].match(/\d+/g) || []).map(Number);
|
||||
for (var i = 0; i < Math.max(ka.length, kb.length); i++) {
|
||||
var d = (kb[i] || 0) - (ka[i] || 0);
|
||||
if (d) return d;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
function renderDoxygenVersionDropdown() {
|
||||
// Doxygen-only rendering. The Sphinx pages have no `#projectnumber` and
|
||||
// no jQuery, so bail early there — they read OPENCV_DOC_VERSIONS above
|
||||
// and build their own dropdown in the navbar template.
|
||||
if (!document.getElementById("projectnumber") || typeof window.jQuery === "undefined")
|
||||
return;
|
||||
var versions = window.OPENCV_DOC_VERSIONS;
|
||||
var h = '<select>';
|
||||
var current_ver = $("#projectnumber")[0].innerText || versions[0][0];
|
||||
current_ver = current_ver.trim();
|
||||
for (i = 0; i < versions.length; i++) {
|
||||
selected = ''
|
||||
if(current_ver === versions[i][0])
|
||||
selected = ' selected="selected"';
|
||||
h += '<option value="' + versions[i][0] + '"' + selected + '>' + versions[i][0] + '</option>';
|
||||
}
|
||||
h += '</select>';
|
||||
$("#projectnumber")[0].innerHTML = h;
|
||||
$("#projectnumber select")[0].addEventListener('change', function() {
|
||||
var v = $(this).children('option:selected').attr('value');
|
||||
var path = undefined;
|
||||
for (i = 0; i < versions.length; i++) {
|
||||
if(v === versions[i][0]) {
|
||||
path = versions[i][1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!path) return;
|
||||
// Go straight to the chosen version's index via its site-absolute path,
|
||||
// so switching works from ANY page of ANY version — no fragile attempt to
|
||||
// substitute the current version inside the current URL (which fails on
|
||||
// pages whose path isn't "/<version>/...", e.g. the 5.0 C++ API tree).
|
||||
// The S3 *website* endpoint serves "/4.13.0/" as index.html, but the plain
|
||||
// REST endpoint does not, so append index.html explicitly.
|
||||
if (!/\.html?($|[?#])/.test(path))
|
||||
path = path.replace(/\/+$/, '') + '/index.html';
|
||||
window.location.href = path; // navigate
|
||||
});
|
||||
return current_ver;
|
||||
}
|
||||
|
||||
// Run as soon as possible. On a normal page load this fires on DOMContentLoaded;
|
||||
// but on the already-deployed legacy pages this file is pulled in dynamically
|
||||
// (a loader appended to dynsections.js) and may arrive AFTER DOMContentLoaded
|
||||
// has fired — in which case render immediately instead of waiting for an event
|
||||
// that will never come again.
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", renderDoxygenVersionDropdown);
|
||||
} else {
|
||||
renderDoxygenVersionDropdown();
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
{{ super() }}
|
||||
<link rel="stylesheet" href="{{ _search }}search.css"/>
|
||||
<script src="{{ pathto('_static/search_map.js', 1) }}"></script>
|
||||
<script src="{{ _doxy }}cookie.js"></script>
|
||||
<script src="{{ _search }}searchdata.js"></script>
|
||||
<script src="{{ _search }}search.js"></script>
|
||||
<script>
|
||||
@@ -80,24 +81,42 @@
|
||||
init_search();
|
||||
searchBox.OnSelectItem(0); // default to "All" on every page load
|
||||
|
||||
// Redirect result clicks: Doxygen filename → Sphinx page via sphinxPageMap.
|
||||
var sphinxRoot = "{{ '../' * (pagename or '').count('/') }}";
|
||||
// Doxygen result href -> Sphinx page, or "" if that page is not in our build.
|
||||
function resolveSphinx(href) {
|
||||
if (!href || href.indexOf("javascript:") === 0) return "";
|
||||
var map = (typeof sphinxPageMap !== "undefined") ? sphinxPageMap : {};
|
||||
var stem = href.split("/").pop().replace(/#.*$/, "").replace(/\.html$/, "");
|
||||
var key = stem.replace(/^group__/, "").replace(/^tutorial_/, "").replace(/__/g, "_");
|
||||
var path = map[stem] || map[key];
|
||||
if (!path && stem.indexOf("namespace") === 0) path = map["core_basic"];
|
||||
var rest = stem;
|
||||
while (!path && rest.indexOf("_1_1") >= 0) {
|
||||
rest = rest.substring(0, rest.lastIndexOf("_1_1"));
|
||||
path = map[rest];
|
||||
}
|
||||
return path || "";
|
||||
}
|
||||
|
||||
document.body.addEventListener("click", function (e) {
|
||||
var a = e.target.closest ? e.target.closest("a.SRSymbol, a.SRScope") : null;
|
||||
if (!a) return;
|
||||
var href = a.getAttribute("href") || "";
|
||||
if (!href || href.startsWith("javascript:")) return;
|
||||
var fname = href.split("/").pop().replace(/#.*$/, ""); // e.g. "group__core.html"
|
||||
var stem = fname.replace(/\.html$/, ""); // e.g. "group__core"
|
||||
// Normalise to the Sphinx stem: strip group__/tutorial_ prefix, __ → _
|
||||
var key = stem.replace(/^group__/, "").replace(/^tutorial_/, "").replace(/__/g, "_");
|
||||
// Also try exact stem (covers classcv_*, structcv_*, namespacecv_*, etc.)
|
||||
var sphinxPath = (typeof sphinxPageMap !== "undefined") &&
|
||||
(sphinxPageMap[stem] || sphinxPageMap[key]);
|
||||
var sphinxPath = resolveSphinx(href);
|
||||
if (sphinxPath) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.location.href = sphinxRoot + sphinxPath;
|
||||
// Doxygen #autotoc_md anchors don't exist in Sphinx; rebuild from heading text.
|
||||
var frag = "";
|
||||
if (/#autotoc_md/.test(href)) {
|
||||
var slug = (a.textContent || "").trim().toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
||||
if (slug) frag = "#" + slug;
|
||||
}
|
||||
window.location.href = sphinxRoot + sphinxPath + frag;
|
||||
} else if (href.indexOf("doc/doxygen/html/") >= 0) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}, true);
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{# OpenCV navbar logo: the built-in pydata `navbar-logo`, plus an
|
||||
"Open Source Computer Vision" subtitle stacked under the wordmark —
|
||||
mirrors the legacy docs.opencv.org header. The title + subtitle are
|
||||
wrapped in `.logo__textwrap` so they form a column to the right of the
|
||||
SVG mark; the version switcher (a separate navbar_start slot) still sits
|
||||
inline beside the title. Link-resolution logic is copied verbatim from
|
||||
the theme's own navbar-logo.html so the brand href behaves identically. #}
|
||||
{% if theme_logo_link %}
|
||||
{% set href = theme_logo_link %}
|
||||
{% else %}
|
||||
{% if not theme_logo.get("link") %}
|
||||
{% set href = pathto(root_doc) %}
|
||||
{% elif hasdoc(theme_logo.get("link")) %}
|
||||
{% set href = pathto(theme_logo.get("link")) %} {# internal page #}
|
||||
{% else %}
|
||||
{% set href = theme_logo.get("link") %} {# external url #}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<a class="navbar-brand logo" href="{{ href }}">
|
||||
{% set is_logo = "light" in theme_logo["image_relative"] %}
|
||||
{% set alt = theme_logo.get("alt_text", "" if theme_logo.get("text") else "%s - Home" % docstitle) %}
|
||||
{% if is_logo %}
|
||||
{% if default_mode is undefined or default_mode == "auto" %}
|
||||
{% set default_mode = "light" %}
|
||||
{% endif %}
|
||||
{% set js_mode = "light" if default_mode == "dark" else "dark" %}
|
||||
<img src="{{ theme_logo['image_relative'][default_mode] }}" class="logo__image only-{{ default_mode }}" alt="{{ alt }}"/>
|
||||
<img src="{{ theme_logo['image_relative'][js_mode] }}" class="logo__image only-{{ js_mode }} pst-js-only" alt="{{ alt }}"/>
|
||||
{% endif %}
|
||||
{% if not is_logo or theme_logo.get("text") %}
|
||||
<span class="logo__textwrap">
|
||||
<p class="title logo__title">{{ theme_logo.get("text") or docstitle }}</p>
|
||||
<p class="logo__subtitle">Open Source Computer Vision</p>
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
@@ -0,0 +1,81 @@
|
||||
{# Navbar version switcher: a plain `<select>` rendered right after the
|
||||
`navbar-logo` slot, mirroring the legacy docs.opencv.org header where the
|
||||
version selector sits inline with the wordmark.
|
||||
|
||||
Single source of truth: the options are built client-side from
|
||||
`window.OPENCV_DOC_VERSIONS`, defined in `_static/version.js` — the SAME
|
||||
list the legacy Doxygen pages carry. There is no build-time scrape and no
|
||||
second list: publishing a release is one edit to that file. We load it via
|
||||
`pathto('_static/version.js', 1)` (the theme's own idiom for JS assets) so
|
||||
the script resolves relative to each page, which means the dropdown
|
||||
populates fully in a local `sphinx-build` preview AND on the deployed site,
|
||||
regardless of how deep the page sits or which prefix it lands under.
|
||||
|
||||
The option whose label equals this build's `release` is marked selected;
|
||||
every other option carries that version's site-absolute path and the
|
||||
`onchange` handler navigates the same tab there. #}
|
||||
<form class="opencv-version-switcher" role="search"
|
||||
aria-label="OpenCV documentation version"
|
||||
onsubmit="return false">
|
||||
<select id="opencv-version-select"
|
||||
aria-label="Select OpenCV documentation version"
|
||||
onchange="if(this.value){window.location.href=this.value;}">
|
||||
<option value="" selected>{{ release }}</option>
|
||||
</select>
|
||||
</form>
|
||||
<script src="{{ pathto('_static/version.js', 1) }}"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var current = {{ release | tojson }};
|
||||
|
||||
// Each version.js entry is a bare directory path (e.g. "/4.13.0"). Link
|
||||
// straight to that directory's index document: the S3 *website* endpoint
|
||||
// would resolve "/4.13.0/" to index.html on its own, but the plain REST
|
||||
// endpoint (bucket.s3.amazonaws.com) does NOT — there "/4.13.0" is a
|
||||
// missing object key. Appending "/index.html" works on both endpoints.
|
||||
function toHref(path) {
|
||||
if (/\.html?($|[?#])/.test(path)) return path; // already a file
|
||||
return path.replace(/\/+$/, '') + '/index.html';
|
||||
}
|
||||
|
||||
function populate() {
|
||||
var versions = window.OPENCV_DOC_VERSIONS;
|
||||
if (!Array.isArray(versions)) return; // version.js absent (list stays at current release)
|
||||
document.querySelectorAll('#opencv-version-select').forEach(function (sel) {
|
||||
// Rebuild from the canonical list so the order matches the legacy
|
||||
// docs.opencv.org dropdown exactly.
|
||||
while (sel.firstChild) sel.removeChild(sel.firstChild);
|
||||
versions.forEach(function (v) {
|
||||
var label = v[0], path = v[1];
|
||||
var opt = document.createElement('option');
|
||||
// Selecting the current version is a no-op (empty value -> the
|
||||
// inline `onchange` guard skips navigation); every other option
|
||||
// navigates to that version's index document.
|
||||
opt.value = (label === current) ? '' : toHref(path);
|
||||
opt.textContent = label;
|
||||
if (label === current) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetSelection() {
|
||||
// After picking an older version and hitting Back, the browser can
|
||||
// restore the <select> from bfcache with the OLD choice. Snap it back
|
||||
// to the current release (the option whose value is empty).
|
||||
document.querySelectorAll('#opencv-version-select').forEach(function (sel) {
|
||||
for (var i = 0; i < sel.options.length; i++) {
|
||||
if (sel.options[i].value === '') { sel.selectedIndex = i; break; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', populate);
|
||||
} else {
|
||||
populate();
|
||||
}
|
||||
// `pageshow` fires on initial load AND on bfcache restore (Back button).
|
||||
window.addEventListener('pageshow', resetSelection);
|
||||
})();
|
||||
</script>
|
||||
+11
-2
@@ -24,7 +24,7 @@ from conf_helpers.postprocess import _inline_coll_graphs_on_finish
|
||||
# -- Project ----------------------------------------------------------------
|
||||
project = "OpenCV"
|
||||
author = "OpenCV Team"
|
||||
release = "5.x"
|
||||
release = "5.0"
|
||||
|
||||
# -- Sphinx core ------------------------------------------------------------
|
||||
extensions = ["myst_parser"]
|
||||
@@ -143,7 +143,16 @@ html_css_files = [
|
||||
"custom.css",
|
||||
]
|
||||
html_theme_options = {
|
||||
"logo": {"text": f"OpenCV {release}"},
|
||||
"logo": {
|
||||
"text": f"OpenCV {release}",
|
||||
"image_light": "_static/opencv-logo.svg",
|
||||
"image_dark": "_static/opencv-logo.svg",
|
||||
},
|
||||
# Navbar layout: logo on the left, version switcher right beside it —
|
||||
# mirrors the legacy docs.opencv.org header where the version selector
|
||||
# sits inline with the wordmark. The switcher template reads the shared
|
||||
# /version.js (window.OPENCV_DOC_VERSIONS); no build-time list is generated.
|
||||
"navbar_start": ["navbar-logo", "opencv-version-switcher"],
|
||||
"header_links_before_dropdown": 6,
|
||||
"external_links": [
|
||||
{"docname": master_doc, "name": "Main Page"},
|
||||
|
||||
@@ -11,6 +11,37 @@ from .state import *
|
||||
from .xml_render import _patch_namespace_xml_for_breathe
|
||||
from .stubs import _generate_api_stubs
|
||||
|
||||
|
||||
def _discover_orphan_groups(xml_dir):
|
||||
if not xml_dir.is_dir():
|
||||
return [], []
|
||||
folders = set()
|
||||
for _root in (OPENCV_ROOT / "modules", CONTRIB_ROOT):
|
||||
if _root.is_dir():
|
||||
folders.update(d.name for d in _root.iterdir()
|
||||
if (d / "include" / "opencv2").is_dir())
|
||||
skip = set(folders) | {f.replace("_", "__") for f in folders}
|
||||
skip |= {_module_group_stem(m) for m in folders}
|
||||
all_groups, child = set(), set()
|
||||
for gx in xml_dir.glob("group__*.xml"):
|
||||
all_groups.add(gx.stem)
|
||||
try:
|
||||
xml = gx.read_text(encoding="utf-8", errors="ignore")
|
||||
except OSError:
|
||||
continue
|
||||
child.update(re.findall(r'<innergroup refid="(group__[^"]+)"', xml))
|
||||
main, extra = [], []
|
||||
for g in sorted(all_groups - child):
|
||||
stem = g[len("group__"):]
|
||||
name = stem.replace("__", "_")
|
||||
if stem in skip or name in skip:
|
||||
continue
|
||||
xml = (xml_dir / f"{g}.xml").read_text(encoding="utf-8", errors="ignore")
|
||||
loc = re.search(r'<location file="([^"]*)"', xml)
|
||||
(extra if loc and "opencv_contrib/" in loc.group(1) else main).append(name)
|
||||
return main, extra
|
||||
|
||||
|
||||
# Skip when input root is DOC_ROOT: writing there is forbidden.
|
||||
if _BIB_ENTRIES_SORTED and SPHINX_INPUT_ROOT != DOC_ROOT:
|
||||
try:
|
||||
@@ -111,12 +142,15 @@ if API_MODULES:
|
||||
OPENCV_ROOT / "modules" / m).is_dir()
|
||||
_main_api = [m for m in API_MODULES if not _is_contrib(m)]
|
||||
_extra_api = [m for m in API_MODULES if _is_contrib(m)]
|
||||
_main_orphans, _extra_orphans = _discover_orphan_groups(_API_XML_DIR)
|
||||
_generate_api_stubs(_main_api, _API_XML_DIR, SPHINX_INPUT_ROOT / "main_modules",
|
||||
root_anchor="api_root", root_title="Main modules")
|
||||
root_anchor="api_root", root_title="Main modules",
|
||||
extra_groups=_main_orphans)
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "main_modules")
|
||||
if _extra_api:
|
||||
if _extra_api or _extra_orphans:
|
||||
_generate_api_stubs(_extra_api, _API_XML_DIR, SPHINX_INPUT_ROOT / "extra_modules",
|
||||
root_anchor="extra_api_root", root_title="Extra modules")
|
||||
root_anchor="extra_api_root", root_title="Extra modules",
|
||||
extra_groups=_extra_orphans)
|
||||
_scan_internal(SPHINX_INPUT_ROOT / "extra_modules")
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
from __future__ import annotations
|
||||
import pathlib, re
|
||||
|
||||
from .state import _doxy_page_to_local, _DOXY_ANCHOR_TO_MEMBER
|
||||
from .state import (_doxy_page_to_local, _DOXY_ANCHOR_TO_MEMBER, DOXYGEN_BASE_URL,
|
||||
_LOCAL_CLASS_URL, _LOCAL_TYPEDEF_URL, _FILE_URL, _API_XML_DIR, DOC_ROOT)
|
||||
|
||||
|
||||
def _doxy_parent_page(page: str, api_dir: pathlib.Path) -> str:
|
||||
@@ -77,10 +78,14 @@ def _inline_collaboration_svgs(api_dir: pathlib.Path,
|
||||
return m.group(0)
|
||||
svg = href_re.sub(_rewrite_href, svg[start:])
|
||||
# carry theme classes + alt for dark mode / a11y
|
||||
return svg.replace(
|
||||
svg = svg.replace(
|
||||
"<svg ",
|
||||
f'<svg class="{m.group("cls")}" role="img" '
|
||||
f'aria-label="{m.group("alt")}" ', 1)
|
||||
# Wrap in a scroll box so large graphs (e.g. file include graphs)
|
||||
# stay fully reachable instead of being clipped — scrolls when wider
|
||||
# than the content area, fits otherwise.
|
||||
return f'<div class="opencv-graph-scroll">{svg}</div>'
|
||||
|
||||
new = img_re.sub(_inline, text)
|
||||
if new != text:
|
||||
@@ -125,6 +130,63 @@ def _strip_breathe_class_clutter(api_dir: pathlib.Path) -> None:
|
||||
if new != text:
|
||||
h.write_text(new, encoding="utf-8")
|
||||
|
||||
def _fix_gapi_images(out_dir: pathlib.Path) -> None:
|
||||
"""Copy gapi doc images to _images/ and fix src paths in gapi.html."""
|
||||
gapi_html = out_dir / "extra_modules" / "gapi.html"
|
||||
if not gapi_html.is_file():
|
||||
return
|
||||
images_dir = out_dir / "_images"
|
||||
images_dir.mkdir(exist_ok=True)
|
||||
|
||||
text = gapi_html.read_text(encoding="utf-8")
|
||||
|
||||
def _fix_src(m):
|
||||
raw_path = m.group(1)
|
||||
if "contrib_modules" not in raw_path:
|
||||
return m.group(0)
|
||||
# raw_path is relative to extra_modules/ in the browser,
|
||||
# but the file lives at out_dir / raw_path (no extra_modules prefix)
|
||||
src_file = out_dir / raw_path
|
||||
if not src_file.is_file():
|
||||
return m.group(0)
|
||||
dest = images_dir / src_file.name
|
||||
if not dest.is_file():
|
||||
import shutil as _shutil
|
||||
_shutil.copy2(src_file, dest)
|
||||
return f'src="../_images/{src_file.name}"'
|
||||
|
||||
new_text = re.sub(r'src="([^"]+)"', _fix_src, text)
|
||||
if new_text != text:
|
||||
gapi_html.write_text(new_text, encoding="utf-8")
|
||||
|
||||
def _copy_js_tryit_files(out_dir: pathlib.Path) -> None:
|
||||
"""Copy js_*.html Try-it pages + assets so iframe src="../../js_*.html" resolves."""
|
||||
import shutil, os
|
||||
js_assets = DOC_ROOT / "js_tutorials" / "js_assets"
|
||||
dest = out_dir / "js_tutorials"
|
||||
if not js_assets.is_dir() or not dest.is_dir():
|
||||
return
|
||||
for src in js_assets.iterdir():
|
||||
if src.is_file():
|
||||
dst = dest / src.name
|
||||
if not dst.exists():
|
||||
shutil.copy2(src, dst)
|
||||
# opencv.js from CMake (OPENCV_JS_PATH); bundle it alongside the Try-it pages.
|
||||
opencv_js = os.environ.get("OPENCV_JS_PATH", "")
|
||||
if opencv_js and pathlib.Path(opencv_js).is_file():
|
||||
dst = dest / "opencv.js"
|
||||
if not dst.exists():
|
||||
shutil.copy2(opencv_js, dst)
|
||||
# Extra assets referenced by Try-it pages but not in js_assets/.
|
||||
_opencv_root = DOC_ROOT.parent
|
||||
for _name, _src in {
|
||||
"box.mp4": _opencv_root / "samples/cpp/tutorial_code/calib3d/real_time_pose_estimation/Data/box.mp4",
|
||||
"space_shuttle.jpg": DOC_ROOT / "tutorials/dnn/images/space_shuttle.jpg",
|
||||
"roi.jpg": DOC_ROOT / "py_tutorials/py_core/py_basic_ops/images/roi.jpg",
|
||||
}.items():
|
||||
if _src.is_file() and not (dest / _name).exists():
|
||||
shutil.copy2(_src, dest / _name)
|
||||
|
||||
|
||||
def _generate_search_map(out_dir: pathlib.Path) -> None:
|
||||
"""Write _static/search_map.js: stem→Sphinx-path for every built HTML page."""
|
||||
@@ -142,6 +204,302 @@ def _generate_search_map(out_dir: pathlib.Path) -> None:
|
||||
lines.append("};")
|
||||
(out_dir / "_static" / "search_map.js").write_text("\n".join(lines), encoding="utf-8")
|
||||
|
||||
_SYM = r"(?:group__|classcv|structcv|unioncv|namespacecv)\w*\.html"
|
||||
|
||||
def _localize_doxygen_links(out_dir: pathlib.Path) -> None:
|
||||
"""Point symbol-page links at the local Sphinx page when we built it:
|
||||
docs.opencv.org URLs, and bare relative names that 404 off the API dir."""
|
||||
import os
|
||||
skip = {"_static", "_sources", "_images", "_sphinx_design_static"}
|
||||
page_paths: dict[str, str] = {}
|
||||
for f in out_dir.rglob("*.html"):
|
||||
rel = f.relative_to(out_dir)
|
||||
if rel.parts and rel.parts[0] in skip:
|
||||
continue
|
||||
page_paths.setdefault(f.name, rel.as_posix())
|
||||
ext_re = re.compile(
|
||||
r'(?P<a><a class="reference external"\s+)?'
|
||||
r'href="' + re.escape(DOXYGEN_BASE_URL) + r'(?:[\w-]+/)*?'
|
||||
r'(?P<page>' + _SYM + r')(?:#(?P<frag>\w+))?"')
|
||||
bare_re = re.compile(r'href="(?P<page>' + _SYM + r')(?:#(?P<frag>[\w:.-]+))?"')
|
||||
# `#include` links -> local file-reference page (same Doxygen file stem).
|
||||
inc_re = re.compile(
|
||||
r'<a class="reference external opencv-include-link" '
|
||||
r'href="[^"]*doc/doxygen/html/[^"]*?(?P<file>[^/"]+\.html)">(?P<path>[^<]+)</a>')
|
||||
strip_re = re.compile(
|
||||
r'<a\b[^>]*\bhref="(?:' + re.escape(DOXYGEN_BASE_URL) + r'|[^"]*doc/doxygen/html/)'
|
||||
r'(?![^"]*javadoc)[^"]*"[^>]*>(?P<txt>[^<]*)</a>')
|
||||
|
||||
for html in out_dir.rglob("*.html"):
|
||||
rel = html.relative_to(out_dir)
|
||||
if rel.parts and rel.parts[0] in skip:
|
||||
continue
|
||||
text = html.read_text(encoding="utf-8")
|
||||
cur = rel.parent.as_posix()
|
||||
|
||||
def _href(local: str, anchor: str) -> str | None:
|
||||
target = page_paths.get(local)
|
||||
if not target:
|
||||
return None
|
||||
href = target if cur == "." else os.path.relpath(target, cur)
|
||||
return f'href="{href}{anchor}"'
|
||||
|
||||
def _ext(m: "re.Match") -> str:
|
||||
member = _DOXY_ANCHOR_TO_MEMBER.get(m.group("frag") or "")
|
||||
h = _href(_doxy_page_to_local(m.group("page")),
|
||||
f"#{member}" if member else "")
|
||||
if not h:
|
||||
return m.group(0)
|
||||
# Flip the now-local link from external to internal styling.
|
||||
return f'<a class="reference internal" {h}' if m.group("a") else h
|
||||
|
||||
def _bare(m: "re.Match") -> str:
|
||||
frag = m.group("frag")
|
||||
return _href(m.group("page"), f"#{frag}" if frag else "") or m.group(0)
|
||||
|
||||
def _inc(m: "re.Match") -> str:
|
||||
h = _href(m.group("file"), "")
|
||||
if not h:
|
||||
return m.group(0)
|
||||
return f'<a class="reference internal opencv-include-link" {h}>{m.group("path")}</a>'
|
||||
|
||||
new = inc_re.sub(_inc, bare_re.sub(_bare, ext_re.sub(_ext, text)))
|
||||
new = strip_re.sub(lambda m: m.group("txt"), new)
|
||||
if new != text:
|
||||
html.write_text(new, encoding="utf-8")
|
||||
|
||||
|
||||
def _drop_moved_stub_search_entries() -> None:
|
||||
"""Drop moved-tutorial stub pages from the Doxygen search index."""
|
||||
doxy_html = _API_XML_DIR.parent / "html"
|
||||
search_dir = doxy_html / "search"
|
||||
if not search_dir.is_dir():
|
||||
return
|
||||
stubs = set()
|
||||
for h in doxy_html.rglob("*table_of_content*.html"):
|
||||
try:
|
||||
if "has been moved to this page" in h.read_text(encoding="utf-8", errors="ignore"):
|
||||
stubs.add(h.name)
|
||||
except OSError:
|
||||
pass
|
||||
if not stubs:
|
||||
return
|
||||
for js in search_dir.glob("*.js"):
|
||||
lines = js.read_text(encoding="utf-8", errors="ignore").splitlines(keepends=True)
|
||||
# Drop only single-target stub entries; keep multi-target terms.
|
||||
kept = [ln for ln in lines if not (
|
||||
ln.count("['../") == 1 and any(f"/{s}'" in ln for s in stubs))]
|
||||
if len(kept) != len(lines):
|
||||
js.write_text("".join(kept), encoding="utf-8")
|
||||
|
||||
|
||||
# Pygments emits `<span class="n">NAME</span>` (and `class="nc"`/`"nf"`
|
||||
# for class/function tokens) inside its rendered `<pre>` for every C++
|
||||
# identifier in a code block. The example pages, snippet pages, tutorial
|
||||
# samples, and any `:::{code-block} cpp` fence all go through Pygments,
|
||||
# so by the time the HTML is written the code blocks have full syntax
|
||||
# colouring but ZERO clickable tokens — `Mat`, `InputArray`,
|
||||
# `getOptimalDFTSize`, etc. are inert text.
|
||||
#
|
||||
# This pass wraps each such span in an `<a class="reference internal"
|
||||
# href="…">` when the token resolves via `_LOCAL_CLASS_URL` /
|
||||
# `_LOCAL_TYPEDEF_URL`, mirroring what the API-stub renderer already
|
||||
# does for inline `<programlisting>`. Idempotent: skips spans already
|
||||
# inside an `<a>`.
|
||||
_PYG_IDENT_SPAN_RE = re.compile(
|
||||
r'(?P<prefix><span class="(?:n|nc|nf|nb|nv|na)">)'
|
||||
r'(?P<name>[A-Za-z_][A-Za-z0-9_]*)'
|
||||
r'(?P<suffix></span>)'
|
||||
)
|
||||
|
||||
# Pygments preprocessor-file-name span:
|
||||
# `<span class="cpf">"opencv2/core.hpp"</span>` (or <…>)
|
||||
# The quote characters are HTML-escaped (`"` / `<` / `>`).
|
||||
# Capture the inner path so we can wrap it in an `<a>` linking to the
|
||||
# local Doxygen file page (`_FILE_URL` map). The quote chars stay
|
||||
# outside the anchor — mirrors how the enum-detail `#include` line is
|
||||
# rendered (`#include <a href="…">opencv2/core.hpp</a>`).
|
||||
_PYG_CPF_SPAN_RE = re.compile(
|
||||
r'(?P<prefix><span class="cpf">)'
|
||||
r'(?P<openq>"|<)'
|
||||
r'(?P<path>[A-Za-z0-9_./+\-]+\.[A-Za-z0-9]+)'
|
||||
r'(?P<closeq>"|>)'
|
||||
r'(?P<suffix></span>)'
|
||||
)
|
||||
|
||||
|
||||
def _linkify_code_blocks(html_dir: pathlib.Path) -> None:
|
||||
"""Walk every `.html` under `html_dir` and turn known identifier
|
||||
tokens inside Pygments-rendered `<pre>` blocks into clickable
|
||||
anchors. The substitution is scoped to spans inside `<pre>` so we
|
||||
don't accidentally repaint inline `<code class="n">` chips in
|
||||
prose; the rule above already targets only Pygments span classes
|
||||
that Pygments uses inside its `<pre>` output."""
|
||||
if not (_LOCAL_CLASS_URL or _LOCAL_TYPEDEF_URL or _FILE_URL):
|
||||
return
|
||||
if not html_dir.is_dir():
|
||||
return
|
||||
import os
|
||||
|
||||
def _resolve(name: str) -> str | None:
|
||||
return _LOCAL_CLASS_URL.get(name) or _LOCAL_TYPEDEF_URL.get(name)
|
||||
|
||||
# `<pre>…</pre>` blocks only — keeps the substitution from touching
|
||||
# inline `<span class="n">` runs that may appear in other contexts.
|
||||
_PRE_BLOCK_RE = re.compile(r"<pre>(.*?)</pre>", re.DOTALL)
|
||||
|
||||
# Relative path from each rendered `.html` file's directory to the
|
||||
# Doxygen html tree (which sits alongside `docs_sphinx/html/` at
|
||||
# `doc/doxygen/html/`). Reused per file so paths render with the
|
||||
# right number of `../` segments regardless of subdir depth.
|
||||
_DOXY_ROOT = html_dir.parent.parent / "doc" / "doxygen" / "html"
|
||||
|
||||
def _doxy_rel(html_path: pathlib.Path, file_url: str) -> str:
|
||||
target = _DOXY_ROOT / file_url
|
||||
try:
|
||||
return os.path.relpath(target, start=html_path.parent)
|
||||
except ValueError:
|
||||
return f"../../../doc/doxygen/html/{file_url}"
|
||||
|
||||
def _wrap_span(m: re.Match) -> str:
|
||||
name = m.group("name")
|
||||
url = _resolve(name)
|
||||
if not url:
|
||||
return m.group(0)
|
||||
return (f'<a class="reference internal" href="{url}">'
|
||||
f'{m.group("prefix")}{name}{m.group("suffix")}</a>')
|
||||
|
||||
def _wrap_cpf(m: re.Match, current_html: pathlib.Path) -> str:
|
||||
path = m.group("path")
|
||||
# Only opencv headers — `<iostream>`, `<stdio.h>` are not in
|
||||
# `_FILE_URL` and stay plain.
|
||||
file_url = _FILE_URL.get(path)
|
||||
if not file_url:
|
||||
return m.group(0)
|
||||
href = _doxy_rel(current_html, file_url)
|
||||
# Keep the opening/closing quotes outside the `<a>` (so they
|
||||
# render as plain `"` / `<`/`>`), and put the link on just
|
||||
# the path text — same shape the enum-detail `#include` line
|
||||
# already uses elsewhere.
|
||||
return (f'{m.group("prefix")}{m.group("openq")}'
|
||||
f'<a class="reference external opencv-include-link" '
|
||||
f'href="{href}">{path}</a>'
|
||||
f'{m.group("closeq")}{m.group("suffix")}')
|
||||
|
||||
def _rewrite_pre(m: re.Match, current_html: pathlib.Path) -> str:
|
||||
inner = m.group(1)
|
||||
# Skip spans already wrapped: if `<a …>` immediately precedes
|
||||
# the `<span class="n">…</span>`, leave it. The Pygments
|
||||
# output doesn't generate `<a>` itself, so the only place
|
||||
# `<a>` appears in the inner text is from a prior pass — we
|
||||
# detect by scanning for `<a `/`</a>` pairs and only rewrite
|
||||
# text outside them.
|
||||
out: list[str] = []
|
||||
i, n = 0, len(inner)
|
||||
while i < n:
|
||||
if inner.startswith("<a ", i):
|
||||
j = inner.find("</a>", i)
|
||||
if j < 0:
|
||||
out.append(inner[i:])
|
||||
break
|
||||
out.append(inner[i:j + 4])
|
||||
i = j + 4
|
||||
else:
|
||||
k = inner.find("<a ", i)
|
||||
if k < 0:
|
||||
seg = inner[i:]
|
||||
seg = _PYG_IDENT_SPAN_RE.sub(_wrap_span, seg)
|
||||
seg = _PYG_CPF_SPAN_RE.sub(
|
||||
lambda mm: _wrap_cpf(mm, current_html), seg)
|
||||
out.append(seg)
|
||||
break
|
||||
seg = inner[i:k]
|
||||
seg = _PYG_IDENT_SPAN_RE.sub(_wrap_span, seg)
|
||||
seg = _PYG_CPF_SPAN_RE.sub(
|
||||
lambda mm: _wrap_cpf(mm, current_html), seg)
|
||||
out.append(seg)
|
||||
i = k
|
||||
return "<pre>" + "".join(out) + "</pre>"
|
||||
|
||||
for html in html_dir.rglob("*.html"):
|
||||
try:
|
||||
text = html.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
continue
|
||||
if "<pre>" not in text:
|
||||
continue
|
||||
new_text = _PRE_BLOCK_RE.sub(
|
||||
lambda m: _rewrite_pre(m, html), text)
|
||||
if new_text != text:
|
||||
try:
|
||||
html.write_text(new_text, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
_INLINE_CODE_RE = re.compile(
|
||||
r'<code class="docutils literal notranslate">(?P<body>[^<]*?)</code>'
|
||||
)
|
||||
_A_BLOCK_RE = re.compile(r'<a\b[^>]*>.*?</a>', re.DOTALL)
|
||||
_INLINE_TOK_RE = re.compile(r'(?:cv::)?_?[A-Za-z][A-Za-z0-9_]*')
|
||||
|
||||
|
||||
def _linkify_inline_code(html_dir: pathlib.Path) -> None:
|
||||
if not (_LOCAL_CLASS_URL or _LOCAL_TYPEDEF_URL):
|
||||
return
|
||||
if not html_dir.is_dir():
|
||||
return
|
||||
|
||||
def _resolve(name: str) -> str | None:
|
||||
bare = name[4:] if name.startswith("cv::") else name
|
||||
return _LOCAL_CLASS_URL.get(bare) or _LOCAL_TYPEDEF_URL.get(bare)
|
||||
|
||||
def _anchor_text(tok: str) -> str:
|
||||
return tok.replace("::", "::")
|
||||
|
||||
def _linkify_body(body: str) -> str:
|
||||
def _sub(m: re.Match) -> str:
|
||||
tok = m.group(0)
|
||||
url = _resolve(tok)
|
||||
if not url:
|
||||
return tok
|
||||
return (f'<a class="reference internal" '
|
||||
f'href="{url}">{_anchor_text(tok)}</a>')
|
||||
return _INLINE_TOK_RE.sub(_sub, body)
|
||||
|
||||
def _rewrite_code(m: re.Match) -> str:
|
||||
body = m.group("body")
|
||||
new_body = _linkify_body(body)
|
||||
if new_body == body:
|
||||
return m.group(0)
|
||||
return (f'<code class="docutils literal notranslate">'
|
||||
f'{new_body}</code>')
|
||||
|
||||
for html in html_dir.rglob("*.html"):
|
||||
try:
|
||||
text = html.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
continue
|
||||
if 'class="docutils literal notranslate"' not in text:
|
||||
continue
|
||||
masked: list[str] = []
|
||||
def _mask(m: re.Match) -> str:
|
||||
masked.append(m.group(0))
|
||||
return f"\x00A{len(masked) - 1}\x00"
|
||||
masked_text = _A_BLOCK_RE.sub(_mask, text)
|
||||
new_masked = _INLINE_CODE_RE.sub(_rewrite_code, masked_text)
|
||||
if new_masked == masked_text:
|
||||
continue
|
||||
new_text = re.sub(
|
||||
r"\x00A(\d+)\x00",
|
||||
lambda mm: masked[int(mm.group(1))],
|
||||
new_masked,
|
||||
)
|
||||
try:
|
||||
html.write_text(new_text, encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _inline_coll_graphs_on_finish(app, exception):
|
||||
"""build-finished entry point."""
|
||||
@@ -151,4 +509,10 @@ def _inline_coll_graphs_on_finish(app, exception):
|
||||
for _api in ("main_modules", "extra_modules"):
|
||||
_inline_collaboration_svgs(out / _api, out / "_images")
|
||||
_strip_breathe_class_clutter(out / _api)
|
||||
_linkify_code_blocks(out)
|
||||
_linkify_inline_code(out)
|
||||
_localize_doxygen_links(out)
|
||||
_drop_moved_stub_search_entries()
|
||||
_copy_js_tryit_files(out)
|
||||
_fix_gapi_images(out)
|
||||
_generate_search_map(out)
|
||||
|
||||
@@ -56,21 +56,41 @@ CONTRIB_MODULES = ([m.strip() for m in _contrib_env.split(",") if m.strip()]
|
||||
|
||||
# SCOPE — env OPENCV_API_MODULES (comma/semicolon); empty disables API pages
|
||||
def _discover_api_modules() -> list[str]:
|
||||
"""Main + contrib modules whose umbrella header declares `@defgroup`."""
|
||||
found = []
|
||||
# Scan both the main tree and opencv_contrib/modules.
|
||||
"""Main + contrib modules that declare an `@defgroup`, by module dir name.
|
||||
|
||||
The `@defgroup` is not always in the umbrella `opencv2/<module>.hpp`: some
|
||||
modules declare it in a sub-header (datasets/dataset.hpp,
|
||||
quality/qualitybase.hpp, reg/map.hpp) or a differently-named top header
|
||||
(dnn_objdetect's core_detect.hpp, cannops' cann.hpp). So scan every header
|
||||
under each module's include/ tree, and use the module DIRECTORY name (which
|
||||
equals the top `@defgroup` / `group__<name>` by convention) as the id."""
|
||||
found: set[str] = set()
|
||||
_roots = [OPENCV_ROOT / "modules"]
|
||||
if CONTRIB_ROOT.is_dir():
|
||||
_roots.append(CONTRIB_ROOT)
|
||||
|
||||
def _has_defgroup(hdr) -> bool:
|
||||
try:
|
||||
return "@defgroup" in hdr.read_text(encoding="utf-8", errors="ignore")
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
for _root in _roots:
|
||||
for _hdr in _root.glob("*/include/opencv2/*.hpp"):
|
||||
if _hdr.stem != _hdr.parents[2].name: # only the umbrella header
|
||||
if not _root.is_dir():
|
||||
continue
|
||||
for _mod in _root.iterdir():
|
||||
_inc = _mod / "include" / "opencv2"
|
||||
if not _inc.is_dir():
|
||||
continue
|
||||
try:
|
||||
if "@defgroup" in _hdr.read_text(encoding="utf-8", errors="ignore"):
|
||||
found.append(_hdr.stem)
|
||||
except OSError:
|
||||
pass
|
||||
# Umbrella / top-level headers first (cheap, the common case)…
|
||||
if any(_has_defgroup(h) for h in sorted(_inc.glob("*.hpp"))):
|
||||
found.add(_mod.name)
|
||||
continue
|
||||
# …else fall back to sub-directory headers.
|
||||
for _h in _inc.rglob("*.hpp"):
|
||||
if _has_defgroup(_h):
|
||||
found.add(_mod.name)
|
||||
break
|
||||
return sorted(found)
|
||||
|
||||
|
||||
@@ -109,10 +129,10 @@ def _module_group_stem(m: str) -> str:
|
||||
# module folder (folder `3d` -> `@defgroup _3d`). Return that real stem.
|
||||
if (_API_XML_DIR / f"group__{m.replace('_', '__')}.xml").is_file():
|
||||
return m
|
||||
for _root in (OPENCV_ROOT / "modules", CONTRIB_ROOT):
|
||||
for _root in (OPENCV_ROOT / "modules", CONTRIB_ROOT / "modules", CONTRIB_ROOT):
|
||||
_hdr = _root / m / "include" / "opencv2" / f"{m}.hpp"
|
||||
if _hdr.is_file():
|
||||
_mm = re.search(r"@defgroup\s+(\S+)",
|
||||
_mm = re.search(r"[@\\]defgroup\s+(\S+)",
|
||||
_hdr.read_text(encoding="utf-8", errors="ignore"))
|
||||
return _mm.group(1) if _mm else m
|
||||
return m
|
||||
@@ -171,6 +191,9 @@ DOXYGEN_BASE_URL = (
|
||||
.rstrip("/") + "/")
|
||||
# First existing wins; env OPENCV_DOXYGEN_TAGFILE overrides
|
||||
_TAG_CANDIDATES = (
|
||||
# Track cmake's actual build dir via the XML dir.
|
||||
_API_XML_DIR.parent / "html" / "opencv.tag",
|
||||
_API_XML_DIR.parent / "opencv.tag",
|
||||
HERE.parent / "build" / "doc" / "doxygen" / "html" / "opencv.tag",
|
||||
HERE.parent.parent / "build" / "doc" / "doxygen" / "html" / "opencv.tag",
|
||||
# extra build-dir layouts (vanilla, contrib, nested CI)
|
||||
@@ -250,11 +273,15 @@ if _TAG_FILE.is_file():
|
||||
)
|
||||
elif _kind in ("class", "struct"):
|
||||
_full = _c.findtext("name") or ""
|
||||
if _full.startswith("cv::"):
|
||||
# Skip G-API/detail internals; a bare cv::Point isn't gapi::own::Point.
|
||||
if _full.startswith("cv::") and not any(
|
||||
_x in _full for _x in ("::own::", "::wip::", "::detail::")):
|
||||
_short = _full.split("::")[-1]
|
||||
_af = _c.findtext("filename")
|
||||
if _short and _af:
|
||||
_CV_SYMBOL_URL.setdefault(_short, DOXYGEN_BASE_URL + _af)
|
||||
# Prefer canonical cv::<short> over nested homonyms.
|
||||
if _short and _af and (
|
||||
_short not in _CV_SYMBOL_URL or _full == f"cv::{_short}"):
|
||||
_CV_SYMBOL_URL[_short] = DOXYGEN_BASE_URL + _af
|
||||
elif _kind == "group":
|
||||
# module pages are kind="group"
|
||||
_n = _c.findtext("name")
|
||||
@@ -290,6 +317,32 @@ if _TAG_FILE.is_file():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Canonical public header for each optional module that may be absent from a
|
||||
# build's Doxygen group XML. Ensure a `_FILE_URL` entry exists so the umbrella
|
||||
# `#include` on the module's placeholder page resolves (and a file-reference
|
||||
# page is generated for it). `setdefault` → a real tag entry always wins.
|
||||
_FALLBACK_MODULE_HEADERS: dict[str, str] = {
|
||||
"datasets": "opencv2/datasets/dataset.hpp",
|
||||
"dnn_objdetect": "opencv2/core_detect.hpp",
|
||||
"quality": "opencv2/quality.hpp",
|
||||
"reg": "opencv2/reg/map.hpp",
|
||||
"cannops": "opencv2/cann.hpp",
|
||||
}
|
||||
# Every header path referenced by a fallback page (umbrella + per-function
|
||||
# includes), ensured present in `_FILE_URL` so each `#include` resolves to a
|
||||
# generated file-reference page. `setdefault` → real tag entries always win.
|
||||
_FALLBACK_FILE_HEADERS: list[str] = [
|
||||
"opencv2/datasets/dataset.hpp", "opencv2/datasets/util.hpp",
|
||||
"opencv2/core_detect.hpp",
|
||||
"opencv2/quality.hpp", "opencv2/quality/qualitybase.hpp",
|
||||
"opencv2/reg/map.hpp", "opencv2/reg/mapper.hpp",
|
||||
"opencv2/cann.hpp", "opencv2/cann_interface.hpp",
|
||||
]
|
||||
for _inc in list(_FALLBACK_MODULE_HEADERS.values()) + _FALLBACK_FILE_HEADERS:
|
||||
# Doxygen file-page stem: `.`->`_8`, `_`->`__` on the bare filename.
|
||||
_fn = _inc.rsplit("/", 1)[-1].replace("_", "__").replace(".", "_8")
|
||||
_FILE_URL.setdefault(_inc, f"{_fn}.html")
|
||||
|
||||
def _doxygen_url(page: str) -> str:
|
||||
return DOXYGEN_BASE_URL + _TAG_FILENAMES.get(page, page)
|
||||
|
||||
@@ -346,11 +399,15 @@ if _LIVE_TAG_FILE.is_file():
|
||||
|
||||
# -- Local-link variants of the maps above (step 8g) ------------------------
|
||||
_LOCAL_SRC_TAG = _TAG_FILE if _TAG_FILE.is_file() else _LIVE_TAG_FILE
|
||||
_LOCAL_CLASS_URL: dict[str, str] = {
|
||||
# _Tp template-parameter placeholder stub
|
||||
"_Tp": "class_Tp.html",
|
||||
}
|
||||
_LOCAL_TYPEDEF_URL: dict[str, str] = {} # 'uchar' -> 'core_hal_interface.html#_CPPv45uchar'
|
||||
_LOCAL_CLASS_URL: dict[str, str] = {}
|
||||
# NOTE: `_Tp` (the template-parameter placeholder) used to map to
|
||||
# `class_Tp.html`, but no stub page is actually written for it — every
|
||||
# `<a href="class_Tp.html">` was a 404. Removing the entry leaves `_Tp`
|
||||
# tokens as plain (non-clickable) text in signatures, matching the
|
||||
# "no 404 clickables" requirement.
|
||||
_LOCAL_TYPEDEF_URL: dict[str, str] = {} # 'uchar' -> 'core_hal_interface.html#uchar'
|
||||
# Group-enum refid -> page#anchor; lets enum-VALUE <ref>s link to their enum doc.
|
||||
_ENUM_REF_URL: dict[str, str] = {}
|
||||
# Doxygen template-param placeholder pages -> (display name, Sphinx page).
|
||||
# Sphinx mirrors these as stubs (stubs._write_placeholder_stubs) so diagram
|
||||
# cross-links resolve instead of 404ing.
|
||||
@@ -361,6 +418,7 @@ _PLACEHOLDER_STUBS: dict[str, tuple[str, str]] = {
|
||||
# Doxygen compound filename -> Sphinx page filename (class/struct compounds).
|
||||
_LOCAL_PAGE_BY_DOXY_FILE: dict[str, str] = {
|
||||
_doxy: _page for _doxy, (_disp, _page) in _PLACEHOLDER_STUBS.items()}
|
||||
_cls_cands: dict[str, set] = {} # short name -> set of full names (collision check)
|
||||
if _LOCAL_SRC_TAG.is_file():
|
||||
try:
|
||||
import xml.etree.ElementTree as _ET
|
||||
@@ -372,7 +430,12 @@ if _LOCAL_SRC_TAG.is_file():
|
||||
_short = _n.split("::")[-1]
|
||||
_fn = _f if _f.endswith(".html") else _f + ".html"
|
||||
_doxy_base = pathlib.PurePosixPath(_fn).name
|
||||
_LOCAL_CLASS_URL.setdefault(_short, _doxy_base)
|
||||
# Skip G-API/detail internals as bare `cv::` targets.
|
||||
if not any(_x in _n for _x in ("::own::", "::wip::", "::detail::")):
|
||||
_cls_cands.setdefault(_short, set()).add(_n)
|
||||
# Prefer canonical cv::<short> over nested homonyms.
|
||||
if _short not in _LOCAL_CLASS_URL or _n == f"cv::{_short}":
|
||||
_LOCAL_CLASS_URL[_short] = _doxy_base
|
||||
# Sphinx mirrors Doxygen's filename except the few remapped.
|
||||
_LOCAL_PAGE_BY_DOXY_FILE.setdefault(
|
||||
_doxy_base, _LOCAL_CLASS_URL.get(_short, _doxy_base))
|
||||
@@ -389,9 +452,19 @@ if _LOCAL_SRC_TAG.is_file():
|
||||
continue
|
||||
_mn = (_mem.findtext("name") or "").strip()
|
||||
_maf = (_mem.findtext("anchorfile") or "").strip()
|
||||
_man = (_mem.findtext("anchor") or "").strip()
|
||||
if not (_mn and _maf):
|
||||
continue
|
||||
if _mn in _LOCAL_TYPEDEF_URL:
|
||||
# The tagfile emits each typedef twice: once with the
|
||||
# bare name (`InputArray`) and once fully-qualified
|
||||
# (`cv::InputArray`). Normalize to the bare short name
|
||||
# so we don't end up with a second map entry whose
|
||||
# anchor literally contains `cv::` (e.g.
|
||||
# `core_basic.html#cv::inputarray`) — that anchor never
|
||||
# exists on the rendered page and every `cv::InputArray`
|
||||
# codespan ends up 404-ing.
|
||||
_short_mn = _mn.split("::")[-1]
|
||||
if _short_mn in _LOCAL_TYPEDEF_URL:
|
||||
continue # first-occurrence wins
|
||||
_bn = pathlib.PurePosixPath(_maf).name
|
||||
if _bn.startswith("group__"):
|
||||
@@ -404,14 +477,50 @@ if _LOCAL_SRC_TAG.is_file():
|
||||
_local_page = "core_basic.html"
|
||||
else:
|
||||
_local_page = _bn
|
||||
# HAL typedefs are global C; else cv::-scoped (cpp-domain v4 anchor)
|
||||
if "hal_interface" in _local_page:
|
||||
_anchor = f"_CPPv4{len(_mn)}{_mn}"
|
||||
# Anchor: mirror exactly the MyST target the detail block
|
||||
# emits, since docutils' make_id lowercases the label and
|
||||
# collapses every run of non-alnum chars to a single "-".
|
||||
# The label differs by kind/where it renders:
|
||||
#
|
||||
# * typedefs, namespace variables, and CLASS-member enums
|
||||
# render via `_render_member_detail` / the class-enum
|
||||
# path, both keyed by the Doxygen member refid
|
||||
# `({m['id']})=` (= "<anchorfile-stem>_1<anchor>").
|
||||
# (The old `_CPPv4…` cpp-domain id was never emitted for
|
||||
# these hand-rolled markdown sections, so every such
|
||||
# cross-reference 404'd.)
|
||||
#
|
||||
# * GROUP/namespace enums render (stubs, kind_key=="enum")
|
||||
# with `({_sphinx_cpp_v4_id(qualified)})=` instead — the
|
||||
# member refid is NOT used — so their anchor is the
|
||||
# mangled, lowercased cpp-v4 id, not the refid.
|
||||
_is_class_pg = _bn.startswith(("class", "struct", "union"))
|
||||
if _mk == "enumeration" and not _is_class_pg:
|
||||
_ns = (_c.findtext("name") or "").strip()
|
||||
_qual = _mn if "::" in _mn else (
|
||||
f"{_ns}::{_mn}" if _ns and _c.get("kind") == "namespace"
|
||||
else _mn)
|
||||
# _sphinx_cpp_v4_id: "_CPPv4N" + join(f"{len(p)}{p}") + "E"
|
||||
_label = ("_CPPv4N"
|
||||
+ "".join(f"{len(_p)}{_p}" for _p in _qual.split("::"))
|
||||
+ "E")
|
||||
else:
|
||||
_anchor = f"_CPPv4N2cv{len(_mn)}{_mn}E"
|
||||
_stem = _bn[:-5] if _bn.endswith(".html") else _bn
|
||||
_label = f"{_stem}_1{_man}" if _man else _stem
|
||||
_anchor = re.sub(r"[^a-z0-9]+", "-", _label.lower()).strip("-")
|
||||
_LOCAL_TYPEDEF_URL[_mn] = f"{_local_page}#{_anchor}"
|
||||
# Index group enums by refid so enum-VALUE <ref>s can resolve here.
|
||||
if _mk == "enumeration" and not _is_class_pg and _man:
|
||||
_est = _bn[:-5] if _bn.endswith(".html") else _bn
|
||||
_ENUM_REF_URL.setdefault(f"{_est}_1{_man}",
|
||||
f"{_local_page}#{_anchor}")
|
||||
except Exception:
|
||||
pass
|
||||
# Drop ambiguous short names (>1 class, no canonical cv::<short>) so a bare
|
||||
# `cv::Point` stays plain text instead of linking to a wrong homonym.
|
||||
for _s, _cands in _cls_cands.items():
|
||||
if len(_cands) > 1 and f"cv::{_s}" not in _cands:
|
||||
_LOCAL_CLASS_URL.pop(_s, None)
|
||||
|
||||
|
||||
def _doxy_page_to_local(basename: str) -> str:
|
||||
@@ -488,27 +597,41 @@ def _func_slug(name: str) -> str:
|
||||
# ---- Citation numbering --------------------------------------------------
|
||||
# @cite KEY -> [N], N = bibtex-plain sort position
|
||||
def _bib_parse(text: str) -> list[dict]:
|
||||
"""Walk a BibTeX file into a list of {_type, _key, field: value, ...}."""
|
||||
"""Walk a BibTeX file into a list of {_type, _key, field: value, ...}.
|
||||
|
||||
Tolerant of a missing closing `}` (as bibtex/Doxygen are): a new
|
||||
`@type{key,` at the start of a line implicitly closes the previous entry,
|
||||
so one malformed record can't swallow the rest of the file. (e.g.
|
||||
ximgproc.bib's `akinlar201782` has no closing brace upstream.)"""
|
||||
out: list[dict] = []
|
||||
n, i = len(text), 0
|
||||
while i < n:
|
||||
m = re.search(r"@(\w+)\s*\{\s*([^\s,]+)\s*,", text[i:])
|
||||
n = len(text)
|
||||
header = re.compile(r"@(\w+)\s*\{\s*([^\s,]+)\s*,")
|
||||
# Every entry header that begins a line is a hard boundary.
|
||||
starts = [m.start() for m in
|
||||
re.finditer(r"(?m)^[^\S\n]*@\w+\s*\{\s*[^\s,]+\s*,", text)]
|
||||
for idx, s in enumerate(starts):
|
||||
m = header.match(text, s)
|
||||
if not m:
|
||||
break
|
||||
continue
|
||||
kind, key = m.group(1), m.group(2)
|
||||
i += m.end()
|
||||
depth, body_start = 1, i
|
||||
while i < n and depth > 0:
|
||||
body_start = m.end()
|
||||
limit = starts[idx + 1] if idx + 1 < len(starts) else n
|
||||
# Matching close within this entry's span; if unbalanced (missing `}`),
|
||||
# fall back to the next entry's boundary, trimming a trailing brace.
|
||||
depth, i, end = 1, body_start, None
|
||||
while i < limit:
|
||||
c = text[i]
|
||||
if c == "{":
|
||||
depth += 1
|
||||
elif c == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
end = i
|
||||
break
|
||||
i += 1
|
||||
if depth != 0:
|
||||
break # malformed entry
|
||||
out.append({"_type": kind.lower(), "_key": key,
|
||||
**_bib_fields(text[body_start:i - 1])})
|
||||
body = (text[body_start:end] if end is not None
|
||||
else text[body_start:limit].rstrip().rstrip("}"))
|
||||
out.append({"_type": kind.lower(), "_key": key, **_bib_fields(body)})
|
||||
return out
|
||||
|
||||
def _bib_fields(body: str) -> dict[str, str]:
|
||||
@@ -764,6 +887,7 @@ def _resolve_redirect(anchor: str) -> str:
|
||||
_ANCHOR_TO_DOC: dict[str, str] = {} # anchor -> docname (internal)
|
||||
_ANCHOR_TO_EXTERNAL: dict[str, tuple[str, str]] = {} # anchor -> (title, url)
|
||||
_ANCHOR_TO_TITLE: dict[str, str] = {} # anchor -> first-heading title
|
||||
_LOCAL_MEMBER_IDS: set[str] = set() # member detail targets we emit
|
||||
# anchors reachable via @subpage/@ref; rest are orphans
|
||||
_REFERENCED_ANCHORS: set[str] = set()
|
||||
|
||||
@@ -871,15 +995,15 @@ __all__ = [
|
||||
"HAVE_SPHINX_DESIGN", "HAVE_BREATHE",
|
||||
"DOXYGEN_BASE_URL", "_doxygen_url",
|
||||
"_TAG_FILE", "_TAG_FILENAMES", "_TAG_TITLES", "_DOC_PAGE_TITLES",
|
||||
"_CV_SYMBOL_URL", "_FILE_URL",
|
||||
"_CV_SYMBOL_URL", "_FILE_URL", "_FALLBACK_MODULE_HEADERS",
|
||||
"_CALL_GRAPH_ANCHORS", "_DOXY_ANCHOR_TO_MEMBER", "_norm_args",
|
||||
"_LIVE_GROUP_URL", "_LIVE_CLASS_URL", "_LIVE_TYPEDEF_URL",
|
||||
"_LOCAL_CLASS_URL", "_LOCAL_TYPEDEF_URL", "_CLASS_TEMPLATE_DISPLAY",
|
||||
"_LOCAL_CLASS_URL", "_LOCAL_TYPEDEF_URL", "_ENUM_REF_URL", "_CLASS_TEMPLATE_DISPLAY",
|
||||
"_LOCAL_PAGE_BY_DOXY_FILE", "_PLACEHOLDER_STUBS", "_doxy_page_to_local",
|
||||
"_func_slug",
|
||||
"_CITE_NUMBER", "_BIB_ENTRIES_SORTED", "_bib_render_all",
|
||||
"_REDIRECT_MAP", "_resolve_redirect",
|
||||
"_ANCHOR_TO_DOC", "_ANCHOR_TO_EXTERNAL", "_ANCHOR_TO_TITLE",
|
||||
"_ANCHOR_TO_DOC", "_ANCHOR_TO_EXTERNAL", "_ANCHOR_TO_TITLE", "_LOCAL_MEMBER_IDS",
|
||||
"_REFERENCED_ANCHORS", "_HEAD_RE",
|
||||
"_scan_internal", "_scan_external",
|
||||
"_IMAGE_INDEX", "_SNIPPET_INDEX", "_SNIPPET_BASES",
|
||||
|
||||
+1393
-92
File diff suppressed because it is too large
Load Diff
@@ -164,6 +164,12 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
lambda m: f"# {m.group('title').strip()}",
|
||||
text, flags=re.MULTILINE)
|
||||
|
||||
# 1c2. Setext H2s starting with digit+dot ("1. Moments\n---") -> ATX ##.
|
||||
text = re.sub(
|
||||
r"^(?P<title>\d+\.[^\n]+?)[ \t]*\n-[-]{2,}[ \t]*$",
|
||||
lambda m: f"## {m.group('title').strip()}",
|
||||
text, flags=re.MULTILINE)
|
||||
|
||||
# 1d. Demote every H1 after the first to H2.
|
||||
def _demote_extra_h1s(src: str) -> str:
|
||||
fence_open_re = re.compile(r'^[ \t]*(?:`{3,}|~{3,})')
|
||||
@@ -197,9 +203,46 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
(len(l) - len(l.lstrip()) for l in lines if l.strip()), default=0)
|
||||
body = "\n".join(l[min_ind:] for l in lines).strip()
|
||||
return f"\n:::{{{kind}}}\n{body}\n:::\n"
|
||||
_ac_stash: dict[str, str] = {}
|
||||
def _ac_hide(m: re.Match) -> str:
|
||||
k = f"\x00AC{len(_ac_stash)}\x00"; _ac_stash[k] = m.group(0); return k
|
||||
_t = re.sub(r"@code(?:\{[^}]*\})?.*?@endcode", _ac_hide, text, flags=re.DOTALL)
|
||||
_t = re.sub(
|
||||
r"^[ \t]*@(?P<dir>note|see|warning|sa)[ \t]*\n?(?P<body>.+?)"
|
||||
r"(?=\n[ \t]*\n|\n[ \t]*@[A-Za-z]|\Z)",
|
||||
_admon_repl, _t, flags=re.DOTALL | re.MULTILINE)
|
||||
for _k, _v in _ac_stash.items():
|
||||
_t = _t.replace(_k, _v)
|
||||
text = _t
|
||||
|
||||
# MyST definition list with "Parameters" heading.
|
||||
def _inline_block_math(s: str) -> str:
|
||||
if re.match(r"^\\f\[.+\\f\]$", s.strip()):
|
||||
return s
|
||||
return re.sub(r"\\f\[(.+?)\\f\]", lambda mm: f"${mm.group(1).strip()}$", s)
|
||||
def _param_block_repl(m: re.Match) -> str:
|
||||
result, has_param, cur_name, cur_desc = [], False, None, []
|
||||
for line in m.group(0).split("\n"):
|
||||
pm = re.match(r"@param\s+(\S+)\s+(.*)", line.strip())
|
||||
rm = re.match(r"@return\s+(.*)", line.strip())
|
||||
if pm:
|
||||
if cur_name:
|
||||
result.append(f"`{cur_name}`\n: {_inline_block_math(' '.join(cur_desc))}")
|
||||
cur_name, cur_desc, has_param = pm.group(1), [pm.group(2).strip()], True
|
||||
elif rm:
|
||||
if cur_name:
|
||||
result.append(f"`{cur_name}`\n: {_inline_block_math(' '.join(cur_desc))}")
|
||||
cur_name = None
|
||||
result.append(f"*(return value)*\n: {_inline_block_math(rm.group(1).strip())}")
|
||||
elif cur_name and line.strip():
|
||||
cur_desc.append(line.strip())
|
||||
if cur_name:
|
||||
result.append(f"`{cur_name}`\n: {_inline_block_math(' '.join(cur_desc))}")
|
||||
header = "\n**Parameters**\n\n" if has_param else "\n"
|
||||
return header + "\n\n".join(result) + "\n"
|
||||
text = re.sub(
|
||||
r"^[ \t]*@(?P<dir>note|see|warning|sa)[ \t]*\n?(?P<body>.+?)(?=\n[ \t]*\n|\n[ \t]*@[A-Za-z]|\Z)",
|
||||
_admon_repl, text, flags=re.DOTALL | re.MULTILINE)
|
||||
r"((?:^@(?:param\s+\S+|return)\s+[^\n]+\n(?:[ \t]+[^\n]+\n)*)+)",
|
||||
_param_block_repl, text, flags=re.MULTILINE)
|
||||
|
||||
# 2. Doxygen LaTeX math markers; preserve indent so blocks inside list items stay in the list.
|
||||
def _split_adj_math(m: re.Match) -> str:
|
||||
@@ -209,7 +252,11 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
_split_adj_math, text, flags=re.MULTILINE)
|
||||
def _fblock(m: re.Match) -> str:
|
||||
ind = m.group("indent")
|
||||
return f"\n{ind}$$\n{m.group('body').strip()}\n{ind}$$\n"
|
||||
body = m.group("body").strip()
|
||||
if "\\\\" in body:
|
||||
body = re.sub(r"\n\s*\n", "\n", body)
|
||||
return f"\n{ind}```{{math}}\n{ind}{body}\n{ind}```\n"
|
||||
return f"\n{ind}$$\n{body}\n{ind}$$\n"
|
||||
text = re.sub(r"^(?P<indent>[ \t]*)\\f\[(?P<body>.+?)\\f\]",
|
||||
_fblock, text, flags=re.DOTALL | re.MULTILINE)
|
||||
text = re.sub(r"\\f\[(.+?)\\f\]",
|
||||
@@ -258,10 +305,17 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
text, flags=re.MULTILINE)
|
||||
|
||||
# 3d. \htmlonly ... \endhtmlonly -> `{raw} html`.
|
||||
_depth = len(docname.split("/")) - 1 if docname else 1
|
||||
_to_root = "../" * _depth
|
||||
def _htmlonly_repl(m: re.Match) -> str:
|
||||
body = re.sub(r'src="(?:\.\./)+(?P<f>js_[^"]+)"',
|
||||
lambda mm: f'src="{_to_root}js_tutorials/{mm.group("f")}"',
|
||||
m.group("body"))
|
||||
body = re.sub(r'\s*onload="[^"]*"', ' height="700px"', body)
|
||||
return f"\n```{{raw}} html\n{body.strip()}\n```\n"
|
||||
text = re.sub(
|
||||
r"\\htmlonly\s*\n(?P<body>.*?)\n\s*\\endhtmlonly",
|
||||
lambda m: f"\n```{{raw}} html\n{m.group('body').strip()}\n```\n",
|
||||
text, flags=re.DOTALL)
|
||||
_htmlonly_repl, text, flags=re.DOTALL)
|
||||
|
||||
# 3e. Plain fences with Doxygen lang spec ("```.sh") -> strip dot, alias-map.
|
||||
text = re.sub(
|
||||
@@ -357,7 +411,61 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
r"@link\s+(?P<target>[\w-]+)(?P<disp>.*?)@endlink",
|
||||
_link_repl, text, flags=re.DOTALL)
|
||||
|
||||
if docname and (docname.startswith("main_modules/") or docname.startswith("extra_modules/")):
|
||||
# API stub rewrites — extended from `api/core_basic` to every `api/`
|
||||
# page so the Functions/Typedefs detail blocks on `core_array`,
|
||||
# `core_utils`, `core_cluster`, … get the same per-token token
|
||||
# linkifier (step 8g) that turns `InputArray`, `OutputArray`,
|
||||
# `Mat`, `_Tp`, etc. inside the signature codespans into individual
|
||||
# `<a>` anchors. The pre-existing regex-driven steps (8a, 8b, 8e,
|
||||
# 8i, 8j and the Vec-rows / cv::Ptr rewrites) are no-ops on pages
|
||||
# whose markdown doesn't match their patterns, so widening the gate
|
||||
# is safe.
|
||||
if docname and (docname.startswith("api/")
|
||||
or docname.startswith("main_modules/")
|
||||
or docname.startswith("extra_modules/")):
|
||||
# Idempotency guard: the stub generator now emits the
|
||||
# "Shorter aliases for the most popular specializations of
|
||||
# Vec<T,n>" section itself (via the `@name` named-group path
|
||||
# in `_summary_block`), so the source MD already contains
|
||||
# both the heading and the Vec rows. The legacy rewrite
|
||||
# below also extracts the rows and injects a fresh section
|
||||
# before "## Typedef Documentation" — without this guard the
|
||||
# heading renders TWICE, the first instance an empty table.
|
||||
# Skip the rewrite when the source MD already has the
|
||||
# heading.
|
||||
if "## Shorter aliases for the most popular specializations of Vec<T,n>" not in text:
|
||||
_vec_rows_re = re.compile(
|
||||
r"(?:^\| `Vec<[^`]*` \| [^\n]*\n)+", re.MULTILINE)
|
||||
_vm = _vec_rows_re.search(text)
|
||||
if _vm:
|
||||
_vec_rows = _vm.group(0)
|
||||
text = text[:_vm.start()] + text[_vm.end():]
|
||||
_shorter = (
|
||||
"## Shorter aliases for the most popular specializations of "
|
||||
"Vec<T,n>\n\n"
|
||||
# Carry the `.api-typedef-table` class so the section
|
||||
# inherits the same table styling (and the light-mode
|
||||
# blue Type-cell anchor rule) as the main Typedefs table
|
||||
# above.
|
||||
"{.api-typedef-table}\n"
|
||||
"| Type | Name | Description |\n"
|
||||
"|---|---|---|\n"
|
||||
+ _vec_rows + "\n")
|
||||
text = text.replace(
|
||||
"## Typedef Documentation",
|
||||
_shorter + "## Typedef Documentation",
|
||||
1)
|
||||
|
||||
# 8c. `{doxygentypedef} cv::Ptr` -> hand-rolled cpp:type (breathe skips C++11 aliases).
|
||||
text = re.sub(
|
||||
r"```\{doxygentypedef\} cv::Ptr\s*\n:project: opencv\s*\n```",
|
||||
"```{eval-rst}\n"
|
||||
".. cpp:namespace:: cv\n"
|
||||
".. cpp:type:: template<typename _Tp> Ptr = std::shared_ptr<_Tp>\n"
|
||||
"```",
|
||||
text)
|
||||
|
||||
# 8e. Classes table rows: append template params + "View details" link.
|
||||
def _rewrite_class_row(m: re.Match) -> str:
|
||||
kind = m.group("kind")
|
||||
name = m.group("name") # 'cv::dnn::BackendNode'
|
||||
@@ -486,34 +594,96 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
r'(?P<inner>.*?)(?P<close></code>)',
|
||||
_linkify_inside_code, text, flags=re.DOTALL)
|
||||
|
||||
def _linkify_markdown_codespan(m: re.Match) -> str:
|
||||
content = m.group("content")
|
||||
# Prefix-aware: `cv::Name` is one hit so the anchor covers both.
|
||||
hits = [(t.start(), t.end(), t.group(0)) for t in
|
||||
_tok_re.finditer(content)
|
||||
if _token_url(_bare(t.group(0)))]
|
||||
if not hits:
|
||||
# 8g. Linkify recognized type tokens in code spans across every API
|
||||
# page (Functions summary + Function Documentation detail blocks).
|
||||
# Was previously gated to `main_modules/core_basic` only — that
|
||||
# left parameter types in other module pages (calib, dnn, …) as
|
||||
# plain code chips even though their local typedef/class targets
|
||||
# exist. Pass 1 walks any existing `<code>` HTML emitted by
|
||||
# `_func_row_split_md` (function name already wrapped in `<a>`;
|
||||
# remaining text tokens get individual anchors). Pass 2 walks
|
||||
# remaining markdown code spans for any rows still using the
|
||||
# backticked-signature form.
|
||||
if (docname and (docname.startswith("api/")
|
||||
or docname.startswith("main_modules/")
|
||||
or docname.startswith("extra_modules/"))
|
||||
and (_LOCAL_CLASS_URL or _LOCAL_TYPEDEF_URL)):
|
||||
def _token_url(tok: str) -> str | None:
|
||||
# Tokens absent from the tagfile stay plain.
|
||||
return _LOCAL_CLASS_URL.get(tok) or _LOCAL_TYPEDEF_URL.get(tok)
|
||||
# Match an optional `cv::` prefix so the anchor spans `cv::Name`.
|
||||
_tok_re = re.compile(r"(?:cv::)?_?[A-Za-z][A-Za-z0-9_]*")
|
||||
def _bare(tok: str) -> str:
|
||||
return tok[4:] if tok.startswith("cv::") else tok
|
||||
def _anchor_text(tok: str) -> str:
|
||||
# Encode `::` so the later cv-linkifier doesn't nest a second anchor.
|
||||
return tok.replace("::", "::")
|
||||
def _linkify_html_segment(seg: str) -> str:
|
||||
def _sub(m: re.Match) -> str:
|
||||
url = _token_url(_bare(m.group(0)))
|
||||
if not url:
|
||||
return m.group(0)
|
||||
from html import escape as _esc
|
||||
parts, last = [], 0
|
||||
for s, e, tok in hits:
|
||||
parts.append(_esc(content[last:s]))
|
||||
parts.append(f'<a class="reference internal" '
|
||||
f'href="{_token_url(_bare(tok))}">'
|
||||
f'{_anchor_text(tok)}</a>')
|
||||
last = e
|
||||
parts.append(_esc(content[last:]))
|
||||
return (f'<code class="docutils literal notranslate">'
|
||||
f'{"".join(parts)}</code>')
|
||||
_masked: list[str] = []
|
||||
def _mask(m: re.Match) -> str:
|
||||
_masked.append(m.group(0))
|
||||
return f"\x00MDLINK{len(_masked)-1}\x00"
|
||||
text = re.sub(r"\[(?:`[^`\n]+`|<br>)+\]\([^)\n]+\)", _mask, text)
|
||||
text = re.sub(r"`(?P<content>[^`\n]+?)`",
|
||||
_linkify_markdown_codespan, text)
|
||||
text = re.sub(r"\x00MDLINK(\d+)\x00",
|
||||
lambda m: _masked[int(m.group(1))], text)
|
||||
return (f'<a class="reference internal" '
|
||||
f'href="{url}">{_anchor_text(m.group(0))}</a>')
|
||||
return _tok_re.sub(_sub, seg)
|
||||
# Pass 1: walk every `<code class="docutils literal notranslate">…</code>`
|
||||
# block; skip any `<a>` already inside (function-name anchor from
|
||||
# `_func_row_split_md`), linkify recognized tokens in the rest.
|
||||
def _linkify_inside_code(m: re.Match) -> str:
|
||||
inner = m.group("inner")
|
||||
out, i, n = [], 0, len(inner)
|
||||
while i < n:
|
||||
if inner.startswith("<a ", i):
|
||||
j = inner.find("</a>", i)
|
||||
if j < 0:
|
||||
out.append(inner[i:]); break
|
||||
out.append(inner[i:j + 4]); i = j + 4
|
||||
else:
|
||||
k = inner.find("<a ", i)
|
||||
if k < 0:
|
||||
out.append(_linkify_html_segment(inner[i:])); break
|
||||
out.append(_linkify_html_segment(inner[i:k])); i = k
|
||||
return m.group("open") + "".join(out) + m.group("close")
|
||||
text = re.sub(
|
||||
r'(?P<open><code class="docutils literal notranslate">)'
|
||||
r'(?P<inner>.*?)(?P<close></code>)',
|
||||
_linkify_inside_code, text, flags=re.DOTALL)
|
||||
# Pass 2: remaining markdown code spans — the Type-column typedef
|
||||
# chips (`InputArray`, `Mat_<uchar>`, `const _InputArray &`, …)
|
||||
# and any other backticked tokens that MyST only turns into
|
||||
# `<code><span class="pre">…</span></code>` at render time, AFTER
|
||||
# this step has run (so pass 1's `<code>` walk can't reach them,
|
||||
# and postprocess's inline walker can't either — Sphinx's
|
||||
# `<span class="pre">` wrapper defeats its body regex). Markdown
|
||||
# links are masked first so anchored Name-column cells stay intact.
|
||||
def _linkify_markdown_codespan(m: re.Match) -> str:
|
||||
content = m.group("content")
|
||||
# Prefix-aware: `cv::Name` is one hit so the anchor covers both.
|
||||
hits = [(t.start(), t.end(), t.group(0)) for t in
|
||||
_tok_re.finditer(content)
|
||||
if _token_url(_bare(t.group(0)))]
|
||||
if not hits:
|
||||
return m.group(0)
|
||||
from html import escape as _esc
|
||||
parts, last = [], 0
|
||||
for s, e, tok in hits:
|
||||
parts.append(_esc(content[last:s]))
|
||||
parts.append(f'<a class="reference internal" '
|
||||
f'href="{_token_url(_bare(tok))}">'
|
||||
f'{_anchor_text(tok)}</a>')
|
||||
last = e
|
||||
parts.append(_esc(content[last:]))
|
||||
return (f'<code class="docutils literal notranslate">'
|
||||
f'{"".join(parts)}</code>')
|
||||
_masked: list[str] = []
|
||||
def _mask(m: re.Match) -> str:
|
||||
_masked.append(m.group(0))
|
||||
return f"\x00MDLINK{len(_masked)-1}\x00"
|
||||
text = re.sub(r"\[(?:`[^`\n]+`|<br>)+\]\([^)\n]+\)", _mask, text)
|
||||
text = re.sub(r"`(?P<content>[^`\n]+?)`",
|
||||
_linkify_markdown_codespan, text)
|
||||
text = re.sub(r"\x00MDLINK(\d+)\x00",
|
||||
lambda m: _masked[int(m.group(1))], text)
|
||||
|
||||
# 6c. Bullet lists of @subpage/@ref -> toctree + visible list. Runs BEFORE step 7.
|
||||
def _subpage_list_to_toctree(src: str) -> str:
|
||||
@@ -606,6 +776,17 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
text = re.sub(r'@ref\s+(?P<name>[\w:-]+)(?:\s+"(?P<disp>[^"]+)")?',
|
||||
_ref_repl, text)
|
||||
|
||||
# 7c. cv.Name -> Markdown link using _CV_SYMBOL_URL; skips code spans.
|
||||
if _CV_SYMBOL_URL:
|
||||
def _cvlink_repl(m: re.Match) -> str:
|
||||
url = _CV_SYMBOL_URL.get(m.group(1))
|
||||
return f'[cv.{m.group(1)}]({url})' if url else m.group(0)
|
||||
_parts = re.split(r'(```.*?```|`[^`\n]+`)', text, flags=re.DOTALL)
|
||||
text = ''.join(
|
||||
p if i % 2 else re.sub(
|
||||
r'(?<!\[)(?<!\()cv\.([A-Za-z][A-Za-z0-9_]*)', _cvlink_repl, p)
|
||||
for i, p in enumerate(_parts))
|
||||
|
||||
# 8. @cite KEY -> `[N]` HTML anchor to citelist (N from opencv.bib order).
|
||||
def _cite_repl(m: re.Match) -> str:
|
||||
key = m.group("key")
|
||||
@@ -668,6 +849,19 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
return pat.sub(repl, src)
|
||||
text = _dedent_subpage_descriptions(text)
|
||||
|
||||
# 9z. Doxygen group/member URLs -> local cross-ref when we built that page.
|
||||
def _doxy_to_local(m: re.Match) -> str:
|
||||
base, anchor = m.group("base"), m.group("anchor")
|
||||
if anchor:
|
||||
tgt = f"{base}_1{anchor}"
|
||||
return f"](#{tgt})" if tgt in _LOCAL_MEMBER_IDS else m.group(0)
|
||||
tgt = f"api_{base[len('group__'):].replace('__', '_')}"
|
||||
return f"](#{tgt})" if tgt in _ANCHOR_TO_DOC else m.group(0)
|
||||
text = re.sub(
|
||||
r"\]\(" + re.escape(DOXYGEN_BASE_URL)
|
||||
+ r"(?:[\w-]+/)*?(?P<base>group__\w+)\.html(?:#(?P<anchor>\w+))?\)",
|
||||
_doxy_to_local, text)
|
||||
|
||||
# 10. @next_tutorial / @prev_tutorial -> drop
|
||||
text = re.sub(r"^@(?:next|prev)_tutorial\{[^}]*\}\s*$", "",
|
||||
text, flags=re.MULTILINE)
|
||||
@@ -766,7 +960,20 @@ def _translate(text: str, docname: str | None = None) -> str:
|
||||
r'!\[(?P<alt>[^\]]*)\]\((?:[^)]*?/)?(?P<dir>images|js_assets)/(?P<rel>[^)]+)\)',
|
||||
_img_repl, text)
|
||||
|
||||
# 12b. Cross-tree contrib image refs -> raw-HTML <img>, depth-relative URL.
|
||||
# 12b. Bare image filename (no dir prefix, e.g. "shape.jpg") -> _IMAGE_INDEX lookup.
|
||||
def _bare_img_repl(m: re.Match) -> str:
|
||||
rel = m.group("rel")
|
||||
if docname:
|
||||
local = DOC_ROOT / pathlib.Path(docname).parent / "images" / rel
|
||||
if local.is_file():
|
||||
return f'{m.group("pre")}images/{rel})'
|
||||
hit = _IMAGE_INDEX.get(rel)
|
||||
return f'{m.group("pre")}/{hit})' if hit else m.group(0)
|
||||
text = re.sub(
|
||||
r'(?P<pre>!\[[^\]]*\]\()(?P<rel>[A-Za-z0-9_.-]+\.[A-Za-z]{2,4})\)',
|
||||
_bare_img_repl, text)
|
||||
|
||||
# 12c. Cross-tree contrib image refs -> raw-HTML <img>, depth-relative URL.
|
||||
def _img_xtree(m: re.Match) -> str:
|
||||
alt, rel = m.group("alt"), m.group("rel")
|
||||
if rel.startswith("/") or "://" in rel:
|
||||
|
||||
@@ -121,28 +121,50 @@ def _member_detail_parts(md):
|
||||
if de is None:
|
||||
return "", [], ""
|
||||
params, returns = [], ""
|
||||
for pl in de.iter("parameterlist"):
|
||||
if pl.get("kind") in ("param", "templateparam"):
|
||||
for it in pl.findall("parameteritem"):
|
||||
nm = ", ".join(
|
||||
t for t in (_itertext(n) for n in
|
||||
it.findall(".//parametername")) if t)
|
||||
d = _doxygen_desc_to_md(it.find("parameterdescription"))
|
||||
if nm:
|
||||
params.append((nm, d))
|
||||
for ss in de.iter("simplesect"):
|
||||
if ss.get("kind") == "return":
|
||||
returns = _itertext(ss)
|
||||
for para in de.findall("para"):
|
||||
for pl in para.findall("parameterlist"):
|
||||
if pl.get("kind") in ("param", "templateparam"):
|
||||
for it in pl.findall("parameteritem"):
|
||||
nm = ", ".join(
|
||||
t for t in (_itertext(n) for n in
|
||||
it.findall(".//parametername")) if t)
|
||||
# Block-aware: a description carrying an <itemizedlist>
|
||||
# (e.g. calibration `flags`) keeps its bullets as real
|
||||
# Markdown instead of collapsing into a run-on paragraph.
|
||||
d = _doxygen_desc_to_md(it.find("parameterdescription"))
|
||||
if nm:
|
||||
params.append((nm, d))
|
||||
for ss in para.findall("simplesect"):
|
||||
if ss.get("kind") == "return":
|
||||
returns = _itertext(ss)
|
||||
# Prune the param/return chrome (rendered separately) then convert the rest
|
||||
# with full block support so lists and notes survive. Preserve tail text when
|
||||
# removing elements so descriptions after parameterlist are not lost.
|
||||
pruned = _copy.deepcopy(de)
|
||||
def _strip(el):
|
||||
for child in list(el):
|
||||
if child.tag == "parameterlist" or (
|
||||
child.tag == "simplesect"
|
||||
and child.get("kind") in ("param", "templateparam", "return")):
|
||||
el.remove(child)
|
||||
else:
|
||||
_strip(child)
|
||||
_strip(pruned)
|
||||
for para in pruned.findall("para"):
|
||||
for child in list(para):
|
||||
if child.tag == "parameterlist":
|
||||
# Preserve tail text (text after the element) by moving it to previous sibling
|
||||
if child.tail:
|
||||
prev_idx = list(para).index(child) - 1
|
||||
if prev_idx >= 0:
|
||||
prev_sibling = para[prev_idx]
|
||||
prev_sibling.tail = (prev_sibling.tail or "") + child.tail
|
||||
else:
|
||||
# No previous sibling, prepend to para text
|
||||
para.text = (para.text or "") + child.tail
|
||||
para.remove(child)
|
||||
elif (child.tag == "simplesect"
|
||||
and child.get("kind") in ("param", "templateparam", "return")):
|
||||
# Same tail preservation for simplesect
|
||||
if child.tail:
|
||||
prev_idx = list(para).index(child) - 1
|
||||
if prev_idx >= 0:
|
||||
prev_sibling = para[prev_idx]
|
||||
prev_sibling.tail = (prev_sibling.tail or "") + child.tail
|
||||
else:
|
||||
para.text = (para.text or "") + child.tail
|
||||
para.remove(child)
|
||||
return _doxygen_desc_to_md(pruned), params, returns
|
||||
|
||||
|
||||
@@ -213,6 +235,8 @@ def _parse_member_sections(cd) -> dict[str, list[dict]]:
|
||||
"kind": kind,
|
||||
"name": (md.findtext("name") or "").strip(),
|
||||
"qualified": qualified,
|
||||
"definition": (md.findtext("definition") or "").strip(),
|
||||
"virt": md.get("virt", "non-virtual"),
|
||||
"type": _itertext(md.find("type")),
|
||||
"args": (md.findtext("argsstring") or "").strip(),
|
||||
"param_types": param_types,
|
||||
@@ -258,6 +282,8 @@ def _build_api_hierarchy(refid: str, xml_dir: pathlib.Path,
|
||||
return None
|
||||
name = (cd.findtext("compoundname") or "").strip()
|
||||
title = (cd.findtext("title") or name).strip()
|
||||
brief_el = cd.find("briefdescription")
|
||||
brief = _doxygen_desc_to_md(brief_el).strip() if brief_el is not None else ""
|
||||
detailed_el = cd.find("detaileddescription")
|
||||
detailed = _doxygen_desc_to_md(detailed_el) if detailed_el is not None else ""
|
||||
# Inner classes (public only). One read per class's XML for its brief.
|
||||
@@ -282,7 +308,7 @@ def _build_api_hierarchy(refid: str, xml_dir: pathlib.Path,
|
||||
child = _build_api_hierarchy(ig.get("refid"), xml_dir, _seen)
|
||||
if child is not None:
|
||||
children.append(child)
|
||||
return {"name": name, "title": title, "detailed": detailed,
|
||||
return {"name": name, "title": title, "brief": brief, "detailed": detailed,
|
||||
"innerclasses": innerclasses, "sections": sections,
|
||||
"children": children}
|
||||
|
||||
@@ -326,25 +352,242 @@ def _doxygen_desc_to_md(el, h_level: int = 3) -> str:
|
||||
parts.append(child.tail)
|
||||
return "".join(parts)
|
||||
|
||||
# Doxygen <highlight class="…"> → Pygments token class, so the
|
||||
# rendered <pre> picks up the existing `.highlight pre .k/.n/.kt/.s/.c`
|
||||
# CSS styling and our `code > a { color: inherit }` rule won't dim the
|
||||
# link colour.
|
||||
_HL_PYG_CLASS = {
|
||||
"keyword": "k", "keywordtype": "kt", "keywordflow": "k",
|
||||
"preprocessor": "cp", "comment": "c", "comment-multiline": "cm",
|
||||
"stringliteral": "s", "charliteral": "sc",
|
||||
}
|
||||
# Identifier-only token regex; used to linkify `InputArray`,
|
||||
# `OutputArray`, `Mat`, `Scalar`, `DFT_INVERSE`, … left as plain
|
||||
# text by Doxygen (no `<ref>` on them).
|
||||
_IDENT_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
||||
# `#include "opencv2/…"` / `#include <opencv2/…>` regex. Doxygen
|
||||
# wraps the WHOLE include directive (keyword + quoted/angled path)
|
||||
# in one `<highlight class="preprocessor">` span — no separate
|
||||
# token for the path — so the per-identifier linkifier above can't
|
||||
# see the path. Match the include line and wrap just the path in
|
||||
# an `<a>` to its local Doxygen file page (`_FILE_URL`).
|
||||
_INCLUDE_PATH_RE = re.compile(
|
||||
r'(#include\s*)(["<])([A-Za-z0-9_./+\-]+\.[A-Za-z0-9]+)([">])'
|
||||
)
|
||||
|
||||
def _programlisting(node) -> str:
|
||||
lines = []
|
||||
for codeline in node.findall("codeline"):
|
||||
lines.append("".join(_hl_text(hl) for hl in codeline.findall("highlight")))
|
||||
return "```cpp\n" + "\n".join(lines) + "\n```"
|
||||
"""Emit `<programlisting>` as a raw HTML `<pre>` block with
|
||||
per-token `<a>` anchors. The user's spec: code listings must
|
||||
be CLICKABLE — every recognised identifier (class/typedef/enum
|
||||
member or `<ref>`-marked token) becomes a link to its local
|
||||
Sphinx target. Falls back to plain text for unknown tokens, so
|
||||
keywords/numbers/operators stay un-linked but still picked up
|
||||
by the Pygments-style span classes for colouring."""
|
||||
from html import escape as _esc
|
||||
out = ['<div class="highlight-cpp notranslate"><div class="highlight"><pre>']
|
||||
|
||||
def _linkify_plain(text: str) -> str:
|
||||
"""Wrap identifier runs that resolve via `_local_url`
|
||||
with an `<a>`; everything else stays HTML-escaped text."""
|
||||
parts: list[str] = []
|
||||
last = 0
|
||||
for m in _IDENT_RE.finditer(text):
|
||||
if m.start() > last:
|
||||
parts.append(_esc(text[last:m.start()]))
|
||||
tok = m.group(0)
|
||||
url = _LOCAL_CLASS_URL.get(tok) or _LOCAL_TYPEDEF_URL.get(tok)
|
||||
if url:
|
||||
parts.append(
|
||||
f'<a class="reference internal" href="{url}">'
|
||||
f'<span class="n">{_esc(tok)}</span></a>')
|
||||
else:
|
||||
parts.append(_esc(tok))
|
||||
last = m.end()
|
||||
parts.append(_esc(text[last:]))
|
||||
return "".join(parts)
|
||||
|
||||
def _linkify_include_path(escaped: str) -> str:
|
||||
"""Find `#include "opencv2/…"` patterns in already-escaped
|
||||
text and wrap the path in an `<a>` to its Doxygen file
|
||||
page. Operates on POST-escape text (where `"` is `"`
|
||||
and `<>` are `<`/`>`)."""
|
||||
def _sub(m: re.Match) -> str:
|
||||
kw, openq, path, closeq = m.groups()
|
||||
file_url = _FILE_URL.get(path)
|
||||
if not file_url:
|
||||
return m.group(0)
|
||||
href = f"../../../doc/doxygen/html/{file_url}"
|
||||
return (f'{kw}{openq}'
|
||||
f'<a class="reference external opencv-include-link" '
|
||||
f'href="{href}">{path}</a>{closeq}')
|
||||
# The escape map converts `"` → `"`, `<` → `<`,
|
||||
# `>` → `>`; rewrite the regex once for that form.
|
||||
def _re_escaped() -> "re.Pattern":
|
||||
return re.compile(
|
||||
r'(#include\s*)("|<)'
|
||||
r'([A-Za-z0-9_./+\-]+\.[A-Za-z0-9]+)'
|
||||
r'("|>)'
|
||||
)
|
||||
return _re_escaped().sub(_sub, escaped)
|
||||
|
||||
def _emit_highlight(hl) -> str:
|
||||
"""One `<highlight class="…">` → optionally-wrapped span
|
||||
sequence preserving inline `<ref>` link targets."""
|
||||
klass = _HL_PYG_CLASS.get(hl.get("class", ""), "")
|
||||
segs: list[str] = []
|
||||
if hl.text:
|
||||
segs.append(_linkify_plain(hl.text))
|
||||
for child in hl:
|
||||
if child.tag == "sp":
|
||||
segs.append(" ")
|
||||
elif child.tag == "ref":
|
||||
inner = "".join(child.itertext())
|
||||
url = _local_url(child.get("refid", ""), inner)
|
||||
if url:
|
||||
segs.append(
|
||||
f'<a class="reference internal" href="{url}">'
|
||||
f'<span class="n">{_esc(inner)}</span></a>')
|
||||
else:
|
||||
segs.append(_linkify_plain(inner))
|
||||
else:
|
||||
segs.append(_linkify_plain("".join(child.itertext())))
|
||||
if child.tail:
|
||||
segs.append(_linkify_plain(child.tail))
|
||||
body = "".join(segs)
|
||||
# Preprocessor lines: linkify any `#include "path"` AFTER
|
||||
# the per-identifier pass (Doxygen lumps the whole include
|
||||
# directive into one `class="preprocessor"` span, so the
|
||||
# path can only be found at the assembled-body level).
|
||||
if klass == "cp":
|
||||
body = _linkify_include_path(body)
|
||||
# Wrap in a Pygments-style `<span class="X">` only for token
|
||||
# kinds whose colouring would otherwise be lost. The "normal"
|
||||
# highlight class is the un-coloured default — emit without
|
||||
# the outer span so embedded `<a>` link colours win.
|
||||
if klass and body:
|
||||
return f'<span class="{klass}">{body}</span>'
|
||||
return body
|
||||
|
||||
codelines = node.findall("codeline")
|
||||
for i, cl in enumerate(codelines):
|
||||
parts = [_emit_highlight(hl) for hl in cl.findall("highlight")]
|
||||
line = "".join(parts)
|
||||
# Empty codeline → emit `<span></span>` placeholder so the
|
||||
# blank line doesn't terminate the surrounding raw-HTML
|
||||
# block under CommonMark rule 7.
|
||||
out.append(line if line else "<span></span>")
|
||||
out.append('</pre></div></div>')
|
||||
# Wrap the whole thing in a `\n` so MyST parses it as a raw
|
||||
# HTML block (CommonMark type 6) — the leading line is `<div…>`
|
||||
# which qualifies. Trailing blank line ends the block.
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
def _ref_link(refid: str, text: str) -> str:
|
||||
if not (refid and text):
|
||||
return f"`{text}`" if text else ""
|
||||
m = re.search(r'_1([a-z]{1,3}[0-9a-f]{20,})$', refid)
|
||||
if m:
|
||||
url = f"{DOXYGEN_BASE_URL}{refid[:m.start()]}.html#{m.group(1)}"
|
||||
else:
|
||||
url = f"{DOXYGEN_BASE_URL}{refid}.html"
|
||||
return f"[`{text}`]({url})"
|
||||
"""Inline `<ref>` → blue link to a LOCAL Sphinx target.
|
||||
The user's spec for cross-references: "blue text, no grey
|
||||
chip, redirect to local pages, no off-site bounce". So:
|
||||
- LOCAL URLs only via `_local_url(refid, text)`; when no
|
||||
local target exists we drop the link (return plain text)
|
||||
instead of routing the reader to docs.opencv.org.
|
||||
- emitted as RAW HTML (`<a class="reference internal" …>`)
|
||||
rather than markdown `[text](url)`. Markdown link syntax
|
||||
in MyST/Sphinx with a `#fragment` URL is interpreted as a
|
||||
pending domain xref — when the fragment doesn't match a
|
||||
registered domain target the result is `<span class="xref
|
||||
myst">text</span>` (unresolved) plus a `#` prefix on the
|
||||
already-`#` href. Raw HTML bypasses the xref resolver so
|
||||
the anchor stays a plain in-page link.
|
||||
- no backticks → no `<code>` grey chip and no 500-weight
|
||||
on the text.
|
||||
"""
|
||||
if not text:
|
||||
return ""
|
||||
url = _local_url(refid, text) if refid else None
|
||||
if not url:
|
||||
return text
|
||||
from html import escape as _esc_rl
|
||||
return (f'<a class="reference internal" href="{url}">'
|
||||
f'{_esc_rl(text)}</a>')
|
||||
|
||||
def _local_url(refid: str, name: str) -> str | None:
|
||||
"""Resolve a Doxygen `<ref>` to a Sphinx-local URL.
|
||||
Layered lookup:
|
||||
1. Class/struct short-name → curated map (`Mat`, `InputArray`,…)
|
||||
from `_LOCAL_CLASS_URL`/`_LOCAL_TYPEDEF_URL`.
|
||||
2. Class/struct member refid (`classcv_1_1Mat_1a<hex>`) →
|
||||
class page + slugified member anchor.
|
||||
3. Group-anchored function refid (`group__core__array_1ga<hex>`)
|
||||
→ same-page slugified anchor (the function's detail block
|
||||
emits `({refid})=` which MyST turns into `<span id="…">`).
|
||||
4. Bare class/struct compound refid → class page.
|
||||
Returns None when nothing matches — caller renders plain text."""
|
||||
if not refid:
|
||||
return None
|
||||
# Try both the full name and its short form (after the last
|
||||
# `::`). The typedef/class maps key on the short name only
|
||||
# (e.g. `InputArray`, not `cv::InputArray`), so a `<ref>` whose
|
||||
# text says `cv::InputArray` would otherwise miss this lookup
|
||||
# and fall through to the function-slug branch below — minting
|
||||
# a non-existent `#cv-inputarray` anchor instead of the real
|
||||
# typedef target `core_basic.html#inputarray`.
|
||||
short_name = name.rsplit("::", 1)[-1] if name else ""
|
||||
direct = (_LOCAL_CLASS_URL.get(name) or _LOCAL_TYPEDEF_URL.get(name)
|
||||
or _LOCAL_CLASS_URL.get(short_name)
|
||||
or _LOCAL_TYPEDEF_URL.get(short_name))
|
||||
if direct:
|
||||
return direct
|
||||
# Class-member refid (long hex suffix).
|
||||
cm = re.match(
|
||||
r"^((?:class|struct)cv_1_1[A-Za-z0-9_]+?)_1"
|
||||
r"([a-z]{1,3}[0-9a-f]{20,})$", refid)
|
||||
if cm:
|
||||
page = cm.group(1)
|
||||
slug = re.sub(r"_+", "-", refid).lower()
|
||||
return f"{page}.html#{slug}"
|
||||
# Group-anchored member on the current group page.
|
||||
#
|
||||
# Doxygen uses two refid suffixes for group members:
|
||||
# * `_1ga<hex>` — function in a group → anchor is the
|
||||
# `_func_slug(name)` (cv-name) emitted by
|
||||
# `_render_core_basic_func`.
|
||||
# * `_1gga<hex>` — enum VALUE in a group → anchor is the
|
||||
# C++ v4 id (`_CPPv4N…E`) emitted by the
|
||||
# per-value `<span id>` in the enum
|
||||
# detail table. The enum-type's parent
|
||||
# name is captured via `_CV_SYMBOL_URL`
|
||||
# elsewhere; here we use the value name
|
||||
# + the parent enum name when available
|
||||
# to mint the same id.
|
||||
m = re.search(r"^group__(?P<grp>[A-Za-z0-9_]+?)_1(?P<kind>gga|ga)(?P<hex>[0-9a-f]+)$", refid)
|
||||
if m:
|
||||
kind = m.group("kind")
|
||||
if kind == "ga": # function
|
||||
short = (name.rsplit("::", 1)[-1] if name else "")
|
||||
short = short.split("(", 1)[0].strip()
|
||||
if not short:
|
||||
return None
|
||||
page_stem = m.group("grp").replace("__", "_")
|
||||
return f"{page_stem}.html#{short.lower()}"
|
||||
# Enum VALUE `gga<enumhash>a<valuehash>`: group pages carry no
|
||||
# per-value anchor, so link to the enum TYPE's doc (refid ga<enumhash>).
|
||||
hexv = m.group("hex")
|
||||
if len(hexv) == 65 and hexv[32] == "a":
|
||||
return _ENUM_REF_URL.get(
|
||||
f"group__{m.group('grp')}_1ga{hexv[:32]}")
|
||||
return None
|
||||
# Bare class/struct compound page.
|
||||
if refid.startswith(("classcv_1_1", "structcv_1_1")):
|
||||
return f"{refid}.html"
|
||||
return None
|
||||
_formula_md = _render_formula
|
||||
|
||||
_BLOCK_TAGS = {"orderedlist", "itemizedlist", "programlisting", "simplesect", "table"}
|
||||
_BLOCK_TAGS = {"orderedlist", "itemizedlist", "programlisting", "simplesect",
|
||||
"table", "xrefsect"}
|
||||
|
||||
# Map missing 'see' and 'sa' Doxygen sections to MyST 'seealso' admonitions to fix unboxed reference rendering.
|
||||
_ADMON_BY_KIND = {"note": "note", "warning": "warning",
|
||||
"attention": "warning", "remark": "note",
|
||||
"see": "seealso", "sa": "seealso"}
|
||||
|
||||
def _emit_block(sub, result: list, level: int) -> None:
|
||||
t = sub.tag
|
||||
@@ -358,13 +601,20 @@ def _doxygen_desc_to_md(el, h_level: int = 3) -> str:
|
||||
result.append(f"- {_listitem_text(item)}")
|
||||
elif t == "simplesect":
|
||||
kind = sub.get("kind", "")
|
||||
admon = {"note": "note", "warning": "warning",
|
||||
"attention": "warning", "remark": "note"}.get(kind)
|
||||
admon = _ADMON_BY_KIND.get(kind)
|
||||
body = "\n\n".join(_blocks(sub, level))
|
||||
if admon:
|
||||
result.append(f":::{{{admon}}}\n{body}\n:::")
|
||||
elif body:
|
||||
result.append(body)
|
||||
elif t == "xrefsect":
|
||||
# @todo/@bug/@deprecated -> a titled admonition box (like Doxygen's).
|
||||
title = (sub.findtext("xreftitle") or "Note").strip()
|
||||
desc = sub.find("xrefdescription")
|
||||
body = "\n\n".join(_blocks(desc, level)) if desc is not None else ""
|
||||
if body:
|
||||
result.append(
|
||||
f":::{{admonition}} {title}\n:class: {title.lower()}\n\n{body}\n:::")
|
||||
elif t == "table":
|
||||
rows = sub.findall("row")
|
||||
if not rows:
|
||||
@@ -473,8 +723,7 @@ def _doxygen_desc_to_md(el, h_level: int = 3) -> str:
|
||||
elif t == "simplesect":
|
||||
# Merge consecutive simplesects of the same kind into one admonition.
|
||||
kind = child.get("kind", "")
|
||||
admon = {"note": "note", "warning": "warning",
|
||||
"attention": "warning", "remark": "note"}.get(kind)
|
||||
admon = _ADMON_BY_KIND.get(kind)
|
||||
bodies = []
|
||||
while (i < len(children) and children[i].tag == "simplesect"
|
||||
and children[i].get("kind", "") == kind):
|
||||
@@ -762,6 +1011,17 @@ def _read_class_data(refid: str, xml_dir: pathlib.Path) -> dict | None:
|
||||
"detailed": _dtl,
|
||||
"params": _params,
|
||||
"returns": _returns,
|
||||
# (text, refid) for every <ref> in the return type, param types
|
||||
# and default values — the exact tokens Doxygen hyperlinks in a
|
||||
# signature (classes, typedefs, enum values), keyed by refid.
|
||||
"sig_refs": [((r.text or "").strip(), r.get("refid", ""))
|
||||
for el in ([md.find("type")]
|
||||
+ [c for p in md.findall("param")
|
||||
for c in (p.find("type"),
|
||||
p.find("defval"))])
|
||||
if el is not None
|
||||
for r in el.iter("ref")
|
||||
if (r.text or "").strip() and r.get("refid")],
|
||||
})
|
||||
if items:
|
||||
sections[skind] = items
|
||||
@@ -772,12 +1032,15 @@ def _read_class_data(refid: str, xml_dir: pathlib.Path) -> dict | None:
|
||||
_itertext(p).strip() for p in detailed_el.findall("para")
|
||||
))
|
||||
include = _normalize_include(cd.findtext("includes") or "")
|
||||
bases = [(b.get("refid"), (b.text or "").strip(), b.get("prot", "public"))
|
||||
for b in cd.findall("basecompoundref") if b.get("refid")]
|
||||
return {
|
||||
"name": (cd.findtext("compoundname") or "").strip(),
|
||||
"brief": _itertext(cd.find("briefdescription")),
|
||||
"detailed": has_detailed,
|
||||
"sections": sections,
|
||||
"include": include,
|
||||
"bases": bases,
|
||||
}
|
||||
|
||||
|
||||
@@ -855,6 +1118,9 @@ def _svg_dark_variant(text: str) -> str:
|
||||
block = block.replace('fill="grey"', 'fill="none"')
|
||||
block = block.replace('fill="#999999"', 'fill="#2d333b"')
|
||||
block = block.replace('fill="#bfbfbf"', 'fill="#2d333b"')
|
||||
# Non-clickable leaf boxes (std headers) are light grey #e0e0e0 — make
|
||||
# them the same darker grey as the current-file box, white text below.
|
||||
block = block.replace('fill="#e0e0e0"', 'fill="#2d333b"')
|
||||
block = block.replace('<text ', '<text fill="#ffffff" ')
|
||||
return block
|
||||
text = _re.sub(r'<g[^>]*class="node"[^>]*>.*?</g>',
|
||||
@@ -862,6 +1128,7 @@ def _svg_dark_variant(text: str) -> str:
|
||||
text = text.replace('fill="grey"', 'fill="none"')
|
||||
text = text.replace('fill="#999999"', 'fill="#2d333b"')
|
||||
text = text.replace('fill="#bfbfbf"', 'fill="#2d333b"')
|
||||
text = text.replace('fill="#e0e0e0"', 'fill="#2d333b"')
|
||||
# Lookahead avoids double-prefixing per-node texts.
|
||||
text = _re.sub(r'<text (?!fill)', '<text fill="#ffffff" ', text)
|
||||
return text
|
||||
|
||||
@@ -57,8 +57,8 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
In addition to this way of layers instantiation, there is a more common factory API (see @ref dnnLayerFactory), it allows to create layers dynamically (by name) and register new ones.
|
||||
You can use both API, but factory API is less convenient for native C++ programming and basically designed for use inside importers (see @ref readNetFromTensorflow()).
|
||||
|
||||
Built-in layers partially reproduce functionality of corresponding ONNX, TensorFlow and Caffe layers.
|
||||
In particular, the following layers and Caffe importer were tested to reproduce <a href="http://caffe.berkeleyvision.org/tutorial/layers.html">Caffe</a> functionality:
|
||||
Built-in layers reproduce the functionality of the corresponding ONNX and TensorFlow operators.
|
||||
The following layers are among the core building blocks used to assemble imported networks:
|
||||
- Convolution
|
||||
- Deconvolution
|
||||
- Pooling
|
||||
|
||||
@@ -126,7 +126,6 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
DNN_MODEL_ONNX = 1, //!< ONNX model
|
||||
DNN_MODEL_TF = 2, //!< TF model
|
||||
DNN_MODEL_TFLITE = 3, //!< TFLite model
|
||||
DNN_MODEL_CAFFE = 4, //!< Caffe model
|
||||
};
|
||||
|
||||
CV_EXPORTS std::string modelFormatToString(ModelFormat modelFormat);
|
||||
|
||||
@@ -145,8 +145,7 @@ PERF_TEST_P_(DNNTestNetwork, SSD)
|
||||
{
|
||||
applyTestTag(CV_TEST_TAG_DEBUG_VERYLONG);
|
||||
|
||||
// The Caffe-SSD specific handling lives in the new engine importer only;
|
||||
// the classic importer can no longer load this model.
|
||||
// SSD_VGG16's specialized preprocessing is handled by the new engine importer only.
|
||||
auto engine_forced = static_cast<dnn::EngineType>(
|
||||
utils::getConfigurationParameterSizeT("OPENCV_FORCE_DNN_ENGINE", dnn::ENGINE_AUTO));
|
||||
if (engine_forced == dnn::ENGINE_CLASSIC)
|
||||
@@ -190,9 +189,7 @@ PERF_TEST_P_(DNNTestNetwork, OpenPose_pose_mpi_faster_4_stages)
|
||||
|
||||
if (backend == DNN_BACKEND_INFERENCE_ENGINE_NN_BUILDER_2019 && (target == DNN_TARGET_MYRIAD || target == DNN_TARGET_HDDL))
|
||||
throw SkipTestException("");
|
||||
// The same .caffemodel but modified .prototxt
|
||||
// See https://github.com/CMU-Perceptual-Computing-Lab/openpose/blob/master/src/openpose/pose/poseParameters.cpp
|
||||
// processNet("dnn/openpose_pose_mpi.caffemodel", "dnn/openpose_pose_mpi_faster_4_stages.prototxt", cv::Size(368, 368));
|
||||
processNet("dnn/onnx/models/openpose_pose_mpi.onnx", "", cv::Size(368, 368));
|
||||
}
|
||||
|
||||
|
||||
@@ -263,8 +263,7 @@ std::string modelFormatToString(ModelFormat modelFormat)
|
||||
return
|
||||
modelFormat == DNN_MODEL_ONNX ? "ONNX" :
|
||||
modelFormat == DNN_MODEL_TF ? "TF" :
|
||||
modelFormat == DNN_MODEL_TFLITE ? "TFLite" :
|
||||
modelFormat == DNN_MODEL_CAFFE ? "Caffe" : "Unknown/Generic";
|
||||
modelFormat == DNN_MODEL_TFLITE ? "TFLite" : "Unknown/Generic";
|
||||
}
|
||||
|
||||
std::string argKindToString(ArgKind kind)
|
||||
|
||||
@@ -87,15 +87,7 @@ install(FILES ${OPENCV_JAR_FILE} OPTIONAL DESTINATION ${OPENCV_JAR_INSTALL_PATH}
|
||||
|
||||
add_dependencies(${the_module} ${the_module}_jar)
|
||||
|
||||
# Javadoc generation can be disabled independently of BUILD_DOCS so the C++
|
||||
# Doxygen / Sphinx docs still build when the Java bindings carry Doxygen-style
|
||||
# tags (e.g. @retval, @remarks) that JDK doclint rejects as fatal errors.
|
||||
# Default ON for parity with upstream; when OFF the `doxygen_javadoc` target is
|
||||
# never created and doc/CMakeLists.txt's `if(TARGET doxygen_javadoc)` guard
|
||||
# drops it from the doxygen_cpp dependency chain automatically.
|
||||
option(BUILD_JAVADOC "Generate Javadoc as part of the documentation build" ON)
|
||||
|
||||
if(BUILD_DOCS AND BUILD_JAVADOC)
|
||||
if(BUILD_DOCS)
|
||||
if(OPENCV_JAVA_SDK_BUILD_TYPE STREQUAL "ANT")
|
||||
add_custom_command(OUTPUT "${OPENCV_DEPHELPER}/${the_module}doc"
|
||||
COMMAND ${ANT_EXECUTABLE} -noinput -k javadoc
|
||||
@@ -142,4 +134,4 @@ if(BUILD_DOCS AND BUILD_JAVADOC)
|
||||
add_dependencies(opencv_docs ${the_module}doc)
|
||||
else()
|
||||
unset(CMAKE_DOXYGEN_JAVADOC_NODE CACHE)
|
||||
endif()
|
||||
endif()
|
||||
@@ -10,7 +10,7 @@ There are different preprocessing parameters such mean subtraction or scale fact
|
||||
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/caffemodel --config /path/to/prototxt
|
||||
python object_detection.py opencv_fd --model /path/to/model.onnx
|
||||
```
|
||||
|
||||
Check `-h` option to know which values are used by default:
|
||||
@@ -34,14 +34,14 @@ You also can use the script to download necessary files from your code. Assume y
|
||||
```python
|
||||
from download_models import downloadFile
|
||||
|
||||
filepath1 = downloadFile("https://drive.google.com/uc?export=download&id=0B3gersZ2cHIxRm5PMWRoTkdHdHc", None, filename="MobileNetSSD_deploy.caffemodel", save_dir="save_dir_1")
|
||||
filepath2 = downloadFile("https://drive.google.com/uc?export=download&id=0B3gersZ2cHIxRm5PMWRoTkdHdHc", "994d30a8afaa9e754d17d2373b2d62a7dfbaaf7a", filename="MobileNetSSD_deploy.caffemodel")
|
||||
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 **MobileNetSSD_deploy.caffemodel** file:
|
||||
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
|
||||
|
||||
@@ -165,10 +165,10 @@ std::string genPreprocArguments(const std::string& modelName, const std::string&
|
||||
{
|
||||
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), .weights (Darknet), .bin (OpenVINO).",
|
||||
".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), .cfg (Darknet), .xml (OpenVINO).",
|
||||
"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) +
|
||||
|
||||
@@ -76,10 +76,10 @@ def add_preproc_args(zoo, parser, sample, alias=None, prefix=""):
|
||||
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), .weights (Darknet), .bin (OpenVINO)', alias=alias)
|
||||
'.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), .cfg (Darknet), .xml (OpenVINO)', alias=alias)
|
||||
'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)
|
||||
|
||||
+16
-53
@@ -93,83 +93,46 @@ yolov5l:
|
||||
postprocessing: "yolov5"
|
||||
sample: "object_detection"
|
||||
|
||||
# YOLO4 object detection family from Darknet (https://github.com/AlexeyAB/darknet)
|
||||
# YOLO object detection family from Darknet (https://pjreddie.com/darknet/yolo/)
|
||||
# Might be used for all YOLOv2, TinyYolov2, YOLOv3, YOLOv4 and TinyYolov4
|
||||
yolov4:
|
||||
load_info:
|
||||
url: "https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v3_optimal/yolov4.weights"
|
||||
sha1: "0143deb6c46fcc7f74dd35bf3c14edc3784e99ee"
|
||||
model: "yolov4.weights"
|
||||
config_load_info:
|
||||
url: "https://github.com/opencv/opencv_extra/raw/refs/heads/4.x/testdata/dnn/yolov4.cfg"
|
||||
sha1: "ed0aeace88527af7524c3baf66ca44fbf049b878"
|
||||
config: "yolov4.cfg"
|
||||
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: 416
|
||||
height: 416
|
||||
width: 608
|
||||
height: 608
|
||||
rgb: true
|
||||
labels: "object_detection_classes_yolo.txt"
|
||||
background_label_id: 0
|
||||
postprocessing: "darknet"
|
||||
postprocessing: "yolov4"
|
||||
sample: "object_detection"
|
||||
|
||||
yolov4-tiny:
|
||||
load_info:
|
||||
url: "https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4-tiny.weights"
|
||||
sha1: "451caaab22fb9831aa1a5ee9b5ba74a35ffa5dcb"
|
||||
model: "yolov4-tiny.weights"
|
||||
config_load_info:
|
||||
url: "https://github.com/opencv/opencv_extra/raw/refs/heads/4.x/testdata/dnn/yolov4-tiny-2020-12.cfg"
|
||||
sha1: "b161c2b0984b0c3b466c04b0d6cb3e52f06d93dd"
|
||||
config: "yolov4-tiny-2020-12.cfg"
|
||||
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"
|
||||
background_label_id: 0
|
||||
postprocessing: "darknet"
|
||||
postprocessing: "yolov4"
|
||||
sample: "object_detection"
|
||||
|
||||
yolov3:
|
||||
load_info:
|
||||
url: "https://pjreddie.com/media/files/yolov3.weights"
|
||||
sha1: "520878f12e97cf820529daea502acca380f1cb8e"
|
||||
model: "yolov3.weights"
|
||||
config_load_info:
|
||||
url: "https://github.com/opencv/opencv_extra/raw/refs/heads/4.x/testdata/dnn/yolov3.cfg"
|
||||
sha1: "caaf16a895b7bae3cd5c042199d1df0269f3dce6"
|
||||
config: "yolov3.cfg"
|
||||
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: 416
|
||||
height: 416
|
||||
width: 640
|
||||
height: 640
|
||||
rgb: true
|
||||
labels: "object_detection_classes_yolo.txt"
|
||||
background_label_id: 0
|
||||
postprocessing: "darknet"
|
||||
sample: "object_detection"
|
||||
|
||||
tiny-yolo-voc:
|
||||
load_info:
|
||||
url: "https://pjreddie.com/media/files/yolov2-tiny-voc.weights"
|
||||
sha1: "24b4bd049fc4fa5f5e95f684a8967e65c625dff9"
|
||||
model: "tiny-yolo-voc.weights"
|
||||
config_load_info:
|
||||
url: "https://github.com/opencv/opencv_extra/raw/refs/heads/4.x/testdata/dnn/tiny-yolo-voc.cfg"
|
||||
sha1: "d26e2408ce4e20136278411760ba904d744fe5b5"
|
||||
config: "tiny-yolo-voc.cfg"
|
||||
mean: [0, 0, 0]
|
||||
scale: 0.00392
|
||||
width: 416
|
||||
height: 416
|
||||
rgb: true
|
||||
labels: "object_detection_classes_pascal_voc.txt"
|
||||
background_label_id: 0
|
||||
postprocessing: "darknet"
|
||||
postprocessing: "yolov4"
|
||||
sample: "object_detection"
|
||||
|
||||
# Caffe implementation of SSD model from https://github.com/PINTO0309/MobileNet-SSD-RealSense
|
||||
|
||||
@@ -77,7 +77,7 @@ string modelName, framework;
|
||||
|
||||
static void preprocess(const Mat& frame, Net& net, Size inpSize);
|
||||
|
||||
static void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, int backend, vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, const string postprocessing);
|
||||
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);
|
||||
|
||||
@@ -334,7 +334,7 @@ int main(int argc, char** argv)
|
||||
classIds.clear();
|
||||
confidences.clear();
|
||||
boxes.clear();
|
||||
postprocess(frame, outs, net, backend, classIds, confidences, boxes, postprocessing);
|
||||
postprocess(frame, outs, net, classIds, confidences, boxes, postprocessing);
|
||||
|
||||
drawPred(classIds, confidences, boxes, frame, sans, stdSize, stdWeight, stdImgSize, stdThickness);
|
||||
|
||||
@@ -383,7 +383,7 @@ int main(int argc, char** argv)
|
||||
confidences.clear();
|
||||
boxes.clear();
|
||||
|
||||
postprocess(frame, outs, net, backend, classIds, confidences, boxes, postprocessing);
|
||||
postprocess(frame, outs, net, classIds, confidences, boxes, postprocessing);
|
||||
|
||||
drawPred(classIds, confidences, boxes, frame, sans, stdSize, stdWeight, stdImgSize, stdThickness);
|
||||
|
||||
@@ -404,10 +404,7 @@ void preprocess(const Mat& frame, Net& net, Size inpSize)
|
||||
|
||||
// Prepare the blob from the image
|
||||
Mat inp;
|
||||
if(framework == "weights"){ // checks whether model is darknet
|
||||
blobFromImage(frame, inp, scale, size, meanv, swapRB, false, CV_32F);
|
||||
}
|
||||
else{
|
||||
{
|
||||
//![preprocess_call]
|
||||
Image2BlobParams imgParams(
|
||||
Scalar::all(scale),
|
||||
@@ -513,7 +510,7 @@ void yoloPostProcessing(
|
||||
}
|
||||
}
|
||||
|
||||
void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, int backend, vector<int>& classIds, vector<float>& confidences, vector<Rect>& boxes, const string postprocessing)
|
||||
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")
|
||||
@@ -552,35 +549,66 @@ void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, int backend, vec
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (postprocessing == "darknet")
|
||||
else if (postprocessing == "yolov4")
|
||||
{
|
||||
for (size_t i = 0; i < outs.size(); ++i)
|
||||
// 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)
|
||||
{
|
||||
// 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]
|
||||
float* data = (float*)outs[i].data;
|
||||
for (int j = 0; j < outs[i].rows; ++j, data += outs[i].cols)
|
||||
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)
|
||||
{
|
||||
Mat scores = outs[i].row(j).colRange(5, outs[i].cols);
|
||||
Point classIdPoint;
|
||||
double confidence;
|
||||
minMaxLoc(scores, 0, &confidence, 0, &classIdPoint);
|
||||
if (confidence > confThreshold)
|
||||
float score = scoresPtr[j];
|
||||
if (score > confThreshold)
|
||||
{
|
||||
int centerX = (int)(data[0] * frame.cols);
|
||||
int centerY = (int)(data[1] * frame.rows);
|
||||
int width = (int)(data[2] * frame.cols);
|
||||
int height = (int)(data[3] * frame.rows);
|
||||
int left = centerX - width / 2;
|
||||
int top = centerY - height / 2;
|
||||
|
||||
classIds.push_back(classIdPoint.x);
|
||||
confidences.push_back((float)confidence);
|
||||
boxes.push_back(Rect(left, top, width, height));
|
||||
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")
|
||||
{
|
||||
@@ -620,7 +648,7 @@ void postprocess(Mat& frame, const vector<Mat>& outs, Net& net, int backend, vec
|
||||
|
||||
// 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 || (postprocessing == "darknet" && backend != DNN_BACKEND_OPENCV))
|
||||
if (outLayers.size() > 1)
|
||||
{
|
||||
map<int, vector<size_t> > class2indices;
|
||||
for (size_t i = 0; i < classIds.size(); i++)
|
||||
|
||||
@@ -158,27 +158,44 @@ def postprocess(frame, outs):
|
||||
confidences.append(float(confidence))
|
||||
boxes.append([left, top, width, height])
|
||||
|
||||
elif args.postprocessing == 'darknet':
|
||||
box_scale_w = frameWidth
|
||||
box_scale_h = frameHeight
|
||||
|
||||
for out in outs:
|
||||
for detection in out:
|
||||
scores = detection[4:]
|
||||
if args.background_label_id >= 0:
|
||||
scores = np.delete(scores, args.background_label_id)
|
||||
classId = np.argmax(scores)
|
||||
confidence = scores[classId]
|
||||
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))
|
||||
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
|
||||
@@ -219,7 +236,7 @@ def postprocess(frame, outs):
|
||||
|
||||
# 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 == 'darknet' or args.postprocessing == 'yolov8' or args.postprocessing == 'yolov5') and args.backend != cv.dnn.DNN_BACKEND_OPENCV:
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user