I recently started in the Fishers Youth Mentoring Initiative, and my mentee is a young man in junior high who really likes lizards. He showed me photos of them on his iPad, photos of his pet lizard, and informed me of many lizard facts. Heās also a talented sketch artist ā showcasing many drawings of Pokemon, lizards and more. Oh, yeah, heās also into computers and loves his iPad.
Part of the mentoring program is to help with school, being there as they adjust to growing up, and both respecting and encouraging their interests.
It just so happens that he had a science project coming up. He wasnāt sure what to write about. His pet lizard recently had an attitude shift, and he figured it was because it wasnāt getting as much food week over week. Changing that, he realized its attitude changed. So, he wanted to cover that somehow.
Seeing his interest in lizards, drawing, and computers I asked if we could combine them. I suggested we build an app, a āReptile Tracker,ā that would help us track reptiles, teach others about them, and show them drawings he did. He loved the idea.
Planning
We only get to meet for 30 minutes each week. So, I gave him some homework. Next time we meet, āshow me what the app would look like.ā He gleefully agreed.
One week later, he proudly showed me his vision for the app:

I said āVery cool.ā Iām now convinced āheās inā on the project, and taking it seriously.
I was also surprised to learn that my expectations of āshow me what it would look likeā were different from what I received from someone both much younger than I and with a different world view. To him, software may simply be visualized as an icon. In my world, itās mockups and napkin sketches. It definitely made me think about othersā perceptions!
True to software engineer and sort-of project manager form, I explained our next step was to figure out what the app would do. So, hereās our plan:
- Identify if there are reptiles in the photo.
- Tell them if itās safe to pick it up, if itās venomous, and so forth.
- Get one point for every reptile found. Weāll only support Lizards, Snakes, and Turtles in the first version.
Alright, time for the next assignment. My homework was to figure out how to do it. His homework was to draw up the Lizard, Snake, and Turtle that will be shown in the app.
Challenge accepted!
I quickly determined a couple key design and development points:
- The icon he drew is great, but looks like a drawing on the screen. I think Iāll need to ask him to draw them on my Surface Book, so they have the right look. Looks like an opportunity for him to try Fresh Paint on my Surface Book.
- Azure Cognitive Services, specifically their Computer Vision solution (API), will work for this task. I found a great article on the Xamarin blog by Mike James. I had to update it a bit for this article, as the calls and packages are a bit different two years later, but it definitely pointed me in the right direction.
Writing the Code
The weekend came, and I finally had time. I had been thinking about the app the remainder of the week. I woke up early Saturday and drew up a sketch of the tracking page, then went back to sleep. Later, when it was time to start the day, I headed over to Starbucksā¦

I broke out my shiny new MacBook Pro and spun up Visual Studio Mac. Xamarin Forms was the perfect candidate for this project ā cross platform, baby! I started a new Tabbed Page project, brought over some code for taking photos with the Xam.Plugin.Media plugin and resizing them, and the beta Xamarin.Essentials plugin for eventual geolocation and settings support. Hey, itās only the first week 
Side Note: Normally I would use my Surface Book. This was a chance for me to seriously play with MFractor for the first time. Yay, even more learning this weekend!
Now that I had the basics in there, I created the interface for the Image Recognition Service. I wanted to be able to swap it out later if Azure didnāt cut it, so Dependency Service to the rescue! Hereās the interface:
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.CognitiveServices.Vision.ComputerVision.Models;
namespace ReptileTracker.Services
{
Ā Ā Ā Ā public interface IImageRecognitionService
Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā string ApiKey { get; set; }
Ā Ā Ā Ā Ā Ā Ā Ā Task<ImageAnalysis> AnalyzeImage(Stream imageStream);
Ā Ā Ā Ā }
}
Now it was time to check out Mikeās article. It made sense, and was close to what I wanted. However, the packages he referenced were for Microsoftās Project Oxford. In 2018, those capabilities have been rolled into Azure as Azure Cognitive Services. Once I found the updated NuGet package – Microsoft.Azure.CognitiveServices.Vision.ComputerVision – and made some code tweaks, I ended up with working code.
A few developer notes for those playing with Azure Cognitive Services:
- Hold on to that API key, youāll need it
- Pay close attention to the Endpoint on the Overview page ā you must provide it, otherwise youāll get a 403 Forbidden

