Tensorflow Lite中输入数据格式(灰度图篇)

0. 引言

首先本项目使用的实力demo来自基于Tensorflow2 Lite在Android手机上实现图像分类的项目,该项目是用于1000个label的分类,而我想借助该项目的Android壳,将推理模型替换为我的7个label的表情识别模型。该项目中的模型input_shape如下:
在这里插入图片描述
可见是RGB格式输入的,但是我的模型格式是如下:
在这里插入图片描述
应当是灰度图作为输入的,于是在输入的格式的变换上上产生了一个难点,我需要将input转为Gray,由此产生了本篇博文。

1. 分析与前期尝试

1.1 Doi原始项目输入处理整理

Doi项目中先是根据model的details获取输入的shape和dtype,创建一个TensorImage对象inputImageBuffer:

int[] imageShape = tflite.getInputTensor(tflite.getInputIndex("conv2d_input")).shape();
DataType imageDataType = tflite.getInputTensor(tflite.getInputIndex("conv2d_input")).dataType();
inputImageBuffer = new TensorImage(imageDataType);      //照输入的类型要求,创建TensorImage对象

然后通过控件获取到图片路径,将图片位图化:

FileInputStream fis = new FileInputStream(image_path);  //根据图片url创建文件输入流对象
Bitmap bitmap = BitmapFactory.decodeStream(fis);   //用于从指定输入流中解析、创建 Bitmap 对象

load进inputImageBuffer:

inputImageBuffer.load(bitmap);     //TensorImage对象加载bitmap图
return imageProcessor.process(inputImageBuffer);  //该函数返回process后的inputImageBuffer

这里的process可以看到,它实现了resize和归一化的操作:

imageProcessor = new ImageProcessor.Builder()
                    .add(new ResizeOp(imageShape[1], imageShape[2], ResizeOp.ResizeMethod.NEAREST_NEIGHBOR))
                    .add(new NormalizeOp(IMAGE_MEAN, IMAGE_STD))
                    .build();

输入的预处理就算完成了,现在进行run

tflite.run(inputImageBuffer.getBuffer(), outputProbabilityBuffer.getBuffer().rewind());

run的输出结果给了outputProbabilityBuffer,这也是我们定义inputImageBuffer时同时定义了outputProbabilityBuffer。

1.2 前期尝试

因为inputImageBuffer的尺寸实在imageProcessor 进行的resize操作,所以自然想到imageProcessor 有没有灰度化的操作,很不幸我似乎没有找到。通过bitmap似乎也没有灰度格式这个说法(注意视觉上的灰度图仍然可能是RGB格式图,而我们这里需要的是真正意义上的灰度图格式),有的是ARGB_xxxx,xxxx表示储存ARGB分类的Byte数。

在寻找新方法时,看到该篇文章DataType error: cannot resolve DataType of…并与作者交流得到指点:tflite模型的input可以是ByteBuffer,再通过研究Android-MNIST项目代码(其model的input也是灰度图,的确是通过ByteBuffer来实现的,不过该项目是通过Kotlin编写)得到完美的解决

2. 利用ByteBuffer实现灰度格式的input

从1中的第2步“然将图片位图化”,得到bitmap开始,后面不再使用inputImageBuffer,而是用ByteBuffer:

FileInputStream fis = new FileInputStream(image_path);  //根据图片url创建文件输入流对象
Bitmap bitmap = BitmapFactory.decodeStream(fis);   //用于从指定输入流中解析、创建 Bitmap 对象

使用ByteBuffer进行预处理:

private ByteBuffer preProcess(Bitmap bitmap) {

        int width = bitmap.getWidth();    //获取原始bitmap的尺寸
        int height = bitmap.getHeight();

        // 设置目标bitmap的大小,即48*48
        int newWidth = 48;
        int newHeight = 48;

		//这里将原始bitmap进行resize到48*48
        bitmap=Bitmap.createScaledBitmap(bitmap, newWidth , newHeight, false);

        //创建了一个ByteBuffer对象inputBuffer ,并对inputBuffer进行初始化
        ByteBuffer inputBuffer = ByteBuffer.allocateDirect(4 * 1 * newWidth * newHeight* 1);
        inputBuffer.order(ByteOrder.nativeOrder());
        inputBuffer.rewind();

		
        // 因为新的bitmap依然是RGB,只是进行了resize,尺寸为48*48*3
        //这里是新建一个48*48*1的数组,用来存放每个像素灰度化的值:
        //  gray_value=weight_1*R+weight_2*G+weight_3*B
        //  且weight_1+weight_2+weight_3=1
        int[] pixels=new int[newWidth * newHeight];
		
		//将bitmap的像素值给pixels举证
        bitmap.getPixels(pixels, 0, newWidth, 0, 0, newWidth, newHeight);

		//便利矩阵存放的RGB像素值并灰度化后存放至inputBuffer
        for(int i =0;i<newWidth * newHeight;i++){
            int pixel = pixels[i];
            float avg = (((pixel >> 16) & 0xFF) * 38 + ((pixel >> 8) & 0xFF) * 75 + (pixel & 0xFF) * 15) >> 7;  
            //RGB三个分量各占1个字节,通过直接对对应的字节进行位操作,实际就等效于十进制中的gray_value=weight_1*R+weight_2*G+weight_3*B灰度换算
            //详见附一
            inputBuffer.putFloat(avg);
            }
        return inputBuffer;
    }

