TensorFlow.js图像扫描App报错:expandDims参数类型异常
问题
我正在开发一款基于TensorFlow.js的图像扫描App,使用Expo及Expo-Camera组件,模型通过Teachable Machine训练。代码整体运行正常,但执行以下代码行时出现错误:const imageTensor = images.next().value.expandDims(0).div(127.5).sub(1);
错误信息:
error: argument 'x' passed to 'expanddims' must be a tensor or tensorlike, but got 'tensor'
完整代码如下:
import * as tf from '@tensorflow/tfjs'; import { bundleResourceIO, cameraWithTensors, } from '@tensorflow/tfjs-react-native'; import { Camera } from 'expo-camera'; import { StatusBar } from 'expo-status-bar'; import React, { useEffect, useRef, useState } from 'react'; import { Dimensions, StyleSheet, Text, View } from 'react-native'; const TensorCamera = cameraWithTensors(Camera); // The size of camera preview. // // These are only for iOS devices. const CAM_PREVIEW_WIDTH = Dimensions.get('window').width; const CAM_PREVIEW_HEIGHT = CAM_PREVIEW_WIDTH / (9 / 16); // The size of the output tensor (image) from TensorCamera. // // 9/16. const OUTPUT_TENSOR_WIDTH = 224; const OUTPUT_TENSOR_HEIGHT = 480; export default function App() { const [tfReady, setTfReady] = useState(false); const [model, setModel] = useState(); const [isHotdog, setIsHotdog] = useState(null); const rafId = useRef(null); // Make sure tfjs and tfjs-react-native work, especially the tensor camera. useEffect(() => { async function prepare() { rafId.current = null; // Request camera permission. await Camera.requestCameraPermissionsAsync(); // Wait for tfjs to initialize the backend. await tf.ready(); // Load model. const modelJson = require('./model/model.json'); const modelWeights = require('./model/weights.bin'); const model = await tf.loadLayersModel( bundleResourceIO(modelJson, modelWeights) ); setModel(model); //console.log(model) // Ready!! setTfReady(true); } prepare(); }, []); // This will be called when the component in unmounted. useEffect(() => { return () => { if (rafId.current != null && rafId.current !== 0) { cancelAnimationFrame(rafId.current); rafId.current = 0; } }; }, []); // Handler that will be called when TensorCamera is ready. const handleCameraStream = (images, updatePreview, gl) => { console.log('camera ready!'); // Here, we want to get the tensor from each frame (image), and feed the // tensor to the model (which we will train separately). // // We will do this repeatly in a animation loop. const loop = () => { // This might not be necessary, but add it here just in case. if (rafId.current === 0) { return; } // Wrap this inside tf.tidy to release tensor memory automatically. tf.tidy(() => { console.log('here is fine'); const imageTensor = images.next().value.expandDims(0).div(127.5).sub(1); console.log('here'); const f = (OUTPUT_TENSOR_HEIGHT - OUTPUT_TENSOR_WIDTH) / 2 / OUTPUT_TENSOR_HEIGHT; const cropped = tf.image.cropAndResize( // Image tensor. imageTensor, tf.tensor2d([f, 0, 1 - f, 1], [1, 4]), [0], [224, 224] ); // Feed the processed tensor to the model and get result tensor(s). const result = model.predict(cropped); console.log('5'); // Get the actual data (an array in this case) from the result tensor. const logits = result.dataSync(); console.log(logits); // Logits should be the probability of two classes (hot dog, not hot dog). if (logits) { setIsHotdog(logits[0] > logits[1]); } else { setIsHotdog(null); } }); rafId.current = requestAnimationFrame(loop); }; loop(); }; if (!tfReady) { return ( <View style={styles.loadingMsg}> <Text>Loading...</Text> </View> ); } else { return ( <View style={styles.container}> <TensorCamera style={styles.camera} autorender={true} type={Camera.Constants.Type.back} // Output tensor related props. // These decide the shape of output tensor from the camera. resizeWidth={OUTPUT_TENSOR_WIDTH} resizeHeight={OUTPUT_TENSOR_HEIGHT} resizeDepth={3} onReady={handleCameraStream} /> <View style={ isHotdog ? styles.resultContainerHotdog : styles.resultContainerNotHotdog } > <Text style={styles.resultText}> {isHotdog ? 'hotdog' : 'nothotdog'} </Text> </View> </View> ); } } const styles = StyleSheet.create({ container: { position: 'relative', width: CAM_PREVIEW_WIDTH, height: CAM_PREVIEW_HEIGHT, marginTop: Dimensions.get('window').height / 2 - CAM_PREVIEW_HEIGHT / 2, }, // Tensor camera requires z-index. camera: { width: '100%', height: '100%', zIndex: 1, }, loadingMsg: { position: 'absolute', width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', }, resultContainerHotdog: { position: 'absolute', top: 0, left: 0, zIndex: 100, padding: 20, borderRadius: 8, backgroundColor: '#00aa00', }, resultContainerNotHotdog: { position: 'absolute', top: 0, left: 0, zIndex: 100, padding: 20, borderRadius: 8, backgroundColor: '#aa0000', }, resultText: { fontSize: 30, color: 'white', }, });
解决方案
这个错误的根源是images.next().value返回的是WebGL纹理张量,这类张量是TensorFlow.js为了性能优化在GPU上创建的特殊张量,无法直接调用expandDims等普通张量操作方法,需要先转换为兼容的张量类型。
修改步骤
转换张量类型并重构操作
将出错的代码行替换为:const cameraTensor = images.next().value; // 将WebGL张量转换为浮点型普通张量,再执行后续操作 const imageTensor = tf.tidy(() => cameraTensor.toFloat().expandDims(0).div(127.5).sub(1));使用
toFloat()确保张量类型统一,同时将所有张量操作包裹在tf.tidy()中自动管理内存,避免内存泄漏。增加模型存在性检查
在循环开头增加model的存在性判断,避免组件未完全初始化时调用model.predict导致的报错:if (rafId.current === 0 || !model) { return; }
修改后的handleCameraStream核心代码
const handleCameraStream = (images, updatePreview, gl) => { console.log('camera ready!'); const loop = () => { if (rafId.current === 0 || !model) { return; } tf.tidy(() => { console.log('here is fine'); const cameraTensor = images.next().value; const imageTensor = tf.tidy(() => cameraTensor.toFloat().expandDims(0).div(127.5).sub(1)); console.log('here'); const f = (OUTPUT_TENSOR_HEIGHT - OUTPUT_TENSOR_WIDTH) / 2 / OUTPUT_TENSOR_HEIGHT; const cropped = tf.image.cropAndResize( imageTensor, tf.tensor2d([f, 0, 1 - f, 1], [1, 4]), [0], [224, 224] ); const result = model.predict(cropped); console.log('5'); const logits = result.dataSync(); console.log(logits); if (logits) { setIsHotdog(logits[0] > logits[1]); } else { setIsHotdog(null); } }); rafId.current = requestAnimationFrame(loop); }; loop(); };
内容的提问来源于stack exchange,提问作者BIKI KUMAR SAH
相关产品推荐
相关产品推荐