And hereās the implementation. Note the implementation must have a parameter-less constructor, otherwise Dependency Service wonāt resolve it.
usingĀ Microsoft.Azure.CognitiveServices.Vision.ComputerVision;
usingĀ Microsoft.Azure.CognitiveServices.Vision.ComputerVision.Models;
usingĀ System;
usingĀ System.Collections.Generic;
usingĀ System.Diagnostics;
usingĀ System.IO;
usingĀ System.Threading.Tasks;
usingĀ ReptileTracker.Services;
usingĀ Xamarin.Forms;
[assembly:Ā Dependency(typeof(ImageRecognitionService))]
namespaceĀ ReptileTracker.Services
{
Ā Ā Ā Ā publicĀ classĀ ImageRecognitionServiceĀ :Ā IImageRecognitionService
Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā <summary>
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā TheĀ AzureĀ CognitiveĀ ServicesĀ ComputerĀ VisionĀ APIĀ key.
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā </summary>
Ā Ā Ā Ā Ā Ā Ā Ā publicĀ stringĀ ApiKeyĀ {Ā get;Ā set;Ā }
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā <summary>
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā ParameterlessĀ constructorĀ soĀ DependencyĀ ServiceĀ canĀ createĀ anĀ instance.
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā </summary>
Ā Ā Ā Ā Ā Ā Ā Ā publicĀ ImageRecognitionService()
Ā Ā Ā Ā Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā }
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā <summary>
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā InitializesĀ aĀ newĀ instanceĀ ofĀ theĀ <seeĀ cref="T:ReptileTracker.Services.ImageRecognitionService"/>Ā class.
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā </summary>
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā <paramĀ name="apiKey">APIĀ key.</param>
Ā Ā Ā Ā Ā Ā Ā Ā publicĀ ImageRecognitionService(stringĀ apiKey)
Ā Ā Ā Ā Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā ApiKeyĀ =Ā apiKey;
Ā Ā Ā Ā Ā Ā Ā Ā }
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā <summary>
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā AnalyzesĀ theĀ image.
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā </summary>
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā <returns>TheĀ image.</returns>
Ā Ā Ā Ā Ā Ā Ā Ā ///Ā <paramĀ name="imageStream">ImageĀ stream.</param>
Ā Ā Ā Ā Ā Ā Ā Ā publicĀ asyncĀ Task<ImageAnalysis>Ā AnalyzeImage(StreamĀ imageStream)
Ā Ā Ā Ā Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā constĀ stringĀ funcNameĀ =Ā nameof(AnalyzeImage);
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā ifĀ (string.IsNullOrWhiteSpace(ApiKey))
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā throwĀ newĀ ArgumentException("APIĀ KeyĀ mustĀ beĀ provided.");
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā varĀ featuresĀ =Ā newĀ List<VisualFeatureTypes>Ā {
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā VisualFeatureTypes.Categories,
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā VisualFeatureTypes.Description,
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā VisualFeatureTypes.Faces,
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā VisualFeatureTypes.ImageType,
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā VisualFeatureTypes.Tags
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā };
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā varĀ credentialsĀ =Ā newĀ ApiKeyServiceClientCredentials(ApiKey);
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā varĀ handlerĀ =Ā newĀ System.Net.Http.DelegatingHandler[]Ā {Ā };
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā usingĀ (varĀ visionClientĀ =Ā newĀ ComputerVisionClient(credentials,Ā handler))
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā try
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā imageStream.PositionĀ =Ā 0;
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā visionClient.EndpointĀ =Ā "https://eastus.api.cognitive.microsoft.com/";
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā varĀ resultĀ =Ā awaitĀ visionClient.AnalyzeImageInStreamAsync(imageStream,Ā features);
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā returnĀ result;
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā catchĀ (ExceptionĀ ex)
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā {
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Debug.WriteLine($"{funcName}:Ā {ex.GetBaseException().Message}");
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā returnĀ null;
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā }
Ā Ā Ā Ā Ā Ā Ā Ā }
Ā Ā Ā Ā }
}
And hereās how I referenced it from my content page:
pleaseWait.IsVisible = true;
pleaseWait.IsRunning = true;
var imageRecognizer = DependencyService.Get<IImageRecognitionService>();
imageRecognizer.ApiKey = AppSettings.ApiKey_Azure_ImageRecognitionService;
var details = await imageRecognizer.AnalyzeImage(new MemoryStream(ReptilePhotoBytes));
pleaseWait.IsRunning = false;
pleaseWait.IsVisible = false;
varĀ tagsReturnedĀ =Ā details?.TagsĀ !=Ā nullĀ
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā &&Ā details?.Description?.CaptionsĀ !=Ā nullĀ
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā &&Ā details.Tags.Any()Ā
Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā &&Ā details.Description.Captions.Any();
lblTags.IsVisible = true;
lblDescription.IsVisible = true;
//Ā DetermineĀ ifĀ reptilesĀ wereĀ found.
varĀ reptilesToDetectĀ =Ā AppResources.DetectionTags.Split(',');
varĀ reptilesFoundĀ =Ā details.Tags.Any(tĀ =>Ā reptilesToDetect.Contains(t.Name.ToLower()));
//Ā ShowĀ animationsĀ andĀ graphicsĀ toĀ makeĀ thingsĀ lookĀ cool,Ā evenĀ thoughĀ weĀ alreadyĀ haveĀ plentyĀ ofĀ info.
awaitĀ RotateImageAndShowSuccess(reptilesFound,Ā "lizard",Ā details,Ā imgLizard);
awaitĀ RotateImageAndShowSuccess(reptilesFound,Ā "turtle",Ā details,Ā imgTurtle);
awaitĀ RotateImageAndShowSuccess(reptilesFound,Ā "snake",Ā details,Ā imgSnake);
awaitĀ RotateImageAndShowSuccess(reptilesFound,Ā "question",Ā details,Ā imgQuestion);
That worked like a champ, with a few gotchas:
- I would receive a 400 Bad Request if I sent an image that was too large. 1024 x 768 worked, but 2000 x 2000 didnāt. The documentation says the image must be less than 4MB, and at least 50×50.
- That API endpoint must be initialized. Examples donāt always make this clear. Thereās no constructor that takes an endpoint address, so itās easy to miss.
- It can take a moment for recognition to occur. Make sure youāre using async/await so you donāt block the UI Thread!
Prettying It Up
Before I get into the results, I wanted to point out I spent significant time prettying things up. I added animations, different font sizes, better icons from The Noun Project, and more. While the image recognizer only took about an hour, the UX took a lot more. Funny how that works.
Mixed Results
So I was getting results. I added a few labels to my view to see what was coming back. Some of them were funny, others were accurate. The tags were expected, but the captions were fascinating. The captions describe the scene as the Computer Vision API sees it. I spent most of the day taking photos and seeing what was returned. Some examples:
- My barista, Matt, was āa smiling woman working in a storeā
- My mom was āa smiling manā ā she was not amused
Most of the time, as long as the subjects were clear, the scene recognition was correct:

