diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 1b478cb6fd..e80bb717c1 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -251,12 +251,6 @@ if(DOXYGEN_FOUND) list(APPEND js_assets "${OPENCV_JS_LOCATION}") endif() - # copy haar cascade files - # set(haar_cascade_files "") - # set(data_harrcascades_path "${OpenCV_SOURCE_DIR}/data/haarcascades/") - # list(APPEND js_tutorials_assets_deps "${data_harrcascades_path}/haarcascade_frontalface_default.xml" "${data_harrcascades_path}/haarcascade_eye.xml") - # list(APPEND js_assets "${data_harrcascades_path}/haarcascade_frontalface_default.xml" "${data_harrcascades_path}/haarcascade_eye.xml") - foreach(f ${js_assets}) get_filename_component(fname "${f}" NAME) add_custom_command(OUTPUT "${opencv_tutorial_html_dir}/${fname}" diff --git a/doc/js_tutorials/js_assets/js_face_detection.html b/doc/js_tutorials/js_assets/js_face_detection.html deleted file mode 100644 index 3925458ecf..0000000000 --- a/doc/js_tutorials/js_assets/js_face_detection.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - -Face Detection Example - - - -

Face Detection Example

-

- <canvas> elements named canvasInput and canvasOutput have been prepared.
- Click Try it button to see the result. You can choose another image.
- You can change the code in the <textarea> to investigate more. -

-
-
- -

-
-
- - - - - - - - - -
- - - -
-
canvasInput
-
-
canvasOutput
-
-
- - - - - diff --git a/doc/js_tutorials/js_assets/js_face_detection_camera.html b/doc/js_tutorials/js_assets/js_face_detection_camera.html deleted file mode 100644 index f839cd7470..0000000000 --- a/doc/js_tutorials/js_assets/js_face_detection_camera.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - -Face Detection Camera Example - - - -

Face Detection Camera Example

-

- Click Start/Stop button to start or stop the camera capture.
- The videoInput is a <video> element used as face detector input. - The canvasOutput is a <canvas> element used as face detector output.
- The code of <textarea> will be executed when video is started. - You can modify the code to investigate more. -

-
-
- -
-

