1
0
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:
omrope79
2026-06-05 16:48:27 +05:30
committed by GitHub
parent c9e7878a1d
commit 04aee009aa
43 changed files with 3468 additions and 650 deletions
+3
View File
@@ -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)
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1
View File
@@ -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

+135
View File
@@ -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();
}
+29 -10
View File
@@ -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);
+37
View File
@@ -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
View File
@@ -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"},
+37 -3
View File
@@ -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")
+366 -2
View File
@@ -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">&quot;opencv2/core.hpp&quot;</span>` (or &lt;…&gt;)
# The quote characters are HTML-escaped (`&quot;` / `&lt;` / `&gt;`).
# 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>&quot;|&lt;)'
r'(?P<path>[A-Za-z0-9_./+\-]+\.[A-Za-z0-9]+)'
r'(?P<closeq>&quot;|&gt;)'
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("::", "&#58;&#58;")
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)
+165 -41
View File
@@ -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",
File diff suppressed because it is too large Load Diff
+241 -34
View File
@@ -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("::", "&#58;&#58;")
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:
+306 -39
View File
@@ -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 `&quot;`
and `<>` are `&lt;`/`&gt;`)."""
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 `"` → `&quot;`, `<` → `&lt;`,
# `>` → `&gt;`; rewrite the regex once for that form.
def _re_escaped() -> "re.Pattern":
return re.compile(
r'(#include\s*)(&quot;|&lt;)'
r'([A-Za-z0-9_./+\-]+\.[A-Za-z0-9]+)'
r'(&quot;|&gt;)'
)
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
-1
View File
@@ -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);
+1 -4
View File
@@ -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));
}
+1 -2
View File
@@ -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)
+2 -10
View File
@@ -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()
+4 -4
View File
@@ -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
+2 -2
View File
@@ -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) +
+2 -2
View File
@@ -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
View File
@@ -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
+59 -31
View File
@@ -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++)
+38 -21
View File
@@ -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)