Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bust the burglars with Apache Kafka and TensorFlow

Bust the burglars with Apache Kafka and TensorFlow

In this session I have shown how Apache Kafka and TensorFlow can be used to process real time video events and send alerts when a potential burglary is detected.

Specifically, I've discussed how Kafka Connect and Kafka Streams can be used to read images from a media stream and how this can be integrated with TensorFlow to analyse the resulting event streams.

Frederieke Scheper

May 28, 2019
Tweet

More Decks by Frederieke Scheper

Other Decks in Technology

Transcript

  1. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 2 https://pixabay.com/p-683065/
  2. Introduction - Let’s meet • It’s great to be in

    Barcelona! • Java Architect @ Ordina JTech • Contracted by Ministry of Justice and Security • Passion for ‣ Big Data technologies ‣ Machine learning 3
  3. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 6 https://pixabay.com/p-683065/
  4. Event streams • Input - Images from IP camera(s) -

    Lock status changes • Output - Burglar Alerts 9 Integrated using Kafka Connect }
  5. Kafka Connectors 1. FTP source ‣ Loads images into Kafka

    2. REST source ‣ Loads lock status changes into Kafka 3. Telegram sink ‣ Developed specifically for this project ‣ Sends burglar alerts 10
  6. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 11 https://pixabay.com/p-683065/
  7. String url = "rtsp://usr:pwd@host:443/videoSub"; Java2DFrameConverter frameConverter = new Java2DFrameConverter(); FFmpegFrameGrabber

    frameGrabber = new FFmpegFrameGrabber(url); frameGrabber.setFrameRate(5); frameGrabber.setImageWidth(640); frameGrabber.start(); Acquiring images - take 1… 15
  8. String url = "rtsp://usr:pwd@host:443/videoSub"; Java2DFrameConverter frameConverter = new Java2DFrameConverter(); FFmpegFrameGrabber

    frameGrabber = new FFmpegFrameGrabber(url); frameGrabber.setFrameRate(5); frameGrabber.setImageWidth(640); frameGrabber.start(); Frame frame = frameGrabber.grabImage(); BufferedImage image = frameConverter.convert(frame); ImageIO.write(image, "png", new File("./video-frame.png")); Acquiring images - take 1… 16
  9. Acquiring images - take 2 • KISS • Use out-of-the

    box support - Motion detection - Movie snapshots • Auto upload using FTP(S) 18 NO CODING REQUIRED
  10. https://github.com/Eneco/kafka-connect-ftp ==> https://github.com/Landoop/stream-reactor { "name": "burglar-alerts-camera-ftp-source", , "config": { "connector.class":

    "com.datamountaineer.….ftp.source.FtpSourceConnector", , "key.converter": "org.apache.kafka.connect.storage.StringConverter" , "value.converter": "com.github.fbascheper.kafka.connect.….ByteArrayConverter" Configuring Kafka Connect 20
  11. https://github.com/Eneco/kafka-connect-ftp ==> https://github.com/Landoop/stream-reactor { "name": "burglar-alerts-camera-ftp-source", , "config": { "connector.class":

    "com.datamountaineer.….ftp.source.FtpSourceConnector", , "key.converter": "org.apache.kafka.connect.storage.StringConverter" , "value.converter": "com.github.fbascheper.kafka.connect.….ByteArrayConverter" , "connect.ftp.refresh": "PT1M" , "connect.ftp.file.maxage": "P14D" , "connect.ftp.fileconverter": "com.datamountaineer.….SimpleFileConverter" , "connect.ftp.monitor.update": "/cam-download/:burglar-alerts-camera-ftp-topic" , "connect.ftp.protocol": "sftp" } } Configuring Kafka Connect 21
  12. public class ImageGrayScaleConverter implements Converter, HeaderConverter { @Override public byte[]

    fromConnectData(String topic, Schema schema, Object value) { val source = ImageIO.read(new ByteArrayInputStream(data)); val convertedImg = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_3BYTE_BGR); convertedImg.getGraphics().drawImage(source, 0, 0, Color.BLACK, null); convertedImg.getGraphics().dispose(); Image grayscale converter 23
  13. public class ImageGrayScaleConverter implements Converter, HeaderConverter { @Override public byte[]

    fromConnectData(String topic, Schema schema, Object value) { val source = ImageIO.read(new ByteArrayInputStream(data)); val convertedImg = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_3BYTE_BGR); convertedImg.getGraphics().drawImage(source, 0, 0, Color.BLACK, null); convertedImg.getGraphics().dispose(); val resized = ImageSizeTransformer.INSTANCE.transform(convertedImg); val grayscale = GrayScaleTransformer.INSTANCE.transform(resized); Image grayscale converter 24
  14. public class ImageGrayScaleConverter implements Converter, HeaderConverter { @Override public byte[]

    fromConnectData(String topic, Schema schema, Object value) { val source = ImageIO.read(new ByteArrayInputStream(data)); val convertedImg = new BufferedImage(source.getWidth(), source.getHeight(), BufferedImage.TYPE_3BYTE_BGR); convertedImg.getGraphics().drawImage(source, 0, 0, Color.BLACK, null); convertedImg.getGraphics().dispose(); val resized = ImageSizeTransformer.INSTANCE.transform(convertedImg); val grayscale = GrayScaleTransformer.INSTANCE.transform(resized); val bos = new ByteArrayOutputStream(); ImageIO.write(grayscale, "jpg", bos); return bos.toByteArray(); } } Image grayscale converter 25
  15. { "name": "burglar-alerts-camera-ftp-source", , "config": { "connector.class": "com.datamountaineer.….ftp.source.FtpSourceConnector", , "key.converter":

    "org.apache.kafka.connect.storage.StringConverter" , "value.converter": "com.github.fbascheper.kafka.connect.….ByteArrayConverter" , "connect.ftp.refresh": "PT1M" , "connect.ftp.file.maxage": "P14D" , "connect.ftp.fileconverter": "com.datamountaineer.….SimpleFileConverter" , "connect.ftp.monitor.update": "/cam-download/:burglar-alerts-camera-ftp-topic" , "connect.ftp.protocol": "sftp" } } Configuring the image converter 26 { "name": "burglar-alerts-camera-ftp-source", , "config": { "connector.class": "com.datamountaineer.….ftp.source.FtpSourceConnector", , "key.converter": "org.apache.kafka.connect.storage.StringConverter" , "value.converter": "com.github.fbascheper.kafka.connect.….ByteArrayConverter" , "connect.ftp.refresh": "PT1M" , "connect.ftp.file.maxage": "P14D" , "connect.ftp.fileconverter": "com.datamountaineer.….SimpleFileConverter" , "connect.ftp.monitor.update": "/cam-download/:burglar-alerts-camera-ftp-topic" , "connect.ftp.protocol": "sftp" } }
  16. { "name": "burglar-alerts-camera-ftp-source", , "config": { "connector.class": "com.datamountaineer.….ftp.source.FtpSourceConnector", , "key.converter":

    "org.apache.kafka.connect.storage.StringConverter" , "value.converter": “com.github.fbascheper.….image.ImageGrayScaleConverter" , "value.converter.resizeTargetWidth": 1024 , "value.converter.resizeTargetHeight": -1 , "value.converter.cropLeft": 165 , "value.converter.cropRight": 200 , "value.converter.cropTop": 50 , "value.converter.cropBottom": 50, , "connect.ftp.refresh": "PT1M" , … , "connect.ftp.protocol": "sftp" } } Configuring the image converter 27
  17. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 28 https://pixabay.com/p-683065/
  18. TensorFlow - ‘ML for everyone’ Familiar with TensorFlow? Used TensorFlow

    before? Which API’s? Python Java Keras Other 29 https://commons.wikimedia.org/wiki/File:Polling_Station_sign_2017.jpg
  19. Transfer learning • Use a ‘pre-trained’ model ‣ Trained on

    a large dataset ‣ Will effectively serve as a ‘generic model’ of the visual world • Two alternatives ‣ Feature extraction ‣ Fine-tuning 34
  20. Actions 35 ENOUGH VARIATION !? • Build a test- and

    validation set ‣ Download images ‣ Apply transformation ‣ Classify images • Run TensorFlow ‣ Export the model • Check out the code at ‣ https://github.com/fbascheper/kafka-tf-burglar-alerts-demo-model
  21. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 36 https://pixabay.com/p-683065/
  22. Kafka Telegram connector • Developed specifically for this project (FOSS

    @ GitHub) • Can be used to send multiple message types ‣ Text ‣ Photo ‣ Videos • Dependencies ‣ Apache Avro ‣ Telegram Bot API 38
  23. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 41 https://pixabay.com/p-683065/
  24. val builder = new StreamsBuilder(); val cameraSourceStream = builder.stream( SOURCE_TOPIC_CAMERA_IMAGES,

    Consumed.with(Serdes.String(), Serdes.ByteArray())); Putting it all together 42
  25. val builder = new StreamsBuilder(); val cameraSourceStream = builder.stream( SOURCE_TOPIC_CAMERA_IMAGES,

    Consumed.with(Serdes.String(), Serdes.ByteArray())); val imageStream = cameraSourceStream .mapValues((readOnlyKey, value) -> new SerializableImage(readOnlyKey, ByteBuffer.wrap(value))); Putting it all together 43
  26. val builder = new StreamsBuilder(); val cameraSourceStream = builder.stream( SOURCE_TOPIC_CAMERA_IMAGES,

    Consumed.with(Serdes.String(), Serdes.ByteArray())); val imageStream = cameraSourceStream .mapValues((readOnlyKey, value) -> new SerializableImage(readOnlyKey, ByteBuffer.wrap(value))); val burglarAlertStream = imageStream .mapValues((readOnlyKey, image) -> TensorFlowMatcher.matchImage(tfGraphDef, image)) .filter((key, imgClass) -> imgClass.getClassification() == Classification.BURGLAR_ALERT); Putting it all together 44
  27. val burglarAlertStream = imageStream .mapValues((readOnlyKey, image) -> TensorFlowMatcher.matchImage(tfGraphDef, image)) .filter((key,

    imgClass) -> imgClass.getClassification() == Classification.BURGLAR_ALERT); val telegramPhotoMessage = burglarAlertStream .mapValues((readOnlyKey, imageClassification) -> TelegramMessageMapper.photoMessage( imageClassification.getImage(), // image imageClassification.toString() // caption ); Putting it all together 45
  28. val burglarAlertStream = imageStream .mapValues((readOnlyKey, image) -> TensorFlowMatcher.matchImage(tfGraphDef, image)) .filter((key,

    imgClass) -> imgClass.getClassification() == Classification.BURGLAR_ALERT); val telegramPhotoMessage = burglarAlertStream .mapValues((readOnlyKey, imageClassification) -> TelegramMessageMapper.photoMessage( imageClassification.getImage(), // image imageClassification.toString() // caption ); telegramPhotoMessage.to(SINK_TOPIC_BURGLAR_ALERTS, Produced.with(Serdes.String(), telegramMessageSerde)); Putting it all together 46
  29. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 47 https://pixabay.com/p-683065/
  30. STREAM (changelog) (“alert-enabled”, 0) ⊗ (“alert-enabled”, 0) ⊗ (“alert-enabled”, -1)

    ⊗ (“alert-enabled”, -1) ⊗ (“alert-enabled”, 0) ⊗ KStream - KTable duality 51 (no change) (no change) KTABLE | alert-enabled | 0 | ⊗ ⊗ ⊗ | alert-enabled | -1 | ⊗ ⊗ ⊗ | alert-enabled | 0 |
  31. STREAM (changelog) (“alert-enabled”, 0) ⊗ (“alert-enabled”, 0) ⊗ (“alert-enabled”, -1)

    ⊗ (“alert-enabled”, -1) ⊗ (“alert-enabled”, 0) ⊗ KStream - KTable duality 52 (no change) (no change) KTABLE | alert-enabled | 0 | ⊗ ⊗ ⊗ | alert-enabled | -1 | ⊗ ⊗ ⊗ | alert-enabled | 0 | STATE !
  32. val alertingEnabledStateStream = builder .stream(SOURCE_TOPIC_SMARTLOCK_POLL, Consumed.with(Serdes.String(), Serdes.String())) .mapValues(SmartLockMapper::toNukiResponse) .map(SmartLockMapper::alertingEnabledMapper) .to(BURGLAR_ALERTING_STATE_TOPIC,

    Produced.with(Serdes.String(), Serdes.Integer())); Adding state - part 1 53 RESULT: A CHANGELOG STREAM WITH (“alert-enabled”, 0) (“alert-enabled”, 0) (“alert-enabled”, -1) public static KeyValue<~> alertingEnabledMapper(String key, NukiResponse rp) { boolean disabled = rp.getLocks().stream() .anyMatch(lock -> LockStates.UNLOCKED.equals(lock.getState())); return KeyValue.pair(ALERT_STATE, disabled ? 0 : -1); }
  33. /* Build global KTable from changelog stream BURGLAR_ALERTING_STATE_TOPIC */ val

    matd = Materialized.<String, Integer, KeyValueStore<Bytes, byte[]>> as(BURGLAR_ALERTING_STATE_STORE) .withKeySerde(Serdes.String()) .withValueSerde(Serdes.Integer()); val alertingEnabledGlobalKTable = builder .globalTable(BURGLAR_ALERTING_STATE_TOPIC, matd); Adding state - part 2 54 RESULT: A TABLE WITH | alert-enabled | 0 or -1 |
  34. val builder = new StreamsBuilder(); val cameraSourceStream = builder.stream( SOURCE_TOPIC_CAMERA_IMAGES,

    Consumed.with(Serdes.String(), Serdes.ByteArray())); val filteredSourceStream = cameraSourceStream .leftJoin( alertingEnabledGlobalKTable, (filename, image) -> ALERT_STATE, (image, enabled) -> enabled.equals(-1) ? image :new byte[]{}) .filter((key, value) -> value.length > 0); Adding the filter in the application 55
  35. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 56 https://pixabay.com/p-683065/
  36. Agenda Introduction Setting up your alarm Uploading images into Kafka

    Building a TensorFlow model Receiving alerts from Kafka Putting it all together Additional filtering Demo Wrap-up 58 https://pixabay.com/p-683065/
  37. Useful links • Source code ‣ https://github.com/fbascheper/kafka-tf-burglar-alerts-demo ‣ https://github.com/fbascheper/kafka-tf-burglar-alerts-demo-model •

    Kafka Connect plugin / converters ‣ https://github.com/fbascheper/kafka-connect-telegram ‣ https://github.com/fbascheper/kafka-connect-storage-converters • Kafka connectors overview ‣ https://www.confluent.io/product/connectors-repository 61