מצלמת אירועים GENX320¶
מודול מצלמת האירועים GENX320 הוא חיישן ראייה מבוסס-אירועים של Prophesee עם רזולוציה של 320x320 ודיוק זמני ברמת המיקרו-שנייה.
לגיליון הנתונים המלא, לתמונות ולהזמנה ראו את עמוד המוצר של מצלמת האירועים GENX320.
הערה
נתמך ב-OpenMV H7 Plus, ב-RT1062 וב-N6.
עיקרי הדברים¶
חיישן ראייה מבוסס-אירועים ברזולוציה 320x320
טווח דינמי של 140 dB, ללא טשטוש תנועה
קצב פלט היסטוגרמת-אירועים של 375 Hz ומעלה
צריכת ההספק מתאימה את עצמה לפעילות הסצנה — מתחילה ב-~3 mW
פועל מ-<5 לוקס ועד אור שמש בהיר ללא חשיפה אוטומטית
מפיק פריימים בגווני אפור או זרמי אירועים גולמיים
שימוש¶
ה-GENX320 הוא חיישן ראייה מבוסס-אירועים — במקום לקרוא את כל מערך ה-320x320 לפי שעון פריימים קבוע, כל פיקסל מדווח על ”אירועים“ אסינכרוניים ברגע שהוא מזהה שינוי בהירות. כל אירוע נושא עמו קואורדינטת X/Y, קוטביות ON/OFF (בהיר→כהה או כהה→בהיר) וחותמת זמן ברמת המיקרו-שנייה. משם נובעים הדיוק הזמני ברמת המיקרו-שנייה של החיישן, היעדר טשטוש התנועה, הטווח הדינמי הגבוה מאוד וצריכת ההספק שמתאימה את עצמה לפעילות. סצנות סטטיות אינן מייצרות נתונים.
קושחת OpenMV חושפת את ה-GENX320 דרך csi.CSI עם cid= csi.GENX320. זמינים שני מצבי פעולה:
מצב היסטוגרמה (ברירת מחדל) — אירועים נצברים על השבב לתאים פר-פיקסל ומדווחים כפריים בגווני אפור ברזולוציה 320x320 בקצב הניתן להגדרה (~20-350 FPS). החיישן מתנהג כמו מצלמה רגילה, ולכן כל שגרות עיבוד התמונה הסטנדרטיות (
Image.find_blobs, פלטות וכו«) עובדות ישירות.מצב אירועים — אירועים גולמיים זורמים אל
ndarrayשל numpy עם חותמות זמן מלאות ברמת המיקרו-שנייה, עבור יישומים שזקוקים לפירוט הזמני ולא לפריים שכבר חולק לתאים.
מצב היסטוגרמה¶
במצב היסטוגרמה ה-GENX320 מפיק פריימים בגווני אפור שבהם כל פיקסל מקודד את פעילות האירועים האחרונה במיקום שלו. פיקסלים מעל קו הבסיס של הבהירות הם אירועי ON (בהירות עולה), ומתחתיו הם אירועי OFF (בהירות יורדת). בהירות קו הבסיס שבברירת המחדל היא 128 וצעד הניגודיות פר-אירוע הוא 16 — העלו את הניגודיות כדי שהאירועים יבלטו:
import csi
import time
csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize((320, 320))
csi0.brightness(128) # baseline (default 128)
csi0.contrast(16) # per-event step
csi0.framerate(50) # 20-350 FPS
clock = time.clock()
while True:
clock.tick()
img = csi0.snapshot()
print(clock.fps())
csi.CSI.brightness, csi.CSI.contrast ו-csi.CSI.framerate הם שלושת הכפתורים שמעצבים את פלט ההיסטוגרמה.
פלט צבעוני¶
הגדירו את csi.CSI.color_palette ל-image.PALETTE_EVT_LIGHT עבור רקע בהיר או ל-image.PALETTE_EVT_DARK עבור רקע כהה — מנהל ההתקן מפיק פריימים בפורמט RGB565 תוך שימוש ישיר בפלטה:
csi0.color_palette(image.PALETTE_EVT_LIGHT)
כיול פיקסלים חמים (hot-pixel)¶
חיישני אירועים צוברים ”פיקסלים חמים“ שמפיקים אירועים מזויפים. הריצו את csi.IOCTL_GENX320_CALIBRATE מול סצנה סטטית כדי לנטרל אותם. מנהל ההתקן בונה ספירת פגיעות פר-פיקסל ברזולוציה 320x320, מחשב את הממוצע וסטיית התקן, ומנטרל כל פיקסל שספירתו גבוהה מ-mean + sigma * stddev — ואז הפיקסלים המנוטרלים מפסיקים להפיק אירועים ברמת החיישן.
שני פרמטרים שולטים בכיול:
event_count— כמה אירועים לספור לפני חישוב הסטטיסטיקה. הלולאה לוכדת פריימים עד שסך האירועים המצטבר חוצה את התקציב הזה. ספירות גבוהות יותר נותנות הערכה אמינה יותר במחיר זמן כיול ארוך יותר.10000היא נקודת התחלה סבירה.sigma— מכפיל הסף על סטיית התקן. ערכים נמוכים יותר אגרסיביים יותר (יותר פיקסלים מנוטרלים); ערכים גבוהים יותר שמרניים יותר.0.5הוא ברירת מחדל טובה.
כוונו תחילה את החיישן אל סצנה סטטית כדי שאירועים הנובעים מתנועה לא ייספרו לחובת פיקסלים שלמעשה תקינים:
csi0.snapshot(time=5000) # let the user steady the camera
disabled = csi0.ioctl(csi.IOCTL_GENX320_CALIBRATE, 10000, 0.5)
print(f"disabled {disabled} hot pixels")
מסנן נגד הבהוב (AFK)¶
מקורות אור מחזוריים (פלואורסצנטיים, צגים מונעי LED) מייצרים כמויות עצומות של אירועים מיותרים. מסנן ה-AFK דוחה אירועים שבהם הפיקסל מתחלף בתדר שבתוך רצועה — הפעילו אותו דרך csi.IOCTL_GENX320_SET_AFK עם קצות הרצועה בהרץ:
csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 1, 130, 160) # 130-160 Hz
csi0.ioctl(csi.IOCTL_GENX320_SET_AFK, 0) # disable
ערכות הטיה (Bias) מוגדרות מראש¶
כל פיקסל ב-GenX320 מריץ קצה אנלוגי קדמי עם כמה הטיות הניתנות להגדרה. הן שולטות יחד ברגישות, ברעש, ברוחב הפס של הפיקסל ובקצב האירועים — השילוב הנכון תלוי בסצנה. ההטיות הבודדות הן:
DIFF_ON — סף הניגודיות החיובי של המשווה. פיקסל מפיק אירוע ON כאשר התאורה הלוגריתמית שלו עלתה במידה הזו. נמוך יותר = רגיש יותר למעברים בהירים.
DIFF_OFF — סף הניגודיות השלילי של המשווה (המקבילה הסימטרית עבור אירועי OFF). נמוך יותר = רגיש יותר למעברים כהים.
FO — תדר הניתוק של המסנן מעביר-הנמוכים של הפיקסל. גבוה יותר = רוחב פס רחב יותר לפיקסל (תגובה מהירה יותר, השהיה נמוכה יותר) אך יותר פעילות רעש רקע.
HPF — תדר הניתוק של המסנן מעביר-הגבוהים. גבוה יותר = דחייה חזקה יותר של שינויי בהירות איטיים; רק מעברים מהירים מגיעים אל המשווים. שימושי כדי להתעלם מסחיפה סביבתית.
REFR — תקופת ההתאוששות (refractory). לאחר שפיקסל מפיק אירוע, הוא נשאר במצב איפוס למשך הזמן הזה לפני שיוכל להפיק אירוע נוסף. גבוה יותר = זמן מת ארוך יותר, שימושי להגבלת קצב האירועים פר-פיקסל.
לאחר csi.CSI.reset מנהל ההתקן מחיל את csi.GENX320_BIASES_LOW_NOISE, ולא את csi.GENX320_BIASES_DEFAULT — ברירות המחדל של גיליון הנתונים מפיקות קצב אירועי רקע גבוה בהרבה, ולכן LOW_NOISE משמש כנקודת התחלה כדי לשמור על זרם שקט. קראו ל-csi.IOCTL_GENX320_SET_BIASES עם ערכה מוגדרת מראש אחרת כאשר היישום זקוק ליותר רגישות או רוחב פס.
csi.IOCTL_GENX320_SET_BIASES מחיל אחת מחמש ערכות מוגדרות מראש:
csi.GENX320_BIASES_DEFAULT— ברירות המחדל של גיליון הנתונים של GenX320. רגישות, רעש ורוחב פס מאוזנים לסצנות כלליות.csi.GENX320_BIASES_LOW_LIGHT— שני ספי הניגודיות מורפים לרגישות גבוהה יותר, FO מונמך כדי לשמור על רעש נמוך, ו-HPF מוגדר ל-0 כך ששינויי בהירות איטיים עדיין נרשמים — סצנת תאורה נמוכה מייצרת מעט אירועים מעצמה, ולכן אנו רוצים שכמה שיותר מהם יעברו.csi.GENX320_BIASES_ACTIVE_MARKER— מכוונן למעקב אחר נוריות LED מהבהבות בעלות ניגודיות גבוהה. ספי הניגודיות מועלים כך שרק מעברים חדים מפעילים אירוע; FO ו-HPF מועלים לגובה כדי למקסם את רוחב הפס של הפיקסל ולדחות כל סחיפה סביבתית איטית; REFR נמשך ל-0 כך שכל קצה הבהוב נלכד ברצף. התוצאה: זרם שכמעט כולו קצות LED, קל למעקב.csi.GENX320_BIASES_LOW_NOISE— ברירת המחדל של מנהל ההתקן. שני ספי הניגודיות מועלים לעומתDEFAULT(פחות רגיש) ו-FO מונמך (פיקסל איטי יותר = פיקסל שקט יותר). הטוב ביותר לסצנות סטטיות או איטיות שבהן אירועים שגויים היו אחרת משתלטים.csi.GENX320_BIASES_HIGH_SPEED— FO מועלה כך שכל פיקסל יכול להגיב מהר יותר, HPF מוגבה כדי לדחות סחיפת בהירות איטית, ו-REFR מוגבה כך שקצה בודד נע במהירות לא מציף את הקריאה — הזמן המת הארוך יותר שומר על נפח האירועים תחום בתנאי תנועה כבדה.
עקפו הטיות בודדות באמצעות csi.IOCTL_GENX320_SET_BIAS בתוספת אחד מ-csi.GENX320_BIAS_DIFF_ON, csi.GENX320_BIAS_DIFF_OFF, csi.GENX320_BIAS_FO, csi.GENX320_BIAS_HPF או csi.GENX320_BIAS_REFR וערך DAC. כל הטיה נקבעת באופן עצמאי — בחרו ערכה מוגדרת מראש כנקודת התחלה, ואז כווננו את ההטיות שהסצנה שלכם צריכה:
csi0.ioctl(csi.IOCTL_GENX320_SET_BIASES, csi.GENX320_BIASES_LOW_LIGHT)
csi0.ioctl(csi.IOCTL_GENX320_SET_BIAS, csi.GENX320_BIAS_HPF, 20)
מעקב¶
מאחר שפלט מצב ההיסטוגרמה הוא פשוט תמונה בגווני אפור, מעקב רכיבים (blob) רגיל עובד ישירות. כדי לעקוב אחר נורית LED של סמן פעיל, טענו את ערכת ההטיה של הסמן הפעיל ומצאו רכיבים בקצה הבהיר של ההיסטוגרמה:
import csi
import time
csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize((320, 320))
csi0.brightness(128)
csi0.contrast(16)
csi0.framerate(200)
csi0.ioctl(csi.IOCTL_GENX320_SET_BIASES, csi.GENX320_BIASES_ACTIVE_MARKER)
clock = time.clock()
while True:
clock.tick()
img = csi0.snapshot()
for blob in img.find_blobs([(120, 140)], invert=True,
pixels_threshold=2, area_threshold=4,
merge=True):
img.draw_detection(blob)
print(clock.fps())
מצב אירועים¶
מצב אירועים עוקף את ההיסטוגרמה שעל השבב ומזרים אירועים גולמיים אל ndarray של numpy. כל אירוע הוא שורה של שש עמודות uint16:
[0]סוג האירוע — ראו להלן[1]חותמת זמן בשניות[2]חותמת זמן באלפיות השנייה[3]חותמת זמן במיקרו-שניות[4]קואורדינטת X, 0-319[5]קואורדינטת Y, 0-319
מנהל ההתקן מפיק שישה סוגי אירועים בעמודה [0]:
csi.PIX_OFF_EVENT— פיקסל זיהה ירידה בבהירות (סף המשווהDIFF_OFFנחצה). X/Y מצביעים על הפיקסל שהפיק את האירוע.csi.PIX_ON_EVENT— פיקסל זיהה עלייה בבהירות (סף ה-DIFF_ONנחצה). X/Y מצביעים על הפיקסל.csi.EXT_TRIGGER_FALLING— פין ההדק החיצוני של החיישן ראה קצה יורד. X/Y אינם בשימוש.csi.EXT_TRIGGER_RISING— פין ההדק החיצוני של החיישן ראה קצה עולה. X/Y אינם בשימוש.csi.RST_TRIGGER_FALLING— הדק איפוס-פיקסל, קצה יורד. X/Y אינם בשימוש. אינו נוצר על ידי הקושחה בשלב זה.csi.RST_TRIGGER_RISING— הדק איפוס-פיקסל, קצה עולה. X/Y אינם בשימוש. אינו נוצר על ידי הקושחה בשלב זה.
כניסת ההדק החיצוני של ה-GENX320 מחווטת לקו סנכרון הפריימים של המצלמה, המנותב גם אל P10 הן במעבד והן בכותרת הפינים — הניעו את P10 כדי להזריק קצות סנכרון אל זרם האירועים ולקלוט אותם כאירועי EXT_TRIGGER_RISING / EXT_TRIGGER_FALLING לצד נתוני הפיקסל.
רוב היישומים מתעניינים רק ב-PIX_OFF_EVENT וב-PIX_ON_EVENT; סוגי ההדק מאפשרים לכם לתאם אירועים עם אותות תזמון חיצוניים.
הקצו את חוצץ האירועים בצורה (EVT_res, 6) כאשר EVT_res הוא חזקה של שתיים בין 1024 ל-65536, ואז היכנסו למצב אירועים דרך csi.IOCTL_GENX320_SET_MODE עם csi.GENX320_MODE_EVENT וגודל החוצץ. קראו אירועים באמצעות csi.IOCTL_GENX320_READ_EVENTS, אשר ממלא את החוצץ עד לקיבולתו ומחזיר את מספר השורות התקפות.
Image.draw_event_histogram ממיר אירועים לתמונה בגווני אפור (rasterize) — עבור כל אירוע ON הוא מוסיף contrast לתא; עבור כל אירוע OFF הוא מחסיר. clear=True מאפס תחילה את התמונה ל-brightness; clear=False צובר על פני קריאות רבות:
import csi
import image
import time
from ulab import numpy as np
img = image.Image(320, 320, image.GRAYSCALE)
events = np.zeros((2048, 6), dtype=np.uint16)
csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.ioctl(csi.IOCTL_GENX320_SET_MODE, csi.GENX320_MODE_EVENT, events.shape[0])
clock = time.clock()
while True:
clock.tick()
n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
img.draw_event_histogram(events[:n], clear=True, brightness=128, contrast=64)
img.flush()
print(n, clock.fps())
ערכות ההטיה המוגדרות מראש של מצב ההיסטוגרמה, מסנן ה-AFK ו-ioctls של כיול הפיקסלים החמים כולם עובדים באותו אופן במצב אירועים — קראו להם לאחר csi.IOCTL_GENX320_SET_MODE.
סינון לפי קוטביות¶
חתכו את מערך האירועים באמצעות ulab כדי לשמור רק אירועי ON (תנועה אל מצב בהיר יותר) או רק אירועי OFF:
TARGET = csi.PIX_ON_EVENT # or csi.PIX_OFF_EVENT
events_slice = events[:n]
indices = np.nonzero(events_slice[:, 0] == TARGET)[0]
if len(indices):
target_events = np.take(events_slice, indices, axis=0)
img.draw_event_histogram(target_events, clear=True,
brightness=128, contrast=64)
צבירת חשיפה ארוכה¶
הגדירו clear=False כדי להמשיך לערום אירועים אל אותה תמונה על פני פריימים רבים — התוצאה היא המחשה של שובל תנועה. אפסו מעת לעת כדי להתחיל חשיפה חדשה:
EXPOSURE_FRAMES = 30
i = 0
while True:
n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
clear = (i % EXPOSURE_FRAMES) == 0
img.draw_event_histogram(events[:n], clear=clear, brightness=128, contrast=64)
img.flush()
i += 1
עיבוד במהירות גבוהה¶
ויתרו על ההמחשה כדי לפנות מעבד לעיבוד אירועים. הדפיסו סטטיסטיקה רק כל איטרציה N — דחיפת שורת הדפסה בכל איטרציה הופכת לצוואר הבקבוק בקצבי אירועים גבוהים:
csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.ioctl(csi.IOCTL_GENX320_SET_MODE, csi.GENX320_MODE_EVENT, events.shape[0])
clock = time.clock()
i = 0
while True:
clock.tick()
n = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
i += 1
if not i % 10:
print(f"{n} events {clock.fps()} fps")
מסנן ניגודיות מרחבית-זמנית (STC)¶
קצה ניגודיות נע אמיתי נוטה להפעיל התפרצות רועשת של אירועים על אותו פיקסל בתוך חלון זמן קצר — אי-התאמת פיקסלים ורעש אנלוגי מייצרים אירועים נוספים סביב המעבר האמיתי שאינם שימושיים ליישום. מסנן ה-STC הוא עיבוד-לאחר על השבב ששומר רק אירוע אחד (או כמה) לכל התפרצות ומשמיט את השאר.
הוא מיישם שלוש אסטרטגיות, הנבחרות דרך csi.IOCTL_GENX320_SET_STC וקבוע GENX320_STC_*. כל מצב מוגדר לפי אילו אירועים הוא מעביר מתוך התפרצות:
מצב |
שומר |
משמיט |
|---|---|---|
כל אירוע |
כלום |
|
האירוע השני של התפרצות |
האירוע הראשון + האירועים המאוחרים יותר |
|
האירוע הראשון של התפרצות |
האירועים שלאחר מכן |
|
הקצוות הראשון + שלאחר מכן |
רעש מיותר בלבד |
בפירוט:
csi.GENX320_STC_DISABLE— מסנן כבוי, כל אירוע עובר (ברירת מחדל).csi.GENX320_STC_ONLY— שומר את האירוע השני של התפרצות. פרמטר:stc_threshold(ms). אם אירוע חדש על פיקסל מגיע בתוךstc_thresholdמאירוע קודם, הוא נחשב ל“שני“ של התפרצות ומועבר — האירוע הראשון וכל אירוע שלאחר מכן באותה התפרצות מסוננים החוצה. הטוב ביותר כאשר אתם רוצים מעבר מאומת-רעש ולא את הפגיעה הראשונה ממש.csi.GENX320_STC_TRAIL_ONLY— שומר את האירוע הראשון של התפרצות. פרמטר:trail_threshold(ms). לאחר שפיקסל מפיק אירוע, אירועים שלאחר מכן על אותו פיקסל מושמטים עד שחלףtrail_threshold. משמר את התזמון המדויק של הקצה המוביל — שימושי כאשר רגע מעבר-הקוטביות חשוב יותר מאשר אישור ההתפרצות.csi.GENX320_STC_TRAIL— משלב את שניהם. פרמטרים:stc_thresholdו-trail_threshold(שניהם ms). שומר את הקצה המוביל לפי מצב Trail בתוספת הקצוות שלאחר מכן לפי מצב STC, כך שמספר אירועים מהתפרצות עדיין עוברים — תפוקת אירועים גבוהה יותר מאשר מסנני המצב הבודד אך האות העשיר ביותר.
שני הספים חייבים להישאר בתוך יחס של בערך 13:1 — החיישן דוחה תצורות שבהן אחד גדול מ-~13x מהאחר:
csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_TRAIL, 1, 2)
csi0.ioctl(csi.IOCTL_GENX320_SET_STC, csi.GENX320_STC_DISABLE)
עומק החוצץ¶
כאשר קצבי האירועים מזנקים, צינור החוצץ המשולש שבברירת המחדל מעדיף את הפריים האחרון ומשליך ישנים. העלו את עומק ה-FIFO באמצעות csi.CSI.framebuffers כדי לתור אירועים במקום זאת — במחיר של עיבוד נתונים מעט ישנים יותר כאשר המארח נופל מאחור:
csi0.framebuffers(10) # FIFO depth, > 3 enables queueing
הזרמה והמחשה בשולחן העבודה¶
להמחשה גרפית בזמן אמת במחשב מארח, כלי הזרמת האירועים של GenX320 במאגר openmv-projects משייך את המצלמה עם קצה קדמי של DearPyGui. הממשק הגרפי של המחשב מריץ שתי המחשות זו לצד זו: קנבס צבירת אירועים (אותו רעיון כמו Image.draw_event_histogram אך עם פלטות לבחירה ומצבי חלון נע לעומת ניקוי אוטומטי) ומפת תדר פר-פיקסל המונעת על ידי מסנן מעביר-פס מסוג IIR — שימושי לאיתור אותות מחזוריים (מאווררים מסתובבים, נוריות LED מהבהבות וכו«) ישירות בזרם האירועים.
הוא מסופק עם שני סקריפטים להזרמה על המצלמה:
מצב מעובד (
genx320_event_mode_streaming_on_cam.py) — המצלמה מפענחת אירועים באמצעותcsi.IOCTL_GENX320_READ_EVENTSומזרימה כל שורה כ-12 בתים על גבי USB ([0]סוג,[1]שניות,[2]ms,[3]us,[4]x,[5]y). קל לצריכה במחשב מכיוון שפורמט התקשורת תואם את פורמט ה-ndarray שעל המצלמה.מצב גולמי (
genx320_raw_event_mode_streaming_on_cam.py) — המצלמה מזרימה את מילות האירוע הארוזות המקוריות של השבב ברוחב 32 ביט דרךcsi.IOCTL_GENX320_READ_EVENTS_RAW. זה 4 בתים לאירוע לעומת 12 במצב המעובד (פחות נתונים פי ~3 על גבי USB), ולכן קצב אירועים בר-השגה גבוה פי ~3 כשהקישור הוא צוואר הבקבוק. המחשב מפענח את המילים הארוזות חזרה לאותו מבנה אירוע בן 6 עמודות באמצעות numpy מווקטר, כך שקוד תוכנת ההמחשה שבהמשך הצינור זהה.
מצב גולמי הוא ברירת המחדל בממשק הגרפי מכיוון שתפוקת ה-USB היא האילוץ המגביל בקצבים שה-GenX320 מסוגל לייצר; עברו למצב מעובד אם אתם צריכים לחבר לוגיקת עיבוד אל הסקריפט שעל המצלמה.