Smart Grocery Cart Using Computer Vision - OpenMV Cam H7
Keep track of the items in a shopping basket using computer vision and an OpenMV Cam H7.
Created By: Kutluhan Aktar

Especially after the recent success of Amazon Go cashierless convenience stores, there is a surge in adaptations of this relatively new approach to the shopping experience, including computer vision, sensor fusion, and deep learning. Since the nonpareil concept of cashierless stores is to make shoppers avoid tedious checkout lines and self-checkout stations, the stores equipped with this technology improve the customer experience and increase profit margins comparatively. While implementing this technology in a grocery or convenience store, smart grocery carts are the most prominent asset to provide an exceptional customer experience like Amazon Go.
Although smart grocery carts improve the customer experience and plummet maintenance costs by providing an automated product tracking and payment system, the current integration methods are expensive investments for small businesses in the food retail industry since these methods require renovating (remodeling) store layouts or paying monthly fees to cloud services.
After perusing recent research papers on smart grocery carts, I noticed there is no appliance devised for converting regular grocery carts into smart grocery carts without changing anything else in an existing establishment. Therefore, I decided to build a budget-friendly and easy-to-use device giving smart grocery cart perks to regular grocery carts with a user-friendly interface.
To detect different food retail products accurately, I needed to create a valid data set in order to train my object detection model with notable veracity. Since OpenMV Cam H7 is a considerably small high-performance microcontroller board designed specifically for machine vision applications, I decided to utilize OpenMV Cam H7 in this project. Also, I was able to capture product images easily while collecting data and store them on an SD card since OpenMV Cam H7 has a built-in MicroSD card module. Then, I employed a color TFT screen (ST7735) to display a real-time video stream, the prediction (detection) results, and the selection (options) menu.
After completing my data set including various food retail products, I built my object detection model with Edge Impulse to detect products added or removed to/from the grocery cart. I utilized Edge Impulse FOMO (Faster Objects, More Objects) algorithm to train my model, which is a novel machine learning algorithm that brings object detection to highly constrained devices. Since Edge Impulse is nearly compatible with all microcontrollers and development boards, I had not encountered any issues while uploading and running my model on OpenMV Cam H7. As labels, I utilized the product brand names, such as Nutella and Snickers.
After training and testing my object detection (FOMO) model, I deployed and uploaded the model on OpenMV Cam H7 as an OpenMV firmware. Therefore, the device is capable of detecting products by running the model independently without any additional procedures or latency.
Since I wanted to create a full-fledged device providing a wholesome shopping experience, I decided to build a web application from scratch in PHP, JavaScript, CSS, and MySQL. Therefore, I installed an Apache HTTP Server (XAMPP) on LattePanda 3 Delta, which also has a MariaDB database.
This complementing web application lets customers create accounts via its interface, receives requests from the device to add or remove products to/from the customer's database table, and creates a concurrent shopping list from the products added to the grocery cart. Also, the application sends an HTML email to the customer's registered email address when the customer finishes shopping and is ready to leave the store, including the generated shopping list and the payment link.
Since OpenMV Cam H7 does not provide Wi-Fi or cellular connectivity, I employed Beetle ESP32-C3 to get commands from OpenMV Cam H7 via serial communication and communicate with the web application via HTTP GET requests, which is an ultra-small size development board intended for IoT applications. To send commands via serial communication and control the selection menu after a product is detected by the model, I connected a joystick to OpenMV Cam H7. I also utilized the joystick while taking and storing pictures of various food retail products.
To enable the device to determine when the customer completes shopping and is ready to leave the store, I connected an MFRC522 RFID reader to Beetle ESP32-C3 so as to detect the assigned RFID key tag provided by the store per grocery cart. Also, I connected a buzzer and an RGB LED to Beetle ESP32-C3 to inform the customer of the device status.
After completing the wiring on a breadboard for the prototype and testing my code and object detection model, I decided to design a PCB for this project to make the device assembly effortless. Since Scrooge McDuck is one of my favorite cartoon characters and is famous for his wealth and stinginess, I thought it would be hilarious to design a shopping-related PCB based on him.
Lastly, to make the device as robust and sturdy as possible while being attached to a grocery cart and utilized by customers, I designed a semi-transparent hinged case compatible with any grocery cart due to its hooks and snap-fit joints (3D printable).
So, this is my project in a nutshell 😃
In the following steps, you can find more detailed information on coding, capturing food retail product images, storing pictures on an SD card, building an object detection (FOMO) model with Edge Impulse, running the model on OpenMV Cam H7, developing a full-fledged web application, and communicating with the web application via Beetle ESP32-C3.
🎁🎨 Also, huge thanks to Creality for sending me a Creality Sonic Pad, a Creality Sermoon V1 3D Printer, and a Creality CR-200B 3D Printer.














