×

野生动物探测器开源分享

消耗积分:0 | 格式:zip | 大小:0.28 MB | 2022-10-31

陈秀珍

分享资料个

描述

在科罗拉多州和其他拥有大量野生动物种群的州,人类与野生动物之间的冲突并不少见(城镇中的熊和驼鹿、涉及麋鹿的交通事故等)

我决定通过创建一个检测运动、拍照并使用机器学习算法分析它以采取某种行动(吓跑它、点亮标志、通知当局)。此外,该设备跟踪环境数据,以帮助研究人员跟踪可能导致更多野生动物遭遇的迁徙模式和条件,它还检测野火以防止不必要的破坏。

 
pYYBAGNY5nuACeNLAA9XERvSk2Y998.jpg
检测装置

分类

该项目的主要重点是机器学习野生动物分类。我通过训练 TensorFlow 来使用来自 Google 图片的数据来识别四种特定的动物物种,从而实现了这一点。所有的初始训练都是在计算机上使用为 TensorFlow 和一千多张野生动物图片预先配置的 docker 机器图像完成的。一旦 TensorFlow 被训练,图形文件被优化以在移动设备上运行,然后它可以被用来在 Android Things 设备上运行,方法是拍摄一张图像,调整它的大小,然后通过 TensorFlow 运行它以确定该图像是否包含一个我们预先训练的分类,以及到什么置信水平。

一旦确定了动物,就必须对这些数据进行处理。根据您的情况,您可以使用额外的硬件来闪光、播放声音或做任何其他事情。对于这个原型,我将 Android Things 连接到 Firebase 后端,以便简单地保存图像和分类信息。

 
poYBAGNY5oiANoSQAAAvK5SdqaE792.jpg
设备拍摄的图像
 

 

 
pYYBAGNY5oyAAc4yAACCU3dURqw296.png
Firebase 上保存的检测结果
 

将信息存储在 Firebase 上后,您可以创建一个辅助应用程序来接收有关动物存在的通知,或者只是存储该数据以用于研究目的。

此外,如果您希望无需检查 Firebase 即可看到结果,您可以在此项目中添加 LCD 屏幕。您可以在此处找到 Gautier Mechling为 1601 系列 LCD 屏幕提供的易于使用的驱动程序。

创建 TensorFlow 分类文件

您要做的第一件事是确保 TensorFlow 在您的计算机上并且可以正常工作。这可能相当复杂,我发现在生成训练文件的整个过程中使其正常工作的最简单方法是安装和使用Docker。该程序将允许您在计算机上运行为 TensorFlow 预配置的虚拟机。

一旦你安装了 Docker 并让它在你的计算机上运行,​​你应该打开它的首选项并为你的虚拟机设置内存使用。我将我的内存设置为使用 7 GB 内存,这可能超出了您的需要,但我花了几天时间试图让 TensorFlow 正确创建所需的训练图表而不会崩溃,然后我才意识到虚拟机内存不足。

 
poYBAGNY5o-ASVk0AAB1jm9RC_8529.jpg
 

一旦你安装了 Docker 并在你的机器上启动它,你需要从终端运行它并下载一个镜像。对于此示例,我在 macOS 下运行,因此对于您的平台,命令可能会有所不同。

docker run -it -v $HOME/tf_files:/tf_files gcr.io/tensorflow/tensorflow:latest-develcd /tensorflowgit pullgit checkout v1.0.1

当一切都完成设置后,您应该在终端中出现如下提示:

root@1643721c503b:/tensorflow#

此时,您需要一组图像来训练 TensorFlow。我使用Fatkun Batch Download Image Chrome 插件从 Google 搜索中批量下载返回的图像。安装插件后,您可以搜索要分类的任何内容并开始选择要保存的图像。

 
pYYBAGNY5pGAD78TAADRvcovjy0663.jpg
 

为了使命名更容易,您可能还需要进入“更多选项”部分,让插件在下载图像时重命名它们。

 
pYYBAGNY5pOAEku6AAAkpOJz1mM886.jpg
 

接下来,您需要将正在使用的图像移动到主目录下的tf_files文件夹中,这是我们在初始化 docker 机器时创建的文件夹。对于此示例,我的图像目录称为TensorFlowTrainingImages 每个可分类项目都应该在该目录中有自己的文件夹,如下所示。

 
poYBAGNY5paABo04AAA86IZJAMs568.jpg
 

