Firefox扩展无法遍历全部图片:getImageData()相关问题求助
getImageData相关逻辑则正常运行 问题描述
我正在开发一款Firefox扩展,通过model_startup()函数加载机器学习模型的过程一切正常。但在后续执行blockk()函数时,尝试使用document.getElementsByTagName('img')获取页面所有图片,并借助已加载的模型对图片进行预测。目前遇到的问题是:该函数仅能处理页面开头的若干张图片后就停止;若移除代码中的const rightSizeImageData = inferenceCtx.getImageData(0, 0, 64, 64)行及相关逻辑,则可正常遍历全部图片。我是JavaScript新手,恳请帮忙排查该问题的原因。
相关代码如下:
console.log('STARTING UP') const MODEL_PATH = 'mdl/model.json' const IMAGE_SIZE = 64; let model; async function model_startup() { console.log('Launching TF.js!'); tf.ENV.set('WEBGL_PACK',false); await tf.ready(); console.log('TensorflowJS backend is: '+tf.getBackend()); let url = browser.runtime.getURL(MODEL_PATH); console.log('Loading model... '+url); try { model = await tf.loadLayersModel(url); } catch(e) { console.log('Failed to load model! '+e); } console.log('Model: ' + model); console.log('Warming up...'); let dummy_data = tf.zeros([1, IMAGE_SIZE, IMAGE_SIZE, 3]); // dummy_data.print(); let warmup_result = model.predict(dummy_data); console.log("finding type : $$$$$$$$ " + typeof warmup_result) warmup_result.print(); warmup_result.dispose(); console.log('Ready to go!'); // blockk(); }; model_startup().then(blockk); function blockk() { const x = document.getElementsByTagName('img'); console.log("wn ############################## " + x.length); let inferenceCanvas = document.createElement('canvas'); inferenceCanvas.width = 64; inferenceCanvas.height = 64; let inferenceCtx = inferenceCanvas.getContext('2d', { alpha: false}); inferenceCtx.imageSmoothingEnabled = true; for(var i = 0; i < x.length; i++) { console.log('pixel kmvsl lsvn'); let img = x[i]; inferenceCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 64, 64); const rightSizeImageData = inferenceCtx.getImageData(0, 0, 64, 64); //write promise. It may work. const rightSizeImageDataTF = tf.browser.fromPixels(rightSizeImageData); const floatImg = rightSizeImageDataTF.toFloat(); console.log('predicting...'); let scaled = floatImg.div(tf.scalar(255)); let batched = tf.stack([scaled]); let result = model.predict(batched); const val = result.dataSync()[0]; console.log(val); if (val > 0.4) { console.log("blocking" + i); let h = img.height let w = img.width pth = img.src x[i].setAttribute("src", "https://indianonlineseller.com/wp-content/uploads/2017/05/blocked-listing.png"); // x[i].setAttribute("onclick", ` this.src = '${pth}' `); x[i].setAttribute("data-src", "https://indianonlineseller.com/wp-content/uploads/2017/05/blocked-listing.png"); x[i].height = h; x[i].width = w; console.log('blocked'); } console.log("co @@@@@@@@@@@@@@@@@@@@@@@@@@@ " + x.length) } };
问题原因分析
我帮你排查了两个核心问题,这应该就是导致遍历中途停止的原因:
动态HTMLCollection的遍历冲突
document.getElementsByTagName('img')返回的是动态HTMLCollection,它会实时同步DOM的变化。当你在循环中修改img的src属性时,这个集合的内容和长度可能会发生变化(比如图片重新加载触发DOM状态更新)。而你用var i配合实时变化的x.length作为循环条件,就会导致循环提前终止。TensorFlow.js张量未释放导致内存泄漏
预测逻辑中创建的多个张量(rightSizeImageDataTF、floatImg、scaled、batched、result)没有被及时释放,随着循环进行内存占用持续飙升,浏览器为了避免性能崩溃,会强制终止脚本执行——这也是移除预测逻辑后遍历能正常完成的原因。
解决方案
针对这两个问题,我们可以做以下修改:
1. 将动态集合转为静态数组
把document.getElementsByTagName('img')的结果转为静态数组,这样遍历过程不会受DOM变化影响:
const x = Array.from(document.getElementsByTagName('img')); // 或者用展开运算符:const x = [...document.getElementsByTagName('img')];
2. 用tf.tidy()自动清理张量
TensorFlow.js提供了tf.tidy()函数,能自动清理函数内部创建的所有临时张量,只保留你返回的结果,完美解决内存泄漏问题。
3. 改用let声明循环变量(推荐)
把循环中的var i改成let i,避免var变量提升带来的潜在作用域问题。
修改后的完整代码
console.log('STARTING UP') const MODEL_PATH = 'mdl/model.json' const IMAGE_SIZE = 64; let model; async function model_startup() { console.log('Launching TF.js!'); tf.ENV.set('WEBGL_PACK',false); await tf.ready(); console.log('TensorflowJS backend is: '+tf.getBackend()); let url = browser.runtime.getURL(MODEL_PATH); console.log('Loading model... '+url); try { model = await tf.loadLayersModel(url); } catch(e) { console.log('Failed to load model! '+e); } console.log('Model: ' + model); console.log('Warming up...'); let dummy_data = tf.zeros([1, IMAGE_SIZE, IMAGE_SIZE, 3]); let warmup_result = model.predict(dummy_data); console.log("finding type : $$$$$$$$ " + typeof warmup_result) warmup_result.print(); // 释放预热用的张量 warmup_result.dispose(); dummy_data.dispose(); console.log('Ready to go!'); }; model_startup().then(blockk); function blockk() { // 转为静态数组,避免动态集合的遍历问题 const x = Array.from(document.getElementsByTagName('img')); console.log("Total images to process: " + x.length); let inferenceCanvas = document.createElement('canvas'); inferenceCanvas.width = 64; inferenceCanvas.height = 64; let inferenceCtx = inferenceCanvas.getContext('2d', { alpha: false}); inferenceCtx.imageSmoothingEnabled = true; // 用let声明循环变量,避免作用域问题 for(let i = 0; i < x.length; i++) { console.log('Processing image index: ', i); let img = x[i]; // 先检查图片是否加载完成,避免drawImage失败 if (!img.complete || img.naturalWidth === 0) { console.log('Image not loaded yet, skipping: ', img.src); continue; } inferenceCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 64, 64); const rightSizeImageData = inferenceCtx.getImageData(0, 0, 64, 64); // 使用tf.tidy自动清理所有临时张量 const val = tf.tidy(() => { const rightSizeImageDataTF = tf.browser.fromPixels(rightSizeImageData); const floatImg = rightSizeImageDataTF.toFloat(); const scaled = floatImg.div(tf.scalar(255)); const batched = tf.stack([scaled]); const result = model.predict(batched); return result.dataSync()[0]; }); console.log('Prediction value: ', val); if (val > 0.4) { console.log("Blocking image index: ", i); const h = img.height; const w = img.width; const pth = img.src; img.setAttribute("src", "https://indianonlineseller.com/wp-content/uploads/2017/05/blocked-listing.png"); img.setAttribute("data-src", pth); // 保存原地址,方便后续恢复 img.height = h; img.width = w; console.log('Blocked image: ', pth); } console.log("Remaining images: ", x.length - i - 1); } };
额外提示
- 我添加了图片加载状态检查,避免处理未完全加载的图片导致
drawImage报错; - 把原图片地址存在
data-src而不是覆盖,后续如果需要恢复图片会更方便。
内容的提问来源于stack exchange,提问作者Pankaj Katkar