Before prototyping my Scrooge McDuck-inspired PCB design, I tested all connections and wiring with OpenMV Cam H7 and Beetle ESP32-C3. Then, I checked the connection status between Beetle ESP32-C3 and the web application hosted on LattePanda 3 Delta.


Then, I designed my Scrooge McDuck-inspired PCB by utilizing KiCad. As mentioned earlier, I chose to design my PCB based on Scrooge McDuck since I loved the juxtaposition of shopping and his well-known stinginess :) I attached the Gerber file of the PCB below. Therefore, if you want, you can order this PCB from PCBWay to build this device giving smart grocery cart perks to any grocery cart.



First of all, by utilizing a soldering iron, I attached headers (female), a COM-09032 analog joystick, a buzzer, a 5mm common anode RGB LED, and a power jack to the PCB.
📌 Component list on the PCB:
OpenMV_H7_1, OpenMV_H7_2 (Headers for OpenMV Cam H7)
Beetle_C3_1, Beetle_C3_2 (Headers for Beetle ESP32-C3)
MFRC522 (Headers for MFRC522 RFID Reader)
ST7735 (Headers for ST7735 1.8" Color TFT Display)
U1 (COM-09032 Analog Joystick)
BZ1 (Buzzer)
D1 (5mm Common Anode RGB LED)
J1 (Power Jack)



// Connections
// Beetle ESP32-C3 :
// MFRC522
// D7 --------------------------- RST
// D2 --------------------------- SDA
// D6 --------------------------- MOSI
// D5 --------------------------- MISO
// D4 --------------------------- SCK
// OpenMV Cam H7
// D0 --------------------------- P4
// D1 --------------------------- P5
// 5mm Common Anode RGB LED
// D21 --------------------------- R
// D8 --------------------------- G
// D9 --------------------------- B
// Buzzer
// D20 --------------------------- +
//
//
// OpenMV Cam H7 :
// ST7735 1.8" Color TFT Display
// 3.3V --------------------------- LED
// P2 --------------------------- SCK
// P0 --------------------------- SDA
// P8 --------------------------- AO
// P7 --------------------------- RESET
// P3 --------------------------- CS
// GND --------------------------- GND
// 3.3V --------------------------- VCC
// JoyStick
// P6 --------------------------- VRX
// P9 --------------------------- SW
After completing soldering, I attached all remaining components to the Scrooge McDuck PCB via headers — OpenMV Cam H7, Beetle ESP32-C3, MFRC522 RFID reader, and ST7735 color TFT display.
I connected a color TFT screen (ST7735) to OpenMV Cam H7 so as to display the real-time video stream, the detection results after running the object detection (FOMO) model, and the selection (options) menu. To save images under two categories and control the selection menu after a product is detected by the model, I connected an analog joystick (COM-09032) to OpenMV Cam H7.
To be able to transfer commands to Beetle ESP32-C3 via serial communication, I connected the hardware serial port of OpenMV Cam H7 (UART 3) to the hardware serial port of Beetle ESP32-C3 (Serial1). To enable the device to determine when the customer finishes shopping and is ready to leave the store, I connected an MFRC522 RFID reader to Beetle ESP32-C3. Also, I connected a buzzer and an RGB LED to inform the customer of the device status, denoting web application connection and serial communication success.

Since I focused on building a budget-friendly and easy-to-use device that captures food retail product images, detects products with object detection, and provides an exceptional customer experience, I decided to design a robust and sturdy semi-transparent hinged case allowing the customer to access the RFID reader and the joystick effortlessly. To avoid overexposure to dust and prevent loose wire connections, I added two snap-fit joints to the hinged case. Also, I decided to emboss different grocery cart icons on the top part of the hinged case to emphasize the shopping theme gloriously :)
Since I needed to connect the top and bottom parts of the hinged case seamlessly, I designed a pin with pin ends (caps). To make the device compatible with any grocery cart, I added two hooks on the back of the bottom part. Also, I added a ring to the bottom part to attach the assigned RFID key tag.
I designed the top and bottom parts of the hinged case, the pin, and the pin ends in Autodesk Fusion 360. You can download their STL files below.