设置好目录后,您可以从 Docker 终端使用以下命令开始重新训练:

python tensorflow/examples/image_retraining/retrain.py \  
--bottleneck_dir=/tf_files/bottlenecks \  
--how_many_training_steps 3000 \  
--model_dir=/tf_files/inception \  
--output_graph=/tf_files/graph.pb \  
--output_labels=/tf_files/labels.txt \  
--image_dir /tf_files/TensorFlowTrainingImages

上面的命令会生成bottlenecks ,本质上是最终分类数据传递使用的数据,以及用于分类的图形和标签文件。

从现在开始,我们使用 TensorFlow 运行的操作可能需要几分钟到一个多小时,具体取决于您计算机的速度。当重新训练命令运行时,您应该会在终端中看到很多类似于以下内容的输出:

Step 130: Train accuracy = 95.0%2017-04-12 18:21:28.495779: 
Step 130: Cross entropy = 0.2503392017-04-12 18:21:28.748928: 
Step 130: Validation accuracy = 92.0% (N=100) 

生成瓶颈后,您将拥有一个graph.pb文件和一个代表您的数据的labels.txt文件。虽然这些格式在您的计算机上运行分类时效果很好,但它们在放入 Android 应用程序时往往不起作用。您将需要优化它们。

首先运行/configure 命令。接受所有默认值。

配置完成后,运行以下命令来设置优化工具。这一步在我的机器上完成了大约一个小时。

bazel build tensorflow/python/tools:optimize_for_inference

构建优化工具后,您可以使用它通过 bazel 优化您的图形文件。

bazel-bin/tensorflow/python/tools/optimize_for_inference \  
--input=/tf_files/graph.pb \  
--output=/tf_files/optimized_graph.pb \  
--input_names=Mul \  
--output_names=final_result

现在您的优化图已经生成,您可以在您的主目录的tf_files文件夹中找到它和您的标签。

 
pYYBAGNY5piAT5fUAAAwf88zCwo339.jpg
 

一旦你有了优化的图表,你就可以将它包含在你的 Android Things 项目中以与之交互。如果您查看该项目的源代码,您可以看到使用 Camera2 API 获取图像并将其传递给TensorFlowImageClassifier.java进行分类的 Java 代码。您还可以找到将分类图像上传到 Firebase 的代码,如下所示

private void uploadAnimal(Bitmap bitmap, final Detection detectedAnimal) { 
   ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 
   bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); 
   byte[] data = outputStream.toByteArray(); 
   FirebaseStorage storage = FirebaseStorage.getInstance(); 
   StorageReference storageReference = storage.getReferenceFromUrl( 
   	FIREBASE_STORAGE_URL).child(System.currentTimeMillis() + ".jpg"); 
   UploadTask uploadTask = storageReference.putBytes(data); 
   uploadTask.addOnFailureListener(new OnFailureListener() { 
       @Override 
       public void onFailure(@NonNull Exception exception) { 
       } 
   }).addOnSuccessListener(new OnSuccessListener() { 
       @Override 
       public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 
           handleNotificationForImage(taskSnapshot.getDownloadUrl(), 
                                      detectedAnimal); 
       } 
   }); 
} 

环境传感器

虽然能够识别野生动物很棒,但这里有机会做更多的事情。如果这些设备之一设置在荒野中,您还可以添加传感器来保存天气信息并将其报告给您的后端。对于我的原型,我包括了一个湿度传感器、温度和压力传感器、一个火焰探测器(理论上当周围没有人时通知某人野火)、一个空气质量传感器和一个紫外线传感器。其中一些在附加到 Android Things 方面有其独特的挑战,我将在下面介绍。

数字传感器

连接到野生动物探测器的最简单的传感器是火焰探测器和运动探测器。这些传感器在低电平空闲,但在检测到火焰或运动时切换到高电平。使用 Android Things 的外围 I/O API,这些传感器是最容易支持的,因为设备只需要监听状态的变化。

对 HC SR501 和火焰探测器的支持都遵循这个类的模式