Or close to correct, in this shot with a turtle at Petsmart:

Sometimes, though, nothing useful would be returned:

I would have thought it would have found āWhite Castleā. I wonder if it wonāt show brand names for some reason? They do have an OCR endpoint, so maybe that would be useful in another use case.
Sometimes, even though I thought an image would āobviouslyā be recognized, it wasnāt:

Iāll need to read more about how to improve accuracy, if and whether thatās even an option.
Good thing I implemented it with an interface! I could try Googleās computer vision services next.
Next Steps
Weāre not done with the app yet ā this week, we will discuss how to handle the scoring. Iāll post updates as we work on it. Here’s a link to the iOS beta.
Some things Iād like to try:
- Highlight the tags in the image, by drawing over the image. Iād make this a toggle.
- Clean up the UI to toggle ādeveloper detailsā. Itās cool to show those now, but it doesnāt necessarily help the target user. Iāll ask my mentee what he thinks.
Please let me know if you have any questions by leaving a comment!
Want to learn more about Xamarin? I suggest Microsoftās totally awesome Xamarin University. All the classes you need to get started are free.
Update 2018-11-06:
- The tags are in two different locations – Tags and Description.Tags. Two different sets of tags are in there, so I’m now combining those lists and getting better results.
- I found I could get color details. I’ve updated the accent color surrounding the photo. Just a nice design touch.