-
- - - - - - - - - - - - - -
- - - -
-
videoInput
-
-
canvasOutput
-
-
- - - - - - diff --git a/doc/js_tutorials/js_assets/lena_yunet.jpg b/doc/js_tutorials/js_assets/lena_yunet.jpg new file mode 100644 index 0000000000..cd258b4568 Binary files /dev/null and b/doc/js_tutorials/js_assets/lena_yunet.jpg differ diff --git a/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown b/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown index 46cab5aa33..f2d7a16a79 100644 --- a/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown +++ b/doc/js_tutorials/js_setup/js_nodejs/js_nodejs.markdown @@ -227,52 +227,128 @@ In the browser, this filesystem is emulated in memory while in Node.js there's a ### The example ### -The following is an adaptation of @ref tutorial_js_face_detection. - @code{.js} const { Canvas, createCanvas, Image, ImageData, loadImage } = require('canvas'); const { JSDOM } = require('jsdom'); const { writeFileSync, existsSync, mkdirSync } = require('fs'); +const https = require('https'); (async () => { - await loadOpenCV(); +const createFileFromUrl = function (path, url, maxRedirects = 10) { + console.log('Downloading ' + url + '...'); + return new Promise((resolve, reject) => { + const download = (url, redirectCount) => { + if (redirectCount > maxRedirects) { + reject(new Error('Too many redirects')); + } else { + let connection = https.get(url, (response) => { + if (response.statusCode === 200) { + let data = []; + response.on('data', (chunk) => { + data.push(chunk); + }); - const image = await loadImage('lena.jpg'); - const src = cv.imread(image); - let gray = new cv.Mat(); - cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); - let faces = new cv.RectVector(); - let eyes = new cv.RectVector(); - let faceCascade = new cv.CascadeClassifier(); - let eyeCascade = new cv.CascadeClassifier(); + response.on('end', () => { + try { + writeFileSync(path, Buffer.concat(data)); + resolve(); + } catch (err) { + reject(new Error('Failed to write file ' + path)); + } + }); + } else if (response.statusCode === 302 || response.statusCode === 301) { + connection.abort(); + download(response.headers.location, redirectCount + 1); + } else { + reject(new Error('Failed to load ' + url + ' status: ' + response.statusCode)); + } + }).on('error', (err) => { + reject(new Error('Network Error: ' + err.message)); + }); + } + }; + download(url, 0); + }); +}; - // Load pre-trained classifier files. Notice how we reference local files using relative paths just - // like we normally would do - faceCascade.load('./haarcascade_frontalface_default.xml'); - eyeCascade.load('./haarcascade_eye.xml'); +if (!existsSync('./face_detection_yunet_2023mar.onnx')) { + await createFileFromUrl('./face_detection_yunet_2023mar.onnx', 'https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx') +} - let mSize = new cv.Size(0, 0); - faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, mSize, mSize); - for (let i = 0; i < faces.size(); ++i) { - let roiGray = gray.roi(faces.get(i)); - let roiSrc = src.roi(faces.get(i)); - let point1 = new cv.Point(faces.get(i).x, faces.get(i).y); - let point2 = new cv.Point(faces.get(i).x + faces.get(i).width, faces.get(i).y + faces.get(i).height); - cv.rectangle(src, point1, point2, [255, 0, 0, 255]); - eyeCascade.detectMultiScale(roiGray, eyes); - for (let j = 0; j < eyes.size(); ++j) { - let point1 = new cv.Point(eyes.get(j).x, eyes.get(j).y); - let point2 = new cv.Point(eyes.get(j).x + eyes.get(j).width, eyes.get(j).y + eyes.get(j).height); - cv.rectangle(roiSrc, point1, point2, [0, 0, 255, 255]); - } - roiGray.delete(); - roiSrc.delete(); +if (!existsSync('./opencv.js')) { + await createFileFromUrl('./opencv.js', 'https://docs.opencv.org/5.x/opencv.js') +} + +if (!existsSync('./lena.jpg')) { + await createFileFromUrl('./lena.jpg', 'https://docs.opencv.org/5.x/lena.jpg') +} + +await loadOpenCV(); + +const image = await loadImage('./lena.jpg'); +const src = cv.imread(image); +let srcBGR = new cv.Mat(); +cv.cvtColor(src, srcBGR, cv.COLOR_RGBA2BGR); + +// Load the deep learning model file. Notice how we reference local files using relative paths just +// like we normally would do +let netDet = new cv.FaceDetectorYN("./face_detection_yunet_2023mar.onnx", "", new cv.Size(320, 320), 0.9, 0.3, 5000); +netDet.setInputSize(new cv.Size(src.cols, src.rows)); +let out = new cv.Mat(); +netDet.detect(srcBGR, out); + +let faces = []; +for (let i = 0, n = out.data32F.length; i < n; i += 15) { + let left = out.data32F[i]; + let top = out.data32F[i + 1]; + let right = (out.data32F[i] + out.data32F[i + 2]); + let bottom = (out.data32F[i + 1] + out.data32F[i + 3]); + left = Math.min(Math.max(0, left), src.cols - 1); + top = Math.min(Math.max(0, top), src.rows - 1); + right = Math.min(Math.max(0, right), src.cols - 1); + bottom = Math.min(Math.max(0, bottom), src.rows - 1); + + if (left < right && top < bottom) { + faces.push({ + x: left, + y: top, + width: right - left, + height: bottom - top, + x1: out.data32F[i + 4] < 0 || out.data32F[i + 4] > src.cols - 1 ? -1 : out.data32F[i + 4], + y1: out.data32F[i + 5] < 0 || out.data32F[i + 5] > src.rows - 1 ? -1 : out.data32F[i + 5], + x2: out.data32F[i + 6] < 0 || out.data32F[i + 6] > src.cols - 1 ? -1 : out.data32F[i + 6], + y2: out.data32F[i + 7] < 0 || out.data32F[i + 7] > src.rows - 1 ? -1 : out.data32F[i + 7], + x3: out.data32F[i + 8] < 0 || out.data32F[i + 8] > src.cols - 1 ? -1 : out.data32F[i + 8], + y3: out.data32F[i + 9] < 0 || out.data32F[i + 9] > src.rows - 1 ? -1 : out.data32F[i + 9], + x4: out.data32F[i + 10] < 0 || out.data32F[i + 10] > src.cols - 1 ? -1 : out.data32F[i + 10], + y4: out.data32F[i + 11] < 0 || out.data32F[i + 11] > src.rows - 1 ? -1 : out.data32F[i + 11], + x5: out.data32F[i + 12] < 0 || out.data32F[i + 12] > src.cols - 1 ? -1 : out.data32F[i + 12], + y5: out.data32F[i + 13] < 0 || out.data32F[i + 13] > src.rows - 1 ? -1 : out.data32F[i + 13], + confidence: out.data32F[i + 14] + }) } +} +out.delete(); - const canvas = createCanvas(image.width, image.height); - cv.imshow(canvas, src); - writeFileSync('output3.jpg', canvas.toBuffer('image/jpeg')); - src.delete(); gray.delete(); faceCascade.delete(); eyeCascade.delete(); faces.delete(); eyes.delete() +faces.forEach(function(rect) { + cv.rectangle(src, {x: rect.x, y: rect.y}, {x: rect.x + rect.width, y: rect.y + rect.height}, [0, 255, 0, 255]); + if(rect.x1>0 && rect.y1>0) + cv.circle(src, {x: rect.x1, y: rect.y1}, 2, [255, 0, 0, 255], 2) + if(rect.x2>0 && rect.y2>0) + cv.circle(src, {x: rect.x2, y: rect.y2}, 2, [0, 0, 255, 255], 2) + if(rect.x3>0 && rect.y3>0) + cv.circle(src, {x: rect.x3, y: rect.y3}, 2, [0, 255, 0, 255], 2) + if(rect.x4>0 && rect.y4>0) + cv.circle(src, {x: rect.x4, y: rect.y4}, 2, [255, 0, 255, 255], 2) + if(rect.x5>0 && rect.y5>0) + cv.circle(src, {x: rect.x5, y: rect.y5}, 2, [0, 255, 255, 255], 2) +}); + +const canvas = createCanvas(image.width, image.height); +cv.imshow(canvas, src); +writeFileSync('output3.jpg', canvas.toBuffer('image/jpeg')); +console.log('The result is saved.') +src.delete(); srcBGR.delete(); })(); /** @@ -287,7 +363,7 @@ const { writeFileSync, existsSync, mkdirSync } = require('fs'); */ function loadOpenCV(rootDir = '/work', localRootDir = process.cwd()) { if(global.Module && global.Module.onRuntimeInitialized && global.cv && global.cv.imread) { - return Promise.resolve() + Promise.resolve() } return new Promise(resolve => { installDOM() @@ -333,13 +409,12 @@ function installDOM(){ ### Execute it ### - Save the file as `exampleNodeCanvasData.js`. -- Make sure the files `aarcascade_frontalface_default.xml` and `haarcascade_eye.xml` are present in project's directory. They can be obtained from [OpenCV sources](https://github.com/opencv/opencv/tree/5.x/data/haarcascades). -- Make sure a sample image file `lena.jpg` exists in project's directory. It should display people's faces for this example to make sense. The following image is known to work: +- The files `face_detection_yunet_2023mar.onnx`, `lena.jpg` and `opencv.js` will be downloaded if they not present in project's directory. -![image](js_assets/lena.jpg) - -The following command should generate the file `output3.jpg`: +The following command should generate the file `output3.jpg` look the image below: @code{.bash} node exampleNodeCanvasData.js @endcode + +![image](js_assets/lena_yunet.jpg) diff --git a/modules/js/CMakeLists.txt b/modules/js/CMakeLists.txt index 19f0b19790..d2a08fc61e 100644 --- a/modules/js/CMakeLists.txt +++ b/modules/js/CMakeLists.txt @@ -112,17 +112,6 @@ foreach(f ${test_files}) list(APPEND opencv_test_js_file_deps "${test_dir}/${f}" "${opencv_test_js_bin_dir}/${f}") endforeach() -# copy test data -set(test_data "haarcascade_frontalface_default.xml") -set(test_data_path "${PROJECT_SOURCE_DIR}/../../data/haarcascades/${test_data}") - -add_custom_command(OUTPUT "${opencv_test_js_bin_dir}/${test_data}" - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${test_data_path}" "${opencv_test_js_bin_dir}/${test_data}" - DEPENDS "${test_data_path}" - COMMENT "Copying ${test_data}" - ) -list(APPEND opencv_test_js_file_deps "${test_data_path}" "${opencv_test_js_bin_dir}/${test_data}") - add_custom_target(${PROJECT_NAME}_test DEPENDS ${OCV_JS_PATH} ${opencv_test_js_file_deps}) diff --git a/modules/js/test/tests.html b/modules/js/test/tests.html index b20013ec63..deec57a706 100644 --- a/modules/js/test/tests.html +++ b/modules/js/test/tests.html @@ -43,9 +43,7 @@ // Helper for opencv.js (see below) var Module = { - preRun: [function() { - Module.FS_createPreloadedFile('/', 'haarcascade_frontalface_default.xml', 'haarcascade_frontalface_default.xml', true, false); - }], + preRun: [], postRun: [] , onRuntimeInitialized: function() { console.log("Emscripten runtime is ready, launching QUnit tests...");