@SuppressWarnings({"unused", "WeakerAccess"})
public class HCSR501 implements AutoCloseable {
   public enum State {
       STATE_HIGH,
       STATE_LOW;
   }
   public interface OnMotionDetectedEventListener {
       void onMotionDetectedEvent(State state);
   }
   private Gpio mMotionDetectorGpio;
   private OnMotionDetectedEventListener mOnMotionDetectedEventListener;
   private boolean mLastState;
   public HCSR501(String pin) throws IOException {
       PeripheralManagerService pioService = new PeripheralManagerService();
       Gpio HCSR501Gpio = pioService.openGpio(pin);
       try {
           connect(HCSR501Gpio);
       } catch( IOException | RuntimeException e ) {
           close();
           throw e;
       }
   }
   private void connect(Gpio HCSR501Gpio) throws IOException {
       mMotionDetectorGpio = HCSR501Gpio;
       mMotionDetectorGpio.setDirection(Gpio.DIRECTION_IN);
       mMotionDetectorGpio.setEdgeTriggerType(Gpio.EDGE_BOTH);
       mLastState = mMotionDetectorGpio.getValue();
       mMotionDetectorGpio.setActiveType(mLastState ? Gpio.ACTIVE_HIGH : Gpio.ACTIVE_LOW);
       mMotionDetectorGpio.registerGpioCallback(mInterruptCallback);
   }
   private void performMotionEvent(State state) {
       if( mOnMotionDetectedEventListener != null ) {
           mOnMotionDetectedEventListener.onMotionDetectedEvent(state);
       }
   }
   private GpioCallback mInterruptCallback = new GpioCallback() {
       @Override
       public boolean onGpioEdge(Gpio gpio) {
           try {
               if( gpio.getValue() != mLastState ) {
                   mLastState = gpio.getValue();
                   performMotionEvent(mLastState ? State.STATE_HIGH : State.STATE_LOW);
               }
           } catch( IOException e ) {
           }
           return true;
       }
   };
   public void setOnMotionDetectedEventListener(OnMotionDetectedEventListener listener) {
       mOnMotionDetectedEventListener = listener;
   }
   @Override
   public void close() throws IOException {
       mOnMotionDetectedEventListener = null;
       if (mMotionDetectorGpio != null) {
           mMotionDetectorGpio.unregisterGpioCallback(mInterruptCallback);
           try {
               mMotionDetectorGpio.close();
           } finally {
               mMotionDetectorGpio = null;
           }
       }
   }
}

模拟传感器

Android Things 的模拟传感器有点棘手。虽然设备可能具有板载模数转换器 (ADC),但它并未在 Android Things 平台上启用。为了解决这个问题,我使用了 MCP3008 ADC 芯片来读取模拟输入并将其转换为可以在 Android Things 板上读取的 int。这是通过一种快速而肮脏的“bit banged”方法完成的,因此您可以更改引脚以匹配您可用的任何内容。使用 ADC,我能够添加对空气质量传感器和紫外线传感器的支持。以下代码是我用于 MCP3008 的代码,并且在我使用过的多个 Android Things 项目中被证明是有价值的。