到此我们就完成了input的预处理,现在开始run:

tflite.run(inputBuff, outputProbabilityBuffer.getBuffer().rewind());

成功运行!!!

3. 总结

目前个人对Android和java都不甚了解,对ByteBuffer也不了解,这个留给暑假还是要学一下Android,很多DeepModel还是要尝试在移动端跑一下。
总之:

  • 对于[1 * n * n * 3]格式要求的input就用1中项目原本的处理方式
  • 对于[1 * n * n * 1]格式要求的input就用2中的ByteBuffer处理

附一

float avg = (((pixel >> 16) & 0xFF) * 38 + ((pixel >> 8) & 0xFF) * 75 + (pixel & 0xFF) * 15) >> 7;

总之上面这个语句用10进制表示就是:
avg =(R38+G75+B*15)/129
只不过这里用二进制位运算而已

该句是将RGB按权重转为Gray,关于方法有RGB转灰度的几种算法(随便找的)
从语句可以看出38、78和15其实就是权重。

我们知道pixel是像素值,包含了RGB分量,想在一下面这个像素点值为例:
在这里插入图片描述
注意:这是一个像素点,该像素点值的16进制为:#455C24
既然语句中使用了">>"位运算,我们就需要将#455C24转为二进制,如下:
0100 0101 0101 1100 0010 0100(BIN)

位右移
  • pixle>>16:即向右移16位,结果为0100 0101,即右边这16位移动时被挤掉了(因为右边是没有位置的,移动1位就丢1位),只剩下最高的8位。
  • 实际上我们发现BIN总共24bit即3Byte,也就是RGB三个分量每个分量占一个字节,这个pixle>>16的结果不就是R分量的值嘛,故R分量的值就是 0100 0101(BIN)
and(&)
  • 0xFF:就是1111 1111,让最高8位0100 0101&1111 1111=0100 0101,发现其实还是原结果
*权重38
  • 这里虽然右边是10进制38,右边会自动变为10进制运算的,所以0100 0101(BIN)=69(DEC),即69*38=2622

其他分量同理

最后进行了一个>>7实际上就是将总的值除以129,因为三个权重(38、75、15)和为128,这里是为了归一化

总之用10进制表示就是:
Gray_value=(R38+G75+B*15)/129
只不过这里用二进制位运算而已

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要将树莓派上使用TensorFlow Lite进行摄像头识别的数据导出,可以按照以下步骤进行: 1. 首先,在树莓派上安装TensorFlow Lite。可以参考TensorFlow Lite官方文档进行安装:https://www.tensorflow.org/lite/guide/python 2. 接着,编写Python代码,使用TensorFlow Lite进行摄像头识别。可以参考TensorFlow Lite官方文档的示例代码:https://www.tensorflow.org/lite/examples/image_classification/overview 3. 在代码添加将识别结果保存为文件的代码。可以使用Python的文件操作函数将数据保存到本地文件。例如,可以使用以下代码将结果保存到CSV文件: ```python import csv # 识别结果保存为CSV文件 with open('result.csv', mode='w') as csv_file: fieldnames = ['image_path', 'predicted_label'] writer = csv.DictWriter(csv_file, fieldnames=fieldnames) writer.writeheader() for image_path, predicted_label in zip(image_paths, predicted_labels): writer.writerow({'image_path': image_path, 'predicted_label': predicted_label}) ``` 4. 保存代码并运行,等待摄像头识别完成并将结果保存到文件。 5. 最后,将保存的结果文件从树莓派导出到其他设备。可以使用SCP命令将文件从树莓派复制到其他设备: ```bash scp pi@<树莓派IP地址>:result.csv <目标设备用户名>@<目标设备IP地址>:<目标路径> ``` 其,`<树莓派IP地址>`是树莓派的IP地址,`<目标设备用户名>`和`<目标设备IP地址>`是目标设备的用户名和IP地址,`<目标路径>`是将文件保存到目标设备的路径。需要在目标设备上安装SCP命令才能使用该命令。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是一个对称矩阵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值