Then, I sliced all 3D models (STL files) in Ultimaker Cura.



Since I wanted to create a semi-transparent solid structure for the hinged case representing product packaging, I utilized this PLA filament:
- Natural
Finally, I printed all parts (models) with my Creality Sermoon V1 3D Printer and Creality CR-200B 3D Printer in combination with the Creality Sonic Pad. You can find more detailed information regarding the Sonic Pad in Step 2.1.


If you are a maker or hobbyist planning to print your 3D models to create more complex and detailed projects, I highly recommend the Sermoon V1. Since the Sermoon V1 is fully-enclosed, you can print high-resolution 3D models with PLA and ABS filaments. Also, it has a smart filament runout sensor and the resume printing option for power failures.
Furthermore, the Sermoon V1 provides a flexible metal magnetic suction platform on the heated bed. So, you can remove your prints without any struggle. Also, you can feed and remove filaments automatically (one-touch) due to its unique sprite extruder (hot end) design supporting dual-gear feeding. Most importantly, you can level the bed automatically due to its user-friendly and assisted bed leveling function.
Before the first use, remove unnecessary cable ties and apply grease to the rails.
#⃣



Test the nozzle and hot bed temperatures.
#⃣

Go to Print Setup ➡ Auto leveling and adjust five predefined points automatically with the assisted leveling function.
#⃣


Finally, place the filament into the integrated spool holder and feed the extruder with the filament.
#⃣


Since the Sermoon V1 is not officially supported by Cura, download the latest Creality Slicer version and copy the official printer settings provided by Creality, including Start G-code and End G-code, to a custom printer profile on Cura.
#⃣





Since I wanted to improve my print quality and speed with Klipper, I decided to upgrade my Creality CR-200B 3D Printer with the Creality Sonic Pad.
Creality Sonic Pad is a beginner-friendly device to control almost any FDM 3D printer on the market with the Klipper firmware. Since the Sonic Pad uses precision-oriented algorithms, it provides remarkable results with higher printing speeds. The built-in input shaper function mitigates oscillation during high-speed printing and smooths ringing to maintain high model quality. Also, it supports G-code model preview.
Although the Sonic Pad is pre-configured for some Creality printers, it does not support the CR-200B officially yet. Therefore, I needed to add the CR-200B as a user-defined printer to the Sonic Pad. Since the Sonic Pad needs unsupported printers to be flashed with the self-compiled Klipper firmware before connection, I flashed my CR-200B with the required Klipper firmware settings via FluiddPI by following this YouTube tutorial.
If you do not know how to write a printer configuration file for Klipper, you can download the stock CR-200B configuration file from here.
After flashing the CR-200B with the Klipper firmware, copy the configuration file (printer.cfg) to a USB drive and connect the drive to the Sonic Pad.
#⃣
After setting up the Sonic Pad, select Other models. Then, load the printer.cfg file.
#⃣


After connecting the Sonic Pad to the CR-200B successfully via a USB cable, the Sonic Pad starts the self-testing procedure, which allows the user to test printer functions and level the bed.
#⃣



After completing setting up the printer, the Sonic Pad lets the user control all functions provided by the Klipper firmware.
#⃣


In Cura, export the sliced model in the ufp format. After uploading .ufp files to the Sonic Pad via the USB drive, it converts them to sliced G-code files automatically.
#⃣
Also, the Sonic Pad can display model preview pictures generated by Cura with the Create Thumbnail script.
#⃣
After printing all parts (models), I placed the pin through the hinges on the top and bottom parts and fixed the pin via the pin ends (caps).
I affixed the Scrooge McDuck PCB to the bottom part of the hinged case via a hot glue gun.
Then, I attached the ST7735 TFT display to the hinged case via its slot on the bottom part to make customers see the screen even if the hinged case is closed via its built-in snap-fit joints.











Finally, I attached the assigned RFID key tag to the ring on the bottom part of the hinged case. Via the slot on the top part of the hinged case, the customer can approximate the key tag to the MFRC522 RFID reader even if the case is closed.