public class MCP3008 {
   private final String csPin;
   private final String clockPin;
   private final String mosiPin;
   private final String misoPin;
   private Gpio mCsPin;
   private Gpio mClockPin;
   private Gpio mMosiPin;
   private Gpio mMisoPin;
   public MCP3008(String csPin, String clockPin, String mosiPin, String misoPin) {
       this.csPin = csPin;
       this.clockPin = clockPin;
       this.mosiPin = mosiPin;
       this.misoPin = misoPin;
   }
   public void register() throws IOException {
       PeripheralManagerService service = new PeripheralManagerService();
       mClockPin = service.openGpio(clockPin);
       mCsPin = service.openGpio(csPin);
       mMosiPin = service.openGpio(mosiPin);
       mMisoPin = service.openGpio(misoPin);
       mClockPin.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
       mCsPin.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
       mMosiPin.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
       mMisoPin.setDirection(Gpio.DIRECTION_IN);
   }
   public int readAdc(int channel) throws IOException {
       if( channel < 0 || channel > 7 ) {
           throw new IOException("ADC channel must be between 0 and 7");
       }
       initReadState();
       initChannelSelect(channel);
       return getValueFromSelectedChannel();
   }
   private int getValueFromSelectedChannel() throws IOException {
       int value = 0x0;
       for( int i = 0; i < 12; i++ ) {
           toggleClock();
           value <<= 0x1;
           if( mMisoPin.getValue() ) {
               value |= 0x1;
           }
       }
       mCsPin.setValue(true);
       value >>= 0x1; // first bit is 'null', so drop it
       return value;
   }
   private void initReadState() throws IOException {
       mCsPin.setValue(true);
       mClockPin.setValue(false);
       mCsPin.setValue(false);
   }
   private void initChannelSelect(int channel) throws IOException {
       int commandout = channel;
       commandout |= 0x18; // start bit + single-ended bit
       commandout <<= 0x3; // we only need to send 5 bits
       for( int i = 0; i < 5; i++ ) {
           if ( ( commandout & 0x80 ) != 0x0 ) {
               mMosiPin.setValue(true);
           } else {
               mMosiPin.setValue(false);
           }
           commandout <<= 0x1;
           toggleClock();
       }
   }
   private void toggleClock() throws IOException {
       mClockPin.setValue(true);
       mClockPin.setValue(false);
   }
   public void unregister() {
       if( mCsPin != null ) {
           try {
               mCsPin.close();
           } catch( IOException ignore ) {
               // do nothing
           }
       }
       if( mClockPin != null ) {
           try {
               mClockPin.close();
           } catch( IOException ignore ) {
               // do nothing
           }
       }
       if( mMisoPin != null ) {
           try {
               mMisoPin.close();
           } catch( IOException ignore ) {
               // do nothing
           }
       }
       if( mMosiPin != null ) {
           try {
               mMosiPin.close();
           } catch( IOException ignore ) {
               // do nothing
           }
       }
   }
}

不支持的硬件

不幸的是,DHT11 湿度传感器使用以纳秒为间隔触发的信号协议,但 Android 平台仅支持低至毫秒。因此,Android Things 无法直接支持 DHT11。但是,有一种解决方法。使用带有 Arduino 的 ATMega328p,我能够将信息从 DHT11 读取到 Arduino 芯片,并使用 I²C 将该信息发送到 Android Things 设备。

 
pYYBAGNY5p2AKUg3AAA9Zo7Lgiw391.png
 

您可以通过在 Android 中打开与该地址的连接来使用自定义地址读取 I²C

PeripheralManagerService service = new PeripheralManagerService();
mHumidity = service.openI2cDevice(BoardDefaults.getHumidityI2cBus(), 0x08);

在上面的示例中,我们使用的自定义地址是8 在 ATMega328p 上运行的 Arduino 代码中,您可以将该地址与 Wire 库一起使用。

#import 
#include 
 
dht11 DHT11;
 
#define DHT11PIN 13
 
uint8_t humidity;
 
void setup() {
 Wire.begin(8);                // join i2c bus with address #8
 Wire.onRequest(requestEvent); // register event
}
 
void loop() {
 int chk = DHT11.read(DHT11PIN);
 humidity = DHT11.humidity;
 delay(1000);
}
 
void requestEvent() {
 Wire.write(humidity);
}

预先编写的驱动程序

虽然添加对数字 I/O 的支持很容易,但还有一件事更容易:使用已经编写的代码。对于 BMP280,我能够从 Google 的官方驱动程序库中引入一个预先编写的驱动程序,让我无需太多工作或时间就可以从该环境传感器读取信息。您可以在此处找到 Google 预先编写的示例,因为其中有一些可能对您使用 Android Things 组合在一起的任何项目有用。这些驱动程序很容易进入您的项目,就像将它们添加到您的 gradle 依赖项中一样

dependencies {
   compile 'com.google.android.things.contrib:driver-bmx280:0.2'
   provided 'com.google.android.things:androidthings:0.4-devpreview'
}

对于这个项目,我还使用 GPS 驱动程序将位置数据添加到 Firebase,尽管您可以从您使用的任何网络连接中检索该数据。

网络连接

在当前状态下,野生动物探测器只是使用无线网络连接互联网。虽然这不是最实用的,特别是考虑到它很可能在没有现成的无线连接的区域使用,但可以轻松修改它以支持蜂窝连接或任何其他更合适的连接。

相机

