Free AI web copilot to create summaries, insights and extended knowledge, download it at here
6911
Abstract
ception {
print(<span class="hljs-string">'Failed to load model.'</span>);
}
}</pre></div><p id="a324">The code really speaks for itself in this case. We are simply loading the tflite file and the labels we already had in our assets folder; and capturing an exception in case something goes wrong.</p><h2 id="d248">Processing canvas points</h2><p id="c1bc">This function is totally the milestone of this post. The truth is that obtaining the right data shape to feed our model with from a real world object is one of the most complex parts within this process.</p><p id="f8fa">First we will create a couple of new constants in our<b> constants.dart</b> file:</p><div id="ebaf"><pre>const int kModelInputSize <span class="hljs-operator">=</span> <span class="hljs-number">28</span><span class="hljs-comment">;</span>
const double kCanvasInnerOffset <span class="hljs-operator">=</span> <span class="hljs-number">40.0</span><span class="hljs-comment">;</span></pre></div><p id="8544">Before continue, I think it’s time to make a quick pause and let me explain you about this <b>kCanvasInnerOffset </b>we just declared. Bear in mind this is a quite explicit way to hardcode this values and shouldn’t be taken as a reference of good practices, but it is ideal for me explaining the reason behind it.</p><p id="8d90">As I mentioned in the previous articles, the MNIST dataset has a peculiarity in its data shape, and is that all the digits are centered in a 20x20 frame, with a 4px padding; and as a result, it returns a 28x28 pixels image (this value being defined as <b>kModelInputSize </b>in our constants file).</p><p id="1ec0">This is what we are trying to illustrate here. We have our canvas that is 200x200 pixels at the moment where we are allowed to draw at the edges with no fear of having a wrong prediction. Then we will add a 40 pixel padding and we will get in return a 280x280 pixels image. In a later step, we will resize this image 10 times smaller, obtaining that way our 28x28 pixels image.</p><p id="d80a">I think you are already forming the right idea in your mind in how we will approach this, so let’s start digging into our processCanvasPoints function. I have created a gist as this one is a pretty long function. There are code comments to been able to follow it up, but don’t hesitate to drop comments for any questions.</p>
<figure id="ef53">
<div>
<div>
<iframe class="gist-iframe" src="/gist/sergiofraile/580ed568780b2b1cde3981c33350046d.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="eb09">Wow, that’s a big chunk of code to digest…</p>
<figure id="e3f5">
<div>
<div>
<img class="ratio" src="http://placehold.it/16x9">
<iframe class="" src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fgiphy.com%2Fembed%2FALZ1PPM20REZ2%2Ftwitter%2Fiframe&url=https%3A%2F%2Fmedia.giphy.com%2Fmedia%2FALZ1PPM20REZ2%2Fgiphy.mp4&image=https%3A%2F%2Fi.giphy.com%2Fmedia%2FALZ1PPM20REZ2%2F200.gif&key=a19fcc184b9711e1b4764040d3dc5c07&type=text%2Fhtml&schema=giphy" allowfullscreen="" frameborder="0" height="415" width="435">
</div>
</div>
</figure></iframe></div></div></figure><p id="0d84">You will notice we are missing a few other constants, in particular, I have moved all paints to the constants file, so just add at the bottom:</p><div id="4b85"><pre>const Color kBrushBlack <span class="hljs-operator">=</span> Colors.black<span class="hljs-comment">;</span>
const Color kBrushWhite <span class="hljs-operator">=</span> Colors.white<span class="hljs-comment">;</span>
final Paint kDrawingPaint <span class="hljs-operator">=</span> Paint()
..strokeCap <span class="hljs-operator">=</span> StrokeCap.square
..isAntiAlias <span class="hljs-operator">=</span> kIsAntiAlias
..color <span class="hljs-operator">=</span> kBrushBlack
..strokeWidth <span class="hljs-operator">=</span> kStrokeWidth<span class="hljs-comment">;</span>
final Paint kWhitePaint <span class="hljs-operator">=</span> Paint()
..strokeCap <span class="hljs-operator">=</span> StrokeCap.square
..isAntiAlias <span class="hljs-operator">=</span> kIsAntiAlias
..color <span class="hljs-operator">=</span> kBrushWhite
..strokeWidth <span class="hljs-operator">=</span> kStrokeWidth<span class="hljs-comment">;</span>
final kBackgroundPaint <span class="hljs-operator">=</span> Paint()..color <span class="hljs-operator">=</span> kBrushBlack<span class="hljs-comment">;</span></pre></div><p id="7cc2">You may as well replace drawingPaint by kDrawingPaint in the <b>drawing_painter.dart</b> file.</p><h2 id="83f1">Predicting the image</h2><p id="cd17">This part is really easy compared to what we did previously. the predict image function looks as follow:</p><div id="7d6a"><pre><span class="hljs-function">Future<List> <span class="hljs-title">predictImage</span><span class="hljs-params">(im.Image image)</span> async </span>{
<span class="hljs-keyword">return</span> await Tflite.<span class="hljs-built_in">runModelOnBinary</span>(
binary: <span class="hljs-built_in">imageToByteListFloat32</span>(image, kModelInputSize),
);
}</pre></div><p id="23a0">We are simply using the runModelOnBinary method available from the Tflite library we imported in one of the previous articles. The only real catch here is that we need to pass a binary, therefore we have a couple of auxiliary functions to make this possible.</p><p id="7439">I don’t expect anyone trying to understand the code behind this two functions, but I hope you get the idea behind them. Basically we are transforming all the pixels of the image into a list and, additionally, converting the rgb values into greyscale. Remember that the dataset we trained our model with was formed by 28x28 greyscale images. The fact that is greyscale means there is only one color channel, and therefore our output will be 28x28x1 into a list shape.</p><div id="f2c8"><pre>Uint8List imageToByteListFloat32(im.Image image, <span class="hljs-built_in">int</span> inputSize) {
<span class="hljs-keyword">var</span> convertedBytes = Float32List(inputSize * inputSize);
<span class="hljs-keyword">var</span> buffer = Float32List.view(convertedBytes.buffer);
<span class="hljs-built_in">int</span> pixelIndex = <span class="hljs-number">0</span>;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i < inputSize; i++) {
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j = <span class="hljs-number">0</span>; j < inputSize; j++) {
<span class="hljs-keyword">var</span> pixel = image.getPixel(j, i);
buffer
Options
[pixelIndex++] =
(im.getRed(pixel) + im.getGreen(pixel) + im.getBlue(pixel)) /
<span class="hljs-number">3</span> /
<span class="hljs-number">255.0</span>;
}
}
<span class="hljs-keyword">return</span> convertedBytes.buffer.asUint8List();
}
<span class="hljs-built_in">double</span> convertPixel(<span class="hljs-built_in">int</span> <span class="hljs-built_in">color</span>) {
<span class="hljs-keyword">return</span> (<span class="hljs-number">255</span> -
(((<span class="hljs-built_in">color</span> >> <span class="hljs-number">16</span>) & <span class="hljs-number">0xFF</span>) * <span class="hljs-number">0.299</span> +
((<span class="hljs-built_in">color</span> >> <span class="hljs-number">8</span>) & <span class="hljs-number">0xFF</span>) * <span class="hljs-number">0.587</span> +
(<span class="hljs-built_in">color</span> & <span class="hljs-number">0xFF</span>) * <span class="hljs-number">0.114</span>)) /
<span class="hljs-number">255.0</span>;
}</pre></div><p id="ec74">It is really common to find this kind type of methods in apps that uses machine learning, particularly when capturing images, so don’t be surprise to see similar things when checking out other mobile projects that combine machine learning. Actually, if you are planning in building your own app using machine learning, I would recommend to create a snippet of this two methods described above, believe me they will come in handy.</p><h2 id="dc5f">It is time to run the app</h2><p id="1e42">At this point, if you run the application an pay attention to the logs the emulator is giving you, you should be able to see the output of the print statement we have when trying to predict something. So if you draw a number 3, you should see something like this:</p><div id="d2e1"><pre>[{<span class="hljs-attr">confidence:</span> <span class="hljs-number">1.0</span>, <span class="hljs-attr">index:</span> <span class="hljs-number">3</span>, <span class="hljs-attr">label:</span> <span class="hljs-number">3</span>}]</pre></div><p id="4bf0">This means our model is 100% sure (confidence level 1.0) that what we draw is a number 3. The index is the output from our model and the label marks the element that corresponds with that index, in this case, as we trying to predict (or infere) numbers, those two values matches. If we were trying to predict if the image contains a dog or a cat, then our indexes would be values between 0 and 1 (two outputs) and our labels would be either 🐕 or 🐈, for instance.</p><p id="41e9">We may also receive more values in the prediction array. Our model may not always be 100% sure that what we have drawn is X number, but will give us a list with what it believes is there. For instance, if we draw the top line of the number 7 (only the top horizontal line), our model output should look similar to this:</p><div id="1131"><pre>[{<span class="hljs-attr">confidence:</span> <span class="hljs-number">0.5581846237182617</span>, <span class="hljs-attr">index:</span> <span class="hljs-number">2</span>, <span class="hljs-attr">label:</span> <span class="hljs-number">2</span>}, {<span class="hljs-attr">confidence:</span> <span class="hljs-number">0.4371589720249176</span>, <span class="hljs-attr">index:</span> <span class="hljs-number">7</span>, <span class="hljs-attr">label:</span> <span class="hljs-number">7</span>}]</pre></div><p id="5f9d">Which translates in: “<i>I am sure a 55.81% that what you draw is a 2; and I am sure in a 43.71% that is a 7.”</i></p><h2 id="4bcd">Delete button</h2><p id="d584">As the last detail, you may have noticed that our canvas doesn’t get cleaned after typing on it. Where there are several ways of doing this, I am just going to add a floating button at the bottom right corner of the screen that will clean our canvas.</p><p id="a0c1">For doing so, we only need to create a <b>FloatingActionButton</b> at the bottom of our scene, so between the last container and before the scaffold widget finish, place this:</p><div id="4d08"><pre>floatingActionButton: <span class="hljs-built_in">FloatingActionButton</span>(
onPressed: () {
<span class="hljs-built_in">_cleanDrawing</span>();
},
tooltip: <span class="hljs-string">'Clean'</span>,
child: <span class="hljs-built_in">Icon</span>(Icons.delete),
),</pre></div><p id="df30">There is a private function there (notice the underscore) for cleaning the canvas. Implementing that function is as easy as to set a new list to the points variable within our _RecognizerScreen class:</p><div id="5527"><pre>void _cleanDrawing() {
setState(() {
points <span class="hljs-operator">=</span> List()<span class="hljs-comment">;</span>
})<span class="hljs-comment">;</span>
}</pre></div><p id="f314">This is how the app should look after this last changes:</p><figure id="6fb2"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*7QFrofQWYNHrKYwpTJdF2g.jpeg"><figcaption></figcaption></figure><h1 id="43b5">You made it again!</h1><p id="dc3b">Phenomenal! You achieved it again 👏🏻 Let me say that our app is almost done at this point, we are really trying to predict what we are drawing.</p><p id="3ec2">What is left for the next and last article of this series is simply decoration, being able to show the user the number our model is returning us and adding a chart so we can see the confidence level of the predictions our model returns.</p><p id="b3cf">As usual, you can access all the code from this section here.</p><div id="850e" class="link-block">
<a href="https://github.com/sergiofraile/when_flutter_meets_tensorflow_part_4">
<div>
<div>
<h2>sergiofraile/when_flutter_meets_tensorflow_part_4</h2>
<div><h3>A new Flutter application. This project is a starting point for a Flutter application. A few resources to get you…</h3></div>
<div><p>github.com</p></div>
</div>
<div>
<div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/)"></div>
</div>
</div>
</a>
</div><p id="b0ef">Looking forward to see you in the next section! 👋</p><div id="61da" class="link-block">
<a href="https://www.twitter.com/FlutterComm">
<div>
<div>
<h2>Flutter Community</h2>
<div><h3>The latest Tweets from Flutter Community (@FlutterComm). Follow to get notifications of new articles and packages from…</h3></div>
<div><p>www.twitter.com</p></div>
</div>
<div>
<div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*rejlBvtYiJEWs0qA)"></div>
</div>
</div>
</a>
</div></article></body>