From 46d52ea2a5f8c92a7fe1644ccd467bec4255e964 Mon Sep 17 00:00:00 2001 From: Neucrack Date: Wed, 19 Jun 2024 10:42:28 +0800 Subject: [PATCH] add self learn classifier support --- docs/doc/assets/self_learn_classifier.jpg | Bin 0 -> 38991 bytes docs/doc/en/basic/maixpy_upgrade.md | 11 ++ docs/doc/en/vision/image_ops.md | 2 + docs/doc/en/vision/self_learn_classifier.md | 38 ++++-- docs/doc/zh/basic/maixpy_upgrade.md | 10 +- docs/doc/zh/vision/image_ops.md | 2 + docs/doc/zh/vision/self_learn_classifier.md | 30 ++++- .../ai_vision/nn_self_learn_classifier.py | 64 +++++++++ .../ai_vision/nn_self_learn_classifier_cam.py | 3 + examples/vision/image_basic/binary.py | 2 + examples/vision/image_basic/image_load.py | 2 + maix/maix_resize.py | 2 + maix/v1/image.py | 2 + maix/version.py | 2 +- projects/app_self_learn_classifier/.gitignore | 5 + projects/app_self_learn_classifier/app.png | Bin 0 -> 2435 bytes projects/app_self_learn_classifier/app.yaml | 10 ++ projects/app_self_learn_classifier/main.py | 121 ++++++++++++++++++ 18 files changed, 290 insertions(+), 16 deletions(-) create mode 100644 docs/doc/assets/self_learn_classifier.jpg create mode 100644 examples/vision/ai_vision/nn_self_learn_classifier.py create mode 100644 examples/vision/ai_vision/nn_self_learn_classifier_cam.py create mode 100644 projects/app_self_learn_classifier/.gitignore create mode 100644 projects/app_self_learn_classifier/app.png create mode 100644 projects/app_self_learn_classifier/app.yaml create mode 100644 projects/app_self_learn_classifier/main.py diff --git a/docs/doc/assets/self_learn_classifier.jpg b/docs/doc/assets/self_learn_classifier.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a401b9efb6895e29bec464f0ad70c05ce2389ffb GIT binary patch literal 38991 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 HFTSv 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
MaLFYc+=P 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 If you are comfortable using the terminal, you can also update MaixPy by using `pip install MaixPy -U` in the terminal. + + +And you can download `wheel` file (`.whl`format) manually, and send to device(transfer method see [MaixVision Usage](./maixvision.md)), then install by `pip install *****.whl` command. diff --git a/docs/doc/en/vision/image_ops.md b/docs/doc/en/vision/image_ops.md index 589b9d67..a00e89b7 100644 --- a/docs/doc/en/vision/image_ops.md +++ b/docs/doc/en/vision/image_ops.md @@ -64,6 +64,8 @@ MaixPy provides the `maix.image.load` method, which can read images from the fil from maix import image img = image.load("/root/image.jpg") +if img is None: + raise Exception(f"load image failed") print(img) ``` diff --git a/docs/doc/en/vision/self_learn_classifier.md b/docs/doc/en/vision/self_learn_classifier.md index 22e8daf9..fd4f5bc5 100644 --- a/docs/doc/en/vision/self_learn_classifier.md +++ b/docs/doc/en/vision/self_learn_classifier.md @@ -4,20 +4,24 @@ title: MaixPy Self-Learning Classifier ## Introduction to MaixPy Self-Learning Classifier -Typically, to recognize new categories, it is necessary to collect a new dataset and train on a computer, which can be cumbersome and complex. This method eliminates the need for computer-based training, allowing for immediate learning of new objects directly on the device, suitable for less complex scenarios. +Usually, to recognize new categories, we need to collect a dataset on a computer and retrain the model, which is a cumbersome and difficult process. Here, we provide a method that allows for instant learning of new objects directly on the device without the need for computer-side training, suitable for less complex scenarios. -For example, if there are a drink bottle and a mobile phone in front of you, take a photo of each to serve as the basis for two categories. Then, collect several photos from different angles of each item, extract their features and save them. During recognition, the image's features are compared with the saved feature values, and the closest match determines the classification. +For example, if there is a bottle and a phone in front of you, you can use the device to take a picture of each as the basis for two classifications. Then, you collect a few more pictures of them from different angles, extract their features and save them. During recognition, the feature values of the image are compared with the saved feature values, and the classification that is more similar to the saved features is considered the corresponding classification. ## Using the Self-Learning Classifier in MaixPy -Steps: +The default image comes with the [Self-Learning Classification APP](https://maixhub.com/app/30), which you can use directly to get familiar with the process. + +![](../../assets/self_learn_classifier.jpg) -* Collect n classification images. -* Collect n*m images, m images for each category, order does not matter. -* Start learning. -* Recognize images and output results. +Steps: +* Click the `+ Class` button to collect n classification (class) images. The object needs to be within the white frame on the screen while collecting the images. +* Click the `+ Sample` button to collect m sample images. Collect some images for each classification. The order does not matter, and the number is flexible. It's best to take pictures from different angles, but not too different. +* Click the `Learn` button to start learning. The device will automatically classify and learn based on the collected classification and sample images, obtaining the characteristics of the classifications. +* Align the object with the center of the screen, recognize the image, and output the result. The screen will show the classification it belongs to and the similarity distance to this classification. The closer the similarity distance, the more similar it is. +* The feature values ​​learned by this APP will be saved to `/root/my_classes.bin`, so the last one will be automatically loaded after exiting the application or restarting it. -Simplified version of the code, for the full version please refer to the complete code in the example. +Simplified version of the code, for the complete version, please refer to the [examples](https://github.com/sipeed/maixpy/tree/main/examples/vision/ai_vision) for the full code. ```python from maix import nn, image @@ -34,7 +38,6 @@ sample_4 = image.load("/root/sample_4.jpg") sample_5 = image.load("/root/sample_5.jpg") sample_6 = image.load("/root/sample_6.jpg") - classifier.add_class(img1) classifier.add_class(img2) classifier.add_class(img3) @@ -51,3 +54,20 @@ img = image.load("/root/test.jpg") max_idx, max_score = classifier.classify(img) print(max_idx, max_score) ``` + +## Storing and Loading Learned Feature Values + +Use the `save` function to store the learned feature values. This will generate a binary file containing the feature values of the objects. When you need to use it again, simply use the `load` function to load the feature values. + +```python +classifier.save("/root/my_classes.bin") +classifier.load("/root/my_classes.bin") +``` + +If you have named each classification and stored them in the `labels` variable, you can also use: + +```python +classifier.save("/root/my_classes.bin", labels=labels) +labels = classifier.load("/root/my_classes.bin") +``` + diff --git a/docs/doc/zh/basic/maixpy_upgrade.md b/docs/doc/zh/basic/maixpy_upgrade.md index 99913e52..1628a14f 100644 --- a/docs/doc/zh/basic/maixpy_upgrade.md +++ b/docs/doc/zh/basic/maixpy_upgrade.md @@ -20,6 +20,14 @@ title: 更新 MaixPy * 在设置中设置 WiFi, 让系统联网。 * 点击设置应用中的 `更新 MaixPy` 进行更新。 +也可以执行 Python 代码调用系统命令来更新: +```python +import os -> 如果你会使用终端, 也可以在终端中使用 `pip install MaixPy -U` 来更新 MaixPy。 +os.system("pip install MaixPy -U") +``` + +> 如果你会使用终端, 也可以直接在终端中使用 `pip install MaixPy -U` 来更新 MaixPy。 + +另外你也可以手动下载`wheel` 文件(`.whl`格式)传输到设备(传输方法见后文[MaixVision 使用](./maixvision.md))后通过 `pip install ******.whl` 命令来安装。 diff --git a/docs/doc/zh/vision/image_ops.md b/docs/doc/zh/vision/image_ops.md index 1b176075..4bc3869c 100644 --- a/docs/doc/zh/vision/image_ops.md +++ b/docs/doc/zh/vision/image_ops.md @@ -64,6 +64,8 @@ MaixPy 提供了`maix.image.load`方法,可以从文件系统读取图像: from maix import image img = image.load("/root/image.jpg") +if img is None: + raise Exception(f"load image failed") print(img) ``` diff --git a/docs/doc/zh/vision/self_learn_classifier.md b/docs/doc/zh/vision/self_learn_classifier.md index 87a9ba43..a5c0555f 100644 --- a/docs/doc/zh/vision/self_learn_classifier.md +++ b/docs/doc/zh/vision/self_learn_classifier.md @@ -12,14 +12,19 @@ title: MaixPy 自学习分类器 ## MaixPy 中使用自学习分类器 +默认镜像自带了 [自学习分类 APP](https://maixhub.com/app/30),可以直接尝试使用熟悉使用流程。 + +![](../../assets/self_learn_classifier.jpg) + 步骤: -* 采集 n 张分类图。 -* 采集 n*m 张图,每个分类采集 m 张,顺序无所谓。 -* 启动学习。 -* 识别图像输出结果。 +* 点击`+ Class` 按钮, 采集 n 张分类(class)图,采集图时物体需要在屏幕的白色框中。 +* 点击`+ Sample`按钮,采集 m 张样本图,每个分类都采集一些,顺序无所谓,张数也比较随意,最好是在各个角度拍一点,不要差距过大。 +* 点击`Learn`按钮,启动学习,会自动根据采集的分类图和样本图进行分类学习,得到分类的特征。 +* 屏幕中央对准物体,识别图像输出结果,可以看到屏幕显示了所属的分类,以及和这个分类的相似距离,相似距离越近则越相似。 +* 此 APP 学习后的特征值会存到`/root/my_classes.bin`,所以退出应用或者重启了仍然会自动加载上一次的。 +简洁版本代码,完整版本请看[例程](https://github.com/sipeed/maixpy/tree/main/examples/vision/ai_vision)里面的完整代码。 -简洁版本代码,完整版本请看例程里面的完整代码。 ```python from maix import nn, image @@ -53,5 +58,20 @@ max_idx, max_score = classifier.classify(img) print(maix_idx, max_score) ``` +## 储存和加载学习到的特征值 + +使用 `save` 函数进行储存,会得到一个二进制文件,里面存了物体的特征值。 +再使用时用`load`函数进行加载即可。 + +```python +classifier.save("/root/my_classes.bin") +classifier.load("/root/my_classes.bin") +``` + +如果你给每一个分类命名了,比如存到了`labels`变量,也可以使用: +```python +classifier.save("/root/my_classes.bin", labels = labels) +labels = classifier.load("/root/my_classes.bin") +``` diff --git a/examples/vision/ai_vision/nn_self_learn_classifier.py b/examples/vision/ai_vision/nn_self_learn_classifier.py new file mode 100644 index 00000000..f3f1f47e --- /dev/null +++ b/examples/vision/ai_vision/nn_self_learn_classifier.py @@ -0,0 +1,64 @@ +from maix import nn, image, display, app, time + +disp = display.Display() +classifier = nn.SelfLearnClassifier(model="/root/models/mobilenet_v2_no_top.mud") + +class_path = [ + "/root/1.jpg", + "/root/2.jpg", + "/root/3.jpg" +] + +samples_path = [ + "/root/sample_1.jpg", + "/root/sample_2.jpg" +] + + +class_images = [] +for path in class_path: + # load image from file + img = image.load(path) + if img is None: + raise Exception(f"load image {path} failed") + class_images.append(img) + # add new class + classifier.add_class(img) + +sample_images = [] +for path in samples_path: + # load image from file + img = image.load(path) + if img is None: + raise Exception(f"load image {path} failed") + sample_images.append(img) + # add new class + classifier.add_sample(img) + +if len(sample_images) > 0: + print("-- start learn") + classifier.learn() + print("-- learn complete") + +classifier.learn() + +img = image.load("/root/test.jpg") +result = classifier.classify(img) +print(f"distances: {result}") +print(f"min distance idx: {result[0][0]}, distance: {result[0][1]}") + +# show result +img_show = image.Image(disp.width(), disp.height()) +img = img.resize(disp.width() // 2, disp.height() // 2, image.Fit.FIT_CONTAIN) +img_show.draw_image(0, 0, img) +img_res = class_images[result[0][0]].resize(disp.width() // 2, disp.height() // 2, image.Fit.FIT_CONTAIN) +img_show.draw_image(disp.width() // 2, 0, img_res) +img_show.draw_string(2, disp.height() // 2 + 80, f"distance: {result[0][1]}", scale=1.5) +img_show.draw_string(2, disp.height() // 2 + 40, f"test.jpg", scale=1.5) +img_show.draw_string(disp.width() // 2 + 2, disp.height() // 2 + 40, class_path[result[0][0]], scale=1.5) + +disp.show(img_show) + + +while not app.need_exit(): + time.sleep_ms(100) diff --git a/examples/vision/ai_vision/nn_self_learn_classifier_cam.py b/examples/vision/ai_vision/nn_self_learn_classifier_cam.py new file mode 100644 index 00000000..19f5e886 --- /dev/null +++ b/examples/vision/ai_vision/nn_self_learn_classifier_cam.py @@ -0,0 +1,3 @@ + +# see https://github.com/sipeed/maixpy/tree/main/projects/app_self_learn_classifier + diff --git a/examples/vision/image_basic/binary.py b/examples/vision/image_basic/binary.py index 752fa712..3cd8de52 100644 --- a/examples/vision/image_basic/binary.py +++ b/examples/vision/image_basic/binary.py @@ -2,6 +2,8 @@ # 1. load image src_img = image.load("test.jpg") +if src_img is None: + raise Exception(f"load image {file_path} failed") # 2. binarize the image thresholds = ((0, 100, 20, 80, 10, 80)) diff --git a/examples/vision/image_basic/image_load.py b/examples/vision/image_basic/image_load.py index 952517bb..744d5e41 100644 --- a/examples/vision/image_basic/image_load.py +++ b/examples/vision/image_basic/image_load.py @@ -2,6 +2,8 @@ file_path = "/maixapp/share/icon/detector.png" img = image.load(file_path, format = image.Format.FMT_RGBA8888 if file_path.endswith(".png") else image.Format.FMT_RGB888) +if img is None: + raise Exception(f"load image {file_path} failed") disp = display.Display() disp.show(img, fit = image.Fit.FIT_CONTAIN) diff --git a/maix/maix_resize.py b/maix/maix_resize.py index e610c1ff..a842cf66 100644 --- a/maix/maix_resize.py +++ b/maix/maix_resize.py @@ -15,6 +15,8 @@ def main_cli(): from maix import image img = image.load(args.input) + if img is None: + raise Exception(f"load image {args.input} failed") fit = { 'fill': image.Fit.FIT_FILL, 'contain': image.Fit.FIT_CONTAIN, diff --git a/maix/v1/image.py b/maix/v1/image.py index 201d4cf6..cf198d55 100644 --- a/maix/v1/image.py +++ b/maix/v1/image.py @@ -42,6 +42,8 @@ def __init__(self, path=None, copy_to_fb=False, width=640, height=480, do_nothin self.__img = image.Image(width, height) else: self.__img = image.load(path) + if self.__img is None: + raise Exception(f"load image {path} failed") def get_priv_img(self): return self.__img diff --git a/maix/version.py b/maix/version.py index ad73408b..3674ece4 100644 --- a/maix/version.py +++ b/maix/version.py @@ -3,6 +3,6 @@ version_major = 4 version_minor = 3 -version_patch = 0 +version_patch = 2 __version__ = "{}.{}.{}".format(version_major, version_minor, version_patch) diff --git a/projects/app_self_learn_classifier/.gitignore b/projects/app_self_learn_classifier/.gitignore new file mode 100644 index 00000000..babf76a7 --- /dev/null +++ b/projects/app_self_learn_classifier/.gitignore @@ -0,0 +1,5 @@ + +build +dist +/CMakeLists.txt + diff --git a/projects/app_self_learn_classifier/app.png b/projects/app_self_learn_classifier/app.png new file mode 100644 index 0000000000000000000000000000000000000000..0e506c27d7ca714956d057e150802e2e0dc769b1 GIT binary patch literal 2435 zcmV-}34Hd6P)zvojIeYK3_x_Um*PWU3JL{~Ov(MV=VN6AU(||7lw*vPAtATF;Hvw~jW2$~; zqBp?dz!30dU@@>9SOa_yxLa+0H89>Z3GhBW*i)2aqdLPujS348;CNsm@C4P^3|s}g zyG}8a)a&}?m%xnL1ciU$7c)422d)Li3@Yd%>f_&~IwD zTmS$!(kzRr3i?iiVul;DWDB8li?qS2vV!JV5c9RhE!zTsUD)OOMS{j#6f@UE4Dcv! z$X<{GODzid6L6G?5MTjrnc7R#84iTpXd(lch#wq3TG*YAgp4&20j$Rl7LOFQAF$hz zkjW+zfXVnp=T`tY+o6b?Oe6pg;1``=0bssE5s#S&08YR^o&{bm;+8lRvCTvP@M-+y zbP@NULjk)?1mN~dmdEw39RL#nzz+OmgKO`6)&vgl3jAfW>qX!e6Gv8$uRJXRKju)t zIukhf`2^5qw+KAap@2(G-~eyOU-ooGN_`@*pmNKG0Z`Jbx z26!*96+f6&MfmxWQ{Klx9+&6LjQ>qH5kV&%Y!800rr(p0Si!WMi;Dhl+Nk%)s(&=H zAUf4V05BVWkaYaxY#ZPM8Dci{8w!>l1i)EZzd5?rZmZ4rC7DD zTZAt5{+M&Tc^Of{A2)P<@@sjb@r9fk1QGh45&P$NVVAAIlm-`jF8~)7#9gFG#6N2~ zpJ#M$=Ro5dz!x$^%Pry>;NQ68Zs2V-H+c}}npolX@jwk1{xIV!z)OL9@s~fy6GVl@ z-hDZr^-!M7ooS zUWlo^O)UU#1)itSUEfocvgov_%H9H5#5$Vi#c z7?+%&{AR}A2#4eG7m{4s>h^~nM99pG5PW?6Coe+E&xV0ChUdYW#o?1Aor=te;6MyvB^zc5?2e^D|z5D)8NgF8ouK=I8ydt_e9K;dcca zE70kelXX*P3Gkry{{NzO|IQo=4SXbHBF5V844s_!z8RR&=lsHvJgM3z4cc3uPSeZ0 zX4m2L5tJCX_W3E(>Sb78yLUv2j474IN*S*oUr~#US2-8B4!BeM^!^BV8hBBEp5>=_ z9q?_P33Q1JVQ9cEV`UIyjx8=R0o3*llGndp=L9VZb!!#azaU_Am1!eV^gy#LF#$n- z!02YvCV*=jzWCBKa$ge|u@ z-|S_xY2bs{BlB@F6o3!q1dN8gt1B#coT-~tCIWyHa{@-vJZq})0bmIGTwvmvlkj+s z9X=Cl6Ng7kT&;4}U%8+*{$v@4>B2o?dnUyDy|v19MkhGx#V z;<`fXL;!Gy1KZCjvvbunXPL+WCOWdcEC~1S=IOiO#a1LpxXmAEn(18;bp{>EvZ86@ zwgKRN+;O|hQte5ujqd}F<1mkn6e7D!wn*Q81-I-PJ>SPxPv2BNrQnw5dp(afO_*g< zeS%wd^b*vh69k_SS``h-xMEY5sMl-NY@I~6_wP{}e)XYOTJluK!ceMvPby`N2*GXl z)EQ+24)ysnB_ZZdSg~S@0ZzglVNNvW{rnasdHs$38z9 zRP@ohVC??Au{MiD25^G|2e{Prc_$Q^K4N*UlJrt|ZTI*I50KV$79C)U>+?|f^DrRr5 z)TfHv{)>s0p<(0C>i&v^+Pi;usgs-ojH&XW{{bORCY;yS-D>~<002ovPDHLkV1oKU BgU0{> literal 0 HcmV?d00001 diff --git a/projects/app_self_learn_classifier/app.yaml b/projects/app_self_learn_classifier/app.yaml new file mode 100644 index 00000000..fe072818 --- /dev/null +++ b/projects/app_self_learn_classifier/app.yaml @@ -0,0 +1,10 @@ +id: self_learn_classifier +name: Self Learn Classifier +version: 1.0.1 +author: Sipeed Ltd +icon: app.png +desc: Learn anything on device, no PC training needed. +files: + - app.png + - app.yaml + - main.py diff --git a/projects/app_self_learn_classifier/main.py b/projects/app_self_learn_classifier/main.py new file mode 100644 index 00000000..f8798428 --- /dev/null +++ b/projects/app_self_learn_classifier/main.py @@ -0,0 +1,121 @@ +from maix import nn, image, camera, display, app, time, touchscreen +import os + +def get_learn_btn_rect(x, y, w, h, label): + s = image.string_size(label) + sc = image.string_size("a") + btn_w = s.width() + sc.width() * 2 + btn_h = s.height() + sc.height() * 2 + y = y - btn_h - 10 + return x, y, btn_w, btn_h, x + sc.width(), y + sc.height() + +def draw_btns(img: image.Image, btns): + for k, r in btns.items(): + img.draw_rect(r[0], r[1], r[2], r[3], image.COLOR_WHITE) + img.draw_string(r[4], r[5], k, image.COLOR_WHITE) + +def is_in_button(x, y, btn_pos): + return x > btn_pos[0] and x < btn_pos[0] + btn_pos[2] and y > btn_pos[1] and y < btn_pos[1] + btn_pos[3] + +def draw_string_center(img, msg): + img.draw_string((img.width() - image.string_size(msg, scale=2, thickness=6)[0]) // 2, img.height() // 2, msg, image.COLOR_WHITE, scale=2, thickness=6) + img.draw_string((img.width() - image.string_size(msg, scale=2, thickness=6)[0]) // 2, img.height() // 2, msg, image.COLOR_RED, scale=2, thickness=2) + +def main(disp): + cam = camera.Camera(disp.width(), disp.height()) + ts = touchscreen.TouchScreen() + + btns = {} + btns["Learn"] = get_learn_btn_rect(2, cam.height(), cam.width(), cam.height(), "Learn") + btns["< Exit"] = get_learn_btn_rect(2, btns["Learn"][3] + 10, cam.width(), cam.height(), "< Exit") + btns["+ Class"] = get_learn_btn_rect(2, cam.height() - btns["Learn"][3] - 50, cam.width(), cam.height(), "+ Class") + btns["+ Sample"] = get_learn_btn_rect(2, cam.height() - btns["Learn"][3] - btns["+ Class"][3] - 100, cam.width(), cam.height(), "+ Sample") + btns["Clear"] = get_learn_btn_rect(cam.width() - btns["Learn"][2], cam.height(), cam.width(), cam.height(), "Clear") + + + classifier = nn.SelfLearnClassifier(model="/root/models/mobilenet_v2_no_top.mud") + labels = [] + if os.path.exists("/root/my_classes.bin"): + labels = classifier.load("/root/my_classes.bin") + learn_times = 0 + show_msg = "" + show_msg_t = 0 + + res_max = None + last_pressed = False + while not app.need_exit(): + img = cam.read() + # recognize + crop = img.resize(classifier.input_width(), classifier.input_height(), image.Fit.FIT_COVER) + if classifier.class_num() > 0: + res_max = classifier.classify(crop)[0] + + # learn + x, y, pressed = ts.read() + if pressed: + if not last_pressed: + last_pressed = True + if is_in_button(x, y, btns["+ Class"]): + classifier.add_class(crop) + labels.append(f"Class {len(labels) + 1}") + classifier.save("/root/my_classes.bin", labels=labels) + elif is_in_button(x, y, btns["+ Sample"]): + classifier.add_sample(crop) + classifier.save("/root/my_classes.bin", labels=labels) + elif is_in_button(x, y, btns["< Exit"]): + app.set_exit_flag(True) + elif is_in_button(x, y, btns["Learn"]): + msg = "Learning ..." + draw_string_center(img, msg) + disp.show(img) + epoch = classifier.learn() + if epoch > 0: + learn_times += 1 + classifier.save("/root/my_classes.bin", labels=labels) + show_msg = "Learn complete" + show_msg_t = time.ticks_s() + else: + show_msg = "Already learned" + show_msg_t = time.ticks_s() + elif is_in_button(x, y, btns["Clear"]): + if os.path.exists("/root/my_classes.bin"): + os.remove("/root/my_classes.bin") + classifier.clear() + labels.clear() + res_max = None + learn_times = 0 + else: + last_pressed = False + + # show + min_len = min(img.width(), img.height()) + if img.width() == min_len: + x = 0 + y = (img.height() - min_len) // 2 + else: + x = (img.width() - min_len) // 2 + y = 0 + img.draw_rect(x, y, min_len, min_len, image.COLOR_WHITE, thickness=2) + img.draw_string(x, y + 2, f"class: {classifier.class_num()}, sample: {classifier.sample_num()}, learn: {learn_times}", image.COLOR_RED, scale=1.5) + img.draw_string(x, y + 4, f"class: {classifier.class_num()}, sample: {classifier.sample_num()}, learn: {learn_times}", image.COLOR_WHITE, scale=1.5) + if res_max: + img.draw_string(x, y + 30, f"{labels[res_max[0]]}", image.COLOR_WHITE, scale=2, thickness=6) + img.draw_string(x, y + 32, f"{labels[res_max[0]]}", image.COLOR_RED, scale=2, thickness=2) + img.draw_string(x, y + 62, f"similarity: {res_max[1]:.2f}", image.COLOR_WHITE, scale=1.5, thickness=4) + img.draw_string(x, y + 64, f"similarity: {res_max[1]:.2f}", image.COLOR_RED, scale=1.5, thickness=2) + draw_btns(img, btns) + if show_msg and time.ticks_s() - show_msg_t < 3: + draw_string_center(img, show_msg) + disp.show(img) + +disp = display.Display() +try: + main(disp) +except Exception: + import traceback + msg = traceback.format_exc() + img = image.Image(disp.width(), disp.height()) + img.draw_string(0, 0, msg, image.COLOR_WHITE) + disp.show(img) + while not app.need_exit(): + time.sleep_ms(100)