这里的相机有点棘手。目前 Android Things 不完全支持 USB 摄像头,但您可以将它们视为 UART 设备。如果您不想为您的相机编写驱动程序,您也可以使用蓝牙相机,因为 Android Things 确实支持蓝牙连接。如果您将 Raspberry Pi 与 Android Things 一起使用,则可以使用标准的带状电缆摄像头,使用 Android 的摄像头 API,无需太多麻烦。

更多细节

虽然这个项目有很多事情要做,但可以在附加的源项目中找到更多详细信息。如上所述,一旦您生成了 TensorFlow 图,您应该能够下载源代码并将图文件替换为您尝试检测的任何内容。对于 Firebase 支持,您还需要创建自己的免费Firebase 项目并将凭据复制到项目中。


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !

'+ '

'+ '

'+ ''+ '
'+ ''+ ''+ '
'+ ''+ '' ); $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code ==5){ $(pop_this).attr('href',"/login/index.html"); return false } if(data.code == 2){ //跳转到VIP升级页面 window.location.href="//m.lene-v.com/vip/index?aid=" + webid return false } //是会员 if (data.code > 0) { $('body').append(htmlSetNormalDownload); var getWidth=$("#poplayer").width(); $("#poplayer").css("margin-left","-"+getWidth/2+"px"); $('#tips').html(data.msg) $('.download_confirm').click(function(){ $('#dialog').remove(); }) } else { var down_url = $('#vipdownload').attr('data-url'); isBindAnalysisForm(pop_this, down_url, 1) } }); }); //是否开通VIP $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code == 2 || data.code ==5){ //跳转到VIP升级页面 $('#vipdownload>span').text("开通VIP 免费下载") return false }else{ // 待续费 if(data.code == 3) { vipExpiredInfo.ifVipExpired = true vipExpiredInfo.vipExpiredDate = data.data.endoftime } $('#vipdownload .icon-vip-tips').remove() $('#vipdownload>span').text("VIP免积分下载") } }); }).on("click",".download_cancel",function(){ $('#dialog').remove(); }) var setWeixinShare={};//定义默认的微信分享信息,页面如果要自定义分享,直接更改此变量即可 if(window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){ var d={ title:'野生动物探测器开源分享',//标题 desc:$('[name=description]').attr("content"), //描述 imgUrl:'https://'+location.host+'/static/images/ele-logo.png',// 分享图标,默认是logo link:'',//链接 type:'',// 分享类型,music、video或link,不填默认为link dataUrl:'',//如果type是music或video,则要提供数据链接,默认为空 success:'', // 用户确认分享后执行的回调函数 cancel:''// 用户取消分享后执行的回调函数 } setWeixinShare=$.extend(d,setWeixinShare); $.ajax({ url:"//www.lene-v.com/app/wechat/index.php?s=Home/ShareConfig/index", data:"share_url="+encodeURIComponent(location.href)+"&format=jsonp&domain=m", type:'get', dataType:'jsonp', success:function(res){ if(res.status!="successed"){ return false; } $.getScript('https://res.wx.qq.com/open/js/jweixin-1.0.0.js',function(result,status){ if(status!="success"){ return false; } var getWxCfg=res.data; wx.config({ //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId:getWxCfg.appId, // 必填,公众号的唯一标识 timestamp:getWxCfg.timestamp, // 必填,生成签名的时间戳 nonceStr:getWxCfg.nonceStr, // 必填,生成签名的随机串 signature:getWxCfg.signature,// 必填,签名,见附录1 jsApiList:['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: setWeixinShare.title, // 分享标题 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 type: setWeixinShare.type, // 分享类型,music、video或link,不填默认为link dataUrl: setWeixinShare.dataUrl, // 如果type是music或video,则要提供数据链接,默认为空 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ”按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ空间”按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); }); }); } }); } function openX_ad(posterid, htmlid, width, height) { if ($(htmlid).length > 0) { var randomnumber = Math.random(); var now_url = encodeURIComponent(window.location.href); var ga = document.createElement('iframe'); ga.src = 'https://www1.elecfans.com/www/delivery/myafr.php?target=_blank&cb=' + randomnumber + '&zoneid=' + posterid+'&prefer='+now_url; ga.width = width; ga.height = height; ga.frameBorder = 0; ga.scrolling = 'no'; var s = $(htmlid).append(ga); } } openX_ad(828, '#berry-300', 300, 250);