FEATURED · 精选文章

OpenCV.js dnn 模块实战:语义分割从图像预处理到逐像素 argmax 可视化的完整实现

发布时间 / 2026/9/7 8:51:53
来源 / 创域科博编辑部
栏目 / 资讯中心
OpenCV.js dnn 模块实战:语义分割从图像预处理到逐像素 argmax 可视化的完整实现 OpenCV.js dnn 模块实战语义分割从图像预处理到逐像素 argmax 可视化的完整实现【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv本篇基于 OpenCV 官方 JavaScript 教程js_semantic_segmentation讲解如何用 OpenCV.js 的 dnn 模块在浏览器中完成语义分割推理。文章完整还原交互示例中“预处理 → 模型加载 → 前向推理 → 逐像素 argmax 上色”的四个环节并结合仓库中的示例源码、模型参数配置与 dnn 的 JS 绑定清单逐段剖析读完后你可以复制示例中的全部代码片段在任意浏览器页面中跑通 DeepLabv3 等语义分割模型。1. 教程目标与示例应用结构官方教程文档 js_semantic_segmentation.markdown 的目标非常直接In this tutorial you will learn how to use OpenCV.js dnn module for semantic segmentation。文档本身通过 iframe 内嵌了一个可交互的网页应用 js_semantic_segmentation.html这也是本教程真正的“载体”双 Canvas 布局左侧canvasInput400×400展示输入图像右侧canvasOutput展示分割结果三个文件上传入口canvasInput用于更换输入图片modelFile用于上传推理模型必需configFile用于上传网络结构配置如 TensorFlow 的 pbtxt按需五个可编辑代码段textarea分别对应推理参数、主流程main()、预处理函数getBlobFromImage、模型加载函数loadModel、后处理函数generateColors与argmax点击Try it按钮后按顺序执行这些代码段完成一次推理。这种“代码即界面”的设计意味着教程中所有代码段都是可独立修改、独立执行的方便读者替换为自己的模型参数做实验。2. 支持的模型与关键参数示例页面在加载时会抓取模型信息 JSON js_semantic_segmentation_model_info.json并由 js_dnn_example_helper.js 中的drawInfoTable函数渲染成附录表格。当前仓库中收录的参考模型如下字段值含义框架tensorflow模型来自 TensorFlow 导出modeldeeplabv3DeepLabv3 语义分割网络inputSize513, 513网络期望的输入尺寸宽, 高mean127.5, 127.5, 127.5三通道均值用于归一化前的减均值std0.007843标准差倒数即1/127.5缩放因子swapRBfalse不交换 R/B 通道输入已是 BGR 顺序modelUrlGoogle Drive 下载链接示例提供的权重文件来源这些参数与页面中第一个代码段一一对应inputSize [513, 513]; mean [127.5, 127.5, 127.5]; std 0.007843; swapRB false;页面明确提示如果你上传的是其他模型必须按该模型的文档修改这段参数后再点击 Try it。以std 0.007843为例它与mean 127.5配合恰好完成(pixel - 127.5) / 127.5的经典归一化把 8-bit 像素映射到 [-1, 1] 区间这正是许多 TensorFlow 语义分割导出模型的输入约定。3. 预处理从 Canvas 图像到 Blob 输入第一个环节是把 Canvas 上的图片转成网络输入。示例中main()调用了getBlobFromImageconst input getBlobFromImage(inputSize, mean, std, swapRB, canvasInput);该函数定义在公共辅助脚本 js_dnn_example_helper.js 中页面启动时会通过utils.executeCode(codeEditor2)把它的源码注入第二个代码段保证用户在编辑器里能看到完整实现getBlobFromImage function(inputSize, mean, std, swapRB, image) { let mat; if (typeof(image) string) { mat cv.imread(image); } else { mat image; } let matC3 new cv.Mat(mat.matSize[0], mat.matSize[1], cv.CV_8UC3); cv.cvtColor(mat, matC3, cv.COLOR_RGBA2BGR); let input cv.blobFromImage(matC3, std, new cv.Size(inputSize[0], inputSize[1]), new cv.Scalar(mean[0], mean[1], mean[2]), swapRB); matC3.delete(); return input; }结合 js_semantic_segmentation.html 中的注释Get blob from image as input for net, and standardize it with mean and std这段代码做了三件事格式统一Canvas 读出的像素是 RGBA先经cv.cvtColor转为CV_8UC3的 BGR 矩阵保证与mean三个分量的通道顺序一致一次完成缩放 归一化 通道交换cv.blobFromImage(matC3, std, size, meanScalar, swapRB)内部按blob (pixel - mean) * std逐像素计算同时缩放到inputSize指定的 513×513当swapRB为true时再对首尾通道做交换。这里传入swapRB false即沿用 BGR 顺序内存管理临时矩阵matC3用完即delete()。blobFromImage与readNet均属于 dnn 模块暴露给 JS 的绑定。可以从源码结构看dnn 的 JS 绑定字典 gen_dict.json 在空命名空间下声明了readNetFromTensorflow、readNetFromTorch、readNetFromONNX、readNetFromTFLite、readNet、blobFromImage等函数说明示例用到的 API 在构建 OpenCV.js 时只要启用 dnn 模块即可获得。4. 模型加载把本地文件写入 Emscripten 虚拟文件系统浏览器中的文件不能直接用路径读取因此loadModel借助 Emscripten 的虚拟文件系统FS完成桥接。该函数同样来自 js_dnn_example_helper.jsloadModel async function(e) { return new Promise((resolve) { let file e.target.files[0]; let path file.name; let reader new FileReader(); reader.readAsArrayBuffer(file); reader.onload function(ev) { if (reader.readyState 2) { let buffer reader.result; let data new Uint8Array(buffer); cv.FS_createDataFile(/, path, data, true, false, false); resolve(path); } } }); }流程是FileReader把用户选择的文件读成ArrayBuffer再用cv.FS_createDataFile(/, path, data, ...)以文件名为路径写入虚拟文件系统根目录随后把该路径回传给页面。HTML 中两个上传入口分别调用它let configPath ; configFile.addEventListener(change, async (e) { configPath await loadModel(e); // status: The config file name is created successfully. }); let modelPath ; modelFile.addEventListener(change, async (e) { modelPath await loadModel(e); configPath ; // 权重更新后清空配置避免配对错误 configFile.value ; });细节值得注意上传modelFile后会把configPath置空并重置configFile的显示提示用户按新模型重新选择配置——这与页面说明“upload inference model (and configFile button if needed)”相呼应。点击 Try it 时若modelPath 页面会直接报错 “Please upload model file by clicking the button first.”不做推理。5. 主推理流程 main()forward 与计时核心推理只有一段紧凑的代码HTML 中第二个代码段main async function() { const input getBlobFromImage(inputSize, mean, std, swapRB, canvasInput); let net cv.readNet(configPath, modelPath); net.setInput(input); const start performance.now(); const result net.forward(); const time performance.now() - start; const colors generateColors(result); const output argmax(result, colors); updateResult(output, time); input.delete(); net.delete(); result.delete(); }各步骤对应关系cv.readNet(configPath, modelPath)同时传入结构与权重两条路径。对 TensorFlow 导出模型configPath为 pbtxt 结构文件、modelPath为.pb权重若模型为单文件格式可只传权重路径另一参数留空。readNet会自动识别格式等价于 C 端cv::dnn::readNet的 JS 绑定net.setInput(input)绑定预处理后的 Blobperformance.now()包裹net.forward()只统计前向推理耗时不含预处理与后处理结果最终显示为Inference time: xx.xx ms推理输出是一个 NCHW 布局的Mat交给后处理的argmax逐像素取类最后统一delete()掉input、net、result避免 Emscripten 堆内存泄漏。结果展示由updateResult完成把后处理得到的分割图cv.resize到输出 Canvas 尺寸400×400后cv.imshow到canvasOutput并输出模型文件名与推理耗时。6. 后处理随机调色板 逐像素 argmax语义分割的输出是每张输入图对应的“每像素类别得分图”NCHW 尺寸为1 × C × H × WC 为类别数DeepLabv3 在 Cityscapes 上常见 19 类Pascal VOC 上常见 21 类具体以模型输出为准。示例用两个函数把得分图变成肉眼可见的色块图HTML 中第五个代码段generateColors function(result) { const numClasses result.matSize[1]; let colors [0,0,0]; while(colors.length numClasses*3){ colors.push(Math.round((Math.random()*255 colors[colors.length-3]) / 2)); } return colors; } argmax function(result, colors) { const C result.matSize[1]; const H result.matSize[2]; const W result.matSize[3]; const resultData result.data32F; const imgSize H*W; let classId []; for (i 0; iimgSize; i) { let id 0; for (j 0; j C; j) { if (resultData[j*imgSizei] resultData[id*imgSizei]) { id j; } } classId.push(colors[id*3]); classId.push(colors[id*31]); classId.push(colors[id*32]); classId.push(255); } output cv.matFromArray(H,W,cv.CV_8UC4,classId); return output; }两段实现的关键点generateColors按类别数 C 生成随机调色板每个新分量取“随机值与 3 个分量前值”的均值colors[colors.length-3]实际是同一个 RGB 组内的前一分量使相邻颜色不至于跳变过大视觉效果更柔和argmax得分图在内存中是“按通道连续”存储的——同一通道内 H×W 个像素连续通道之间有imgSize的步长因此内层循环用resultData[j*imgSizei]遍历 C 个通道来找最大值的下标id像素拼装每个像素写入[R, G, B, 255]四字节最终cv.matFromArray(H, W, cv.CV_8UC4, classId)组装成带透明通道的 8-bit 4 通道Mat正好匹配canvasOutput的CV_8UC4显示格式。7. 在仓库中如何进一步深入本教程是 JS dnn 系列之一同目录下还有图像分类、目标检测、姿态估计、风格迁移等姊妹篇见 js_table_of_contents_dnn.markdown各示例共享同一套辅助函数与页面骨架读懂语义分割示例后迁移到其他任务只需替换main()与后处理部分页面引用的模型参数表由 drawInfoTable 从 JSON 动态生成如果你要为示例新增一个模型只需按现有键名model、inputSize、mean、std、swapRB、modelUrl等在 js_semantic_segmentation_model_info.json 的tensorflow数组中追加一项从源码结构看readNet/blobFromImage等函数在 gen_dict.json 中登记为 dnn 模块 JS 绑定的导出符号这意味着自定义页面直接加载带 dnn 模块的 OpenCV.js 构建产物即可获得与示例相同的能力。8. 适用前提与注意事项运行该示例需要 OpenCV.js 构建包含dnn模块且页面脚本中已完成cv全局对象加载示例通过utils.loadOpenCv(callback)就绪后才启用 Try it 按钮模型文件必须在浏览器端由用户本地上传后写入虚拟文件系统页面本身只负责“读取文件 → 建文件 → 推理”不内置权重inputSize、mean、std、swapRB必须与目标模型的导出约定一致否则结果会是全黑或错乱色块——这是移植其他分割模型如 FCN、ENet 等时最常见的排错点推理输出尺寸受输入尺寸限制DeepLabv3 输入 513×513 时输出约 513×513显示时会被缩放到 400×400 的输出 Canvas属于展示层面的插值不影响 argmax 的类别归属。【免费下载链接】opencvOpen Source Computer Vision Library项目地址: https://gitcode.com/GitHub_Trending/opencv31/opencv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