Since I wanted to provide a wholesome user experience with this device, I decided to make it able to send the list of the products added to the grocery cart and the payment link to the customer's registered email address on the database. However, I did not want to make this feature dependent on a paid email forwarder or cloud service. Therefore, I decided to send HTML emails directly from localhost via Twilio's SendGrid Email API.
SendGrid Email API provides proven email deliverability with its cloud-based architecture and has free of charge plan with 100 emails per day for relatively small projects like this. Also, SendGrid API provides official libraries for different programming languages, including PHP.

Then, click the Create a Single Sender button and enter the required information to comply with the anti-spam laws. It is recommended to use a unique email service provider other than Gmail, Hotmail, etc.
#⃣


After verifying the entered email address, choose PHP as the integration API option for localhost.
#⃣


Click the Create API Key button to generate an API key with full feature access.
#⃣


After generating the API key, install the latest release of the SendGrid Email API PHP Library on localhost directly from this GitHub repository.
# ⃣



To provide an exceptional online customer experience, I developed a full-fledged web application from scratch in PHP, JavaScript, CSS, and MySQL. This web application lets customers create accounts via its interface, receives requests from the device to add or remove the products detected by the object detection model to/from the customer's database table, and creates a concurrent shopping list. Also, it sends an HTML email to the customer's registered email address via SendGrid Email API when the customer finishes shopping and is ready to leave the store, including the generated shopping list and the payment link.
As shown below, the web application consists of one folder and 7 code files:
- /assets
- -- /sendgrid-php
- -- background.jpg
- -- class.php
- -- icon.png
- -- index.css
- -- index.js
- -- update_list.php
- index.php
- product_list.php
- shopping.php


📁 class.php
In the class.php file, I created a class named _main to bundle the following functions under a specific structure.
⭐ Include the Twilio SendGrid Email API PHP Library.
require("sendgrid-php/sendgrid-php.php");
⭐ Define the _main class and its functions.
⭐ Define the API key and the sender email address required by SendGrid Email API.
public $conn;
private $sendgrid_API_Key = "<_API_KEY_>";
private $from_email = "<_FROM_EMAIL_>";
public function __init__($conn){
$this->conn = $conn;
}
⭐ In the database_create_customer_table function, create a database table on the MariaDB database and add the given customer information to the recently created database table.
⭐ If the customer information is added to the database table successfully, redirect the customer to the product list page — product_list.php.
⭐ Redirect the customer to the home page if there is a pertinent database error.
public function database_create_customer_table($table, $firstname, $lastname, $card_info, $email){
// Create a new database table.
$sql_create = "CREATE TABLE `$table`(
id int AUTO_INCREMENT PRIMARY KEY NOT NULL,
firstname varchar(255) NOT NULL,
lastname varchar(255) NOT NULL,
card_info varchar(255) NOT NULL,
email varchar(255) NOT NULL,
product_id varchar(255) NOT NULL,
product_name varchar(255) NOT NULL,
product_price varchar(255) NOT NULL
);";
if(mysqli_query($this->conn, $sql_create)){
echo("<br>Database Table Created Successfully!");
// Add the customer information to the recently created database table.
$sql_insert = "INSERT INTO `$table`(`firstname`, `lastname`, `card_info`, `email`, `product_id`, `product_name`, `product_price`)
VALUES ('$firstname', '$lastname', '$card_info', '$email', 'X', 'X', 'X')"
;
if(mysqli_query($this->conn, $sql_insert)) echo("<br><br>Customer information added to the database table successfully!");
// If the customer information is added to the database table successfully, redirect the customer to the product list page.
header("Location: product_list.php");
exit();
}else{
// Redirect the customer to the home page if there is an error.
header("Location: ./?databaseTableAlreadyCreated");
exit();
}
}
⭐ In the insert_new_data function, insert the given product information into the customer's database table.
public function insert_new_data($table, $product_id, $product_name, $product_price){
$sql_insert = "INSERT INTO `$table`(`firstname`, `lastname`, `card_info`, `email`, `product_id`, `product_name`, `product_price`)
SELECT `firstname`, `lastname`, `card_info`, `email`, '$product_id', '$product_name', '$product_price'
FROM `$table` WHERE id=1"
;
if(mysqli_query($this->conn, $sql_insert)){ return true; } else{ return false; }
}
⭐ In the remove_data function, delete the first row with the given product ID from the customer's database table.
public function remove_data($table, $product_id){
$sql_delete = "DELETE FROM `$table` WHERE `product_id`='$product_id' limit 1";
if(mysqli_query($this->conn, $sql_delete)){ return true; } else{ return false; }
}
⭐ In the get_purchased_product_list function, get all saved product information in the database table and return it as three lists — product names, product IDs, and product prices.
public function get_purchased_product_list($table){
$product_names = []; $product_ids = []; $product_prices = [];
$sql_list = "SELECT * FROM `$table` WHERE id!=1 ORDER BY `id` ASC";
$result = mysqli_query($this->conn, $sql_list);
$check = mysqli_num_rows($result);
if($check > 0){
while($row = mysqli_fetch_assoc($result)){
array_push($product_names, $row["product_name"]);
array_push($product_ids, $row["product_id"]);
array_push($product_prices, $row["product_price"]);
}
return array($product_names, $product_ids, $product_prices);
}else{
return array(["Not Found!"], ["Not Found!"], ["Not Found!"]);
}
}
⭐ In the get_table_name function, obtain the latest registered customer's (via the web application interface) assigned table name from the database.
⭐ If the return value is true, return the obtained table name as a variable.
⭐ If the return value is false, print the obtained table and its creation date.
%kutluhan%2022-12-16 23:49:39%
public function get_table_name($return){
$sql_get = "SELECT `table_name`, `create_time` FROM `information_schema`.`TABLES` WHERE `table_schema` = 'smart_grocery_cart' ORDER BY `CREATE_TIME` DESC limit 1";
$result = mysqli_query($this->conn, $sql_get);
$check = mysqli_num_rows($result);
if($check > 0){
while($row = mysqli_fetch_assoc($result)){
if(!$return) echo("%".$row["table_name"]."%".$row["create_time"]."%");
else return $row["table_name"];
}
}
}
⭐ In the get_email function, obtain the email address of the given customer from the database.
private function get_email($table){
$sql_email = "SELECT * FROM `$table` WHERE id=1";
$result = mysqli_query($this->conn, $sql_email);
$check = mysqli_num_rows($result);
if($check > 0){
if($row = mysqli_fetch_assoc($result)){ return $row["email"]; }
else{ return "Not Found!"; }
}
}
⭐ In the send_product_list_email function, obtain (and assign) the three product information arrays generated by the get_purchased_product_list function and create HTML table rows by utilizing these arrays.
Then, send an HTML email via SendGrid Email API to the customer's registered email address, including the list of the products added to the grocery cart and the link to the payment page — product_list.php.
public function send_product_list_email($table, $tag){
// Get the customer's email address.
$to_email = $this->get_email($table);
// Obtain the list of the products added to the cart from the customer's database table.
$product_names = []; $product_ids = []; $product_prices = [];
list($product_names, $product_ids, $product_prices) = $this->get_purchased_product_list($_GET['table']);
$list = "";
for($i=0; $i<count($product_names); $i++){
$list .= '<tr>
<td>'.$product_names[$i].'</td>
<td>'.$product_ids[$i].'</td>
<td>'.$product_prices[$i].'</td>
</tr>
';
}
// Send an HTML email via the SendGrid Email PHP API.
$email = new \SendGrid\Mail\Mail();
$email->setFrom($this->from_email, "Smart Grocery Cart");
$email->setSubject("Cart Product List");
$email->addTo($to_email, "Customer");
$email->addContent("text/html",
'<!DOCTYPE html>
<html>
<head>
<style>
h1{text-align:center;font-weight:bold;color:#25282A;}
table{background-color:#043458;width:100%;border:10px solid #25282A;}
th{background-color:#D1CDDA;color:#25282A;border:5px solid #25282A;font-size:25px;font-weight:bold;}
td{color:#AEE1CD;border:5px solid #25282A;text-align:left;font-size:20px;font-weight:bold;}
a{text-decoration:none;background-color:#5EB0E5;}
</style>
</head>
<body>
<h1>Thanks for shopping at our store :)</h1>
<h1>Your Customer Tag: '.$tag.'</h1>
<table>
<tr>
<th>Product Name</th>
<th>Product ID</th>
<th>Product Price</th>
</tr>
'.$list.'
</table>
<a href="http://localhost/smart_grocery_cart/product_list.php"><h1>