5.28. קודי QR ו-AprilTags

המזהים שראינו עד כה – רכיבים/כתמים (blobs), קווים, מעגלים, מלבנים – מאתרים מאפיינים גאומטריים: מיקומים וקווי מתאר שאותם מפרש שלב מאוחר יותר. שאר המזהים מאתרים מאפיינים סמליים: תבניות מודפסות שהמבנה החזותי שלהן קיים במיוחד כדי לקודד מטען (payload). המצלמה מאתרת אותן, המפענח קורא את הביטים, ומה שחוזר אינו מיקום אלא מחרוזת (או מזהה) שמדפיס הסמל בחר בכוונה.

שתי משפחות כאלה שולטות ביישומי מצלמות קטנות. קודי QR נושאים טקסט שרירותי, כתובות URL, כרטיסי קשר, או מטענים בינאריים – הקודים הדו-ממדיים הפונים לצרכן שמופיעים על כרזות, אריזות וכרטיסי עלייה למטוס. AprilTags נושאים מזהה מספרי יחיד מתוך קבוצה קבועה קטנה, מפוענחים במהירות אפילו ממרחק רב, ו(כאשר מסופקים מקדמי העדשה הפנימיים) מדווחים על תנוחה בעלת 6 דרגות חופש בפריים המצלמה – הקודים הדו-ממדיים הפונים לרובוטיקה שמסמנים רחפנים, מטרות כיול ונקודות ייחוס. שני המזהים מחזירים אובייקטי תוצאה עם אותו אוצר מילים של תיבה תוחמת שבו משתמשים מזהי הרכיבים/כתמים והמלבנים, אבל המטען עושה אותם שונים באמת מכל מה שכוסה עד כה.

5.28.1. קודי QR

find_qrcodes() סורק את הפריים לאיתור קודי QR ומחזיר רשימה של אובייקטי תוצאה מסוג QRCode:

codes = img.find_qrcodes()

for c in codes:
    img.draw_rectangle(c.rect, color=(0, 255, 0))
    for corner in c.corners:
        img.draw_circle((corner[0], corner[1], 4),
                        color=(0, 255, 0))
    print(c.payload)

המזהה מקבל פרמטר אופציונלי יחיד roi כדי להגביל את החיפוש. הוא זקוק לקלט בגווני אפור – פריים צבעוני מומר באופן פנימי לפני הפענוח.

כל זיהוי נושא את התיבה התוחמת (x, y, w, h, rect), את ארבע הפינות שזוהו (corners, המרובע ההיטלי שמתווים תבניות האיתור של קוד ה-QR), ואת המטען המפוענח כמחרוזת. הפינות הן הדבר הנכון לצייר בעת הוספת הערות לזיהוי – קוד QR הנצפה מחוץ לציר אינו מיושר לצירים והתיבה התוחמת נותנת רק קו מתאר רופף.

המטא-נתונים של המפענח מכסים את כל מה שמפענח ה-QR למד בדרך. version הוא גרסת קוד ה-QR, 1 – 40, הקובעת את גודל רשת המודולים (קוד גרסה 1 הוא ברוחב 21 מודולים, קוד גרסה 40 הוא 177). ecc_level הוא רמת תיקון השגיאות (0 – 3 עבור L / M / Q / H); רמות גבוהות יותר שומרות יותר מילות קוד לתיקון שגיאות ושורדות יותר נזק במחיר של פחות מקום למטען. mask הוא דפוס המסכה (0 – 7) שהמקודד בחר כדי למזער בלבול אצל המפענח. data_type הוא הקידוד שעליו דיווח המפענח – מספרי, אלפאנומרי, בינארי, או קאנג’י – והדגלים is_numeric / is_alphanumeric / is_binary / is_kanji חושפים את אותו ערך כבוליאנים נוחים יותר.

eci הוא ערך ה-Extended Channel Interpretation, המזהה את קידוד הטקסט שבו נמצאים הבייטים (UTF-8, ISO-8859-1, וכן הלאה). קוד QR מחומר מודפס שרירותי אינו בהכרח UTF-8; יישום שצריך לפענח את הבייטים נכון בודק את eci ומפענח בהתאם. המקרה של קאנג’י בפרט: MicroPython אינו מנתח קידוד קאנג’י, ולכן יש להתייחס למטען is_kanji כמערך בייטים ולפענח אותו על ידי היישום.

שימוש טיפוסי: מצלמה קוראת קודי QR ממסוע ומדווחת את המטען המפוענח למארח. המצלמה מריצה את find_qrcodes() פעם אחת לכל פריים, עוברת על הרשימה שהוחזרה, בוחרת את הקודים שה-data_type שלהם תואם את מה שהיישום מצפה לו, ומעבירה את c.payload דרך UART או USB. נתוני התיבה התוחמת והפינות שימושיים לתצוגה המקדימה ב-IDE אך אינם מה שמעניין את המארח.

5.28.2. AprilTags

find_apriltags() סורק את הפריים לאיתור AprilTags ומחזיר רשימה של אובייקטי תוצאה מסוג AprilTag:

tags = img.find_apriltags(families=image.TAG36H11)

for t in tags:
    img.draw_rectangle(t.rect, color=(0, 255, 0))
    img.draw_cross(t.cx, t.cy, color=(0, 255, 0))
    print(t.id, t.decision_margin)

AprilTags נבדלים מקודי QR במטרות התכנון שלהם. קוד QR בנוי כדי לקודד נתונים שרירותיים בסמל צפוף יחיד שהמשתמש קורא פעם אחת מטווח קרוב. AprilTag בנוי כדי לקודד מזהה קטן בסמל דליל שהמצלמה קוראת ברציפות ממרחק, עם כמה שיותר סובלנות לשגיאות כפי שמאפשר קוד ה-Hamming של המשפחה שלו. ההתפשרות מתבטאת בשני הכיוונים: קוד QR יכול לשאת מאות בייטים אך צריך להיקרא מקרוב; AprilTag נושא רק כמה מאות מזהים ייחודיים אך נקרא באמינות ממרחק של מטרים.

מילת המפתח families מקבלת מסכת ביטים של משפחות התגיות לפענוח. המשפחות הזמינות הן image.TAG16H5, image.TAG25H9, image.TAG36H10, image.TAG36H11, image.TAGCIRCLE21H7, image.TAGCIRCLE49H12, image.TAGCUSTOM48H12, image.TAGSTANDARD41H12, ו-image.TAGSTANDARD52H13. כל משפחה מתפשרת בין מספר המזהים לבין החוסן. מספר ה-H בשם הוא מרחק ה-Hamming המינימלי בין שני קודים כלשהם במשפחה – כמה ביטים חייבים להתהפך לפני שקוד תקף אחד הופך לאחר – ל-TAG16H5 יש 30 מזהים במרחק 5, ל-TAG25H9 יש 35 מזהים במרחק 9, ול-TAG36H11 (ברירת המחדל והנפוצה ביותר) יש 587 מזהים במרחק 11. המזהה מתקן עד שתי שגיאות ביט ללא קשר למשפחה, ולכן המרחק מכריע עד כמה התיקון הזה מסוכן: דפוס אקראי בפריים רועש צריך רק לנחות בטווח של שני ביטים מקוד תקף כדי להתפענח כזיהוי שגוי, והמשפחות בעלות המרחק הגבוה מפזרות את הקודים שלהן בדלילות כה רבה עד שהתנגשויות כאלה הופכות נדירות – הסיבה שבגללה TAG36H11 היא הבחירה המומלצת. זמן הזיהוי גדל עם מספר המשפחות המופעלות, ולכן יישום מפעיל רק את מה שהוא באמת מדפיס. מסכת הביטים היא ה-OR הביטי של קבועי המשפחות כאשר נדרשות מספר משפחות בקריאה אחת.

כל זיהוי נושא את אוצר המילים של התיבה התוחמת – x, y, w, h, rect, area, מרכזי מסה שלמים ותת-פיקסליים (cx, cy, cxf, cyf) – ואת ארבע הפינות שזוהו (corners). שדות הזיהוי באים בהמשך: id הוא המזהה המספרי בתוך המשפחה (0 – 586 עבור TAG36H11), family הוא קבוע המשפחה המספרי, ו-name הוא שם המשפחה כמחרוזת.

שדות איכות ההתאמה הם מה שיישום משתמש בו כדי לסנן זיהויים. decision_margin הוא ציון ביטחון בטווח 0.0 – 1.0; גבוה יותר עדיף, וסינון זיהויים מתחת ל-decision_margin > 0.1 מנקה את רוב הפגיעות השגויות ללא עלות. hamming סופר את שגיאות הביט שהמפענח קיבל עבור תגית זו – נמוך יותר עדיף, 0 משמעו פענוח מושלם. goodness הוא מדד היסטורי לאיכות התמונה שהמפענח הנוכחי כבר אינו מחשב; הוא תמיד 0.0 וניתן להתעלם ממנו.

5.28.3. תנוחה מתוך מקדמים פנימיים

המאפיין המהפכני של find_apriltags(), זה שמצדיק את AprilTags כנקודת הייחוס המועדפת ברובוטיקה, הוא שהמתודה יכולה לשחזר את התנוחה בעלת 6 דרגות החופש של התגית בפריים המצלמה ישירות מהפינות שזוהו ומקבוצה קטנה של מקדמי כיול פנימיים. המקדמים הפנימיים הם אורכי המוקד של המצלמה בצירים X ו-Y בפיקסלים (fx, fy) והמרכז האופטי בפיקסלים (cx, cy), כל ארבעתם נמדדים על ידי היישום פעם אחת בעזרת הליך כיול ומקודדים קשיחות לאחר מכן.

כאשר מסופקים המקדמים הפנימיים, ה-AprilTag המוחזר ממלא את שדות x_translation, y_translation, z_translation שלו במיקום התגית יחסית למצלמה, ואת x_rotation, y_rotation, z_rotation (ואת ה-rotation הכפול לשם סימטריה) בכיוון התגית. ללא המקדמים הפנימיים, כל ששת השדות הם 0.0 והיישום אחראי לכל אומדן תנוחה שהוא צריך.

שדות ההזזה מדווחים ביחידות רוחב תגית: המפענח מתייחס לתגית כרוחב 1 יחידה, ולכן היישום מכפיל כל הזזה ברוחב הפיזי של התגית המודפסת כדי לקבל מרחקים מטריים. תגית המודפסת ברוחב 100 מ“מ ומדווחת z_translation = 8.3 נמצאת במרחק 830 מ“מ מהמצלמה; אותה תגית המודפסת ברוחב 50 מ“מ באותו מרחק תדווח z_translation = 16.6. שדות הסיבוב הם ברדיאנים ואינם זקוקים לשינוי קנה מידה.

אומדן התנוחה הוא הבסיס למגוון רחב של יישומי רובוטיקה: עגינת רובוט לתחנת טעינה המסומנת בתגית, מעקב אחר שביל נקודות ציון מודפס, שחזור תנוחת המצלמה עצמה ממספר תגיות ידועות בסביבה. מצלמה שמכירה את המקדמים הפנימיים, רואה תגית, ויש לה מיקום בעולם האמיתי עבור התגית – יש לה, באותו חשבון, מיקום בעולם האמיתי עבור עצמה.

5.28.4. מתי לבחור במה

קודי QR ו-AprilTags פותרים בעיות שונות. הבחירה ביניהם מסתכמת במה שהסמל המודפס נושא.

כאשר היישום צריך לשאת נתונים שרירותיים דרך הסמל המודפס – כתובת URL, מחרוזת מספר סידורי, רשומת קשר – קוד QR הוא הבחירה הנכונה. מאות בייטים נכנסים בקוד בגודל סביר, הקידוד פומבי ונתמך בכל סמארטפון, והמפענח מתמודד עם סיבוב, נזק מתון, וזוויות אלכסוניות.

כאשר היישום צריך מזהה קטן הנקרא ברציפות ממרחק עם תנוחה אופציונלית – נקודת ייחוס על רובוט נע, מטרת כיול בחדר, סמן עגינה בתחנת טעינה – AprilTag הוא הבחירה הנכונה. מאות מזהים מספיקים בשפע למקרה השימוש, קוד ה-Hamming משחזר משגיאות ביט שהיו מביסות קוד QR, ואומדן התנוחה הוא חינם ברגע שהמקדמים הפנימיים מכוילים.

חלק מהיישומים משתמשים בשניהם: AprilTag מסמן מיקום ידוע וקוד QR משויך (מודפס לצדו) נושא את המטא-נתונים על מה שהמיקום הזה אומר. שני המזהים רצים באופן עצמאי על אותו פריים והיישום מתאם בין התיבות התוחמות שלהם כדי להתאים כל תגית לקוד המלווה שלה.