מקרר חכם
כתבתי את הפוסט הזה לפני שנתיים וחצי, עכשיו אמרתי שאחזור לכתוב שוב, אז נתחיל איתו ונמשיך לפרויקטים הבאים שבדרך.
תהיתי על איזה פרויקט לכתוב עכשיו, חשבתי להתחיל להגיע לפרויקטים יותר עדכניים אבל סוף החלטתי ללכת יותר על הכיוון הכרונולוגי כדי להראות את השינוי שחל במהלך השנים.
קצת רקע
כשהייתי בי"ב (2015~) היה לי יחסית הרבה זמן פנוי אז דאגתי להעסיק את עצמי בכל מיני פרויקטים. למדתי 5 יחידות מחשבים והייתה לנו אפשרות לעשות 5 יחידות נוספות כחלק מתוכנית של מכון דוידסון (https://davidson.weizmann.ac.il/programs/cs).
בכיתה י"א היינו נוסעים בערך פעם בחודש למכון ויצמן ועוברים שם כמה הרצאות בכל מיני נושאים שקשורים למחשבים, אני לא זוכר הרבה אבל אני זוכר שהיה מעשיר וחשף עוד עיסוקים שקשורים לעולם המחשבים.
בכיתה י"ב מי שהחליט להמשיך היה יכול לעשות עבודת גמר בהיקף של 5 יחידות כאשר לכל אחד מצטרף מנחה שמלווה אותו במהלך העבודה.
עם יחסית הרבה זמן פנוי, החלטתי ללכת על זה והגעתי עם הרבה שאיפות כמו שתקראו תכף.
בשונה מהרבה מהפרויקטים שלי התיעוד כאן היה יחסית טוב בעיקר כי היינו צריכים להגיש דוחות התקדמות ותיק עבודה גדול, ובנוסף באותה תקופה היה נפוץ להתכתב במייל אז יש לי עוד קצת שיחות שהיו לי עם המנחה.
זה ממש נחמד האמת לקרוא את התיעודים מפעם, אני ממש יכול להרגיש את התחושות שהיו לי ברגעים שכתבתי את הדברים.
מתחילים
מאז שאני קטן תמיד אהבתי צעצועים בשלט רחוק, בעיקר כאלו שעפים. היו לי כמה וכמה מסוקים קטנים על שלט שקניתי לאורך השנים, ותמיד מחזור החיים שלהם היה דומה.
בהתחלה הייתי ממש רוצה מסוק על שלט, אחרי זה הייתי קונה אחד ממיטב כספי, אז הייתי ממש מתלהב ממנו ונהנה מכל רגע של הטסה. אחרי זה הייתה מגיעה ההבנה המבאסת שהוא יכול לטוס שלוש דקות עד שהסוללה נגמרת ואז צריך להטעין אותו שעה ולבסוף הייתי מטיס אותו קצת יותר מדי באגרסיביות, הוא היה נתקע בקיר, חלק ממנו היה נשבר וככה היה מסתיים הרומן עם המסוק על שלט.
באזור י"ב התחלתי להיכנס לתוך העולם של הרחפנים (שעליו אכתוב עוד בהמשך) והיה לי רחפן צעצוע hubsan x4 107d.
זה היה הרחפן הראשון שקניתי שהיה לו מצלמה שמשדרת חזרה לשלט וממש התאהבתי ברעיון הזה, קניתי גם הרבה סוללות אז יכלתי להטיס אותו הרבה והיה מעולה.

ובחזרה לעניינו, רציתי להתעסק במשהו שקשור לרחפנים והחלטתי שהפרויקט יהיה להצליח לשלוט על התנועה של הרחפן עם חפץ כלשהו.
מה שדמיינתי זה מעין מקל שאני מחזיק ומצביע איתו לכל מיני כיוונים והרחפן זז לפי הכיוון שהמקל מצביע עליו.
זה היה נשמע לי ממש מגניב והחלטתי ללכת על זה מבלי שהבנתי כמה זה מורכב וקשה, אפילו עכשיו שאני חושב על זה הפרויקט נראה לי ממש קשה והוא בטוח יאתגר אותי ממש .
מחקר ראשוני
אז הפרויקט הזה בעצם מחולק לכמה חלקים:
- שליטה על הרחפן עצמו – בשביל החלק הזה צריך להבין איך מועבר מידע לרחפן, מה פרוטוקול התקשורת, איך לשדר את הגלים…
- עיבוד תמונה – צריך לנתח את המיקום של החפץ, את הכיוון שלו, ובהתאם לזה לשלוח את הפקודות הנכונות לרחפן
ואת כל זה צריך לעשות מספיק מהר כדי שהרחפן יגיב ויישאר כמו שצריך באוויר.
אז התחלתי במחקר ובעיקר קראתי בפורומים באינטרנט לגבי השליטה על הרחפן. רציתי להבין האם יש תיעוד של איך שהוא עובד, והאם אפשר "להתחזות לשלט" ושלוח לו פקודות משלי.
האמת שקראתי את התיעוד שעשיתי אז שמחתי לראות שכל המחקר הזה התנהל עוד ביולי, יופי של התקדמות בלוח זמנים האמת.
אחרי שיטוט באינטרנט מצאתי פוסט באחד הפורומים שאומר שמישהו הצליח לפענח את הפרוטוקול תקשורת של החברה ובעצם אפשר ליצור בעצמך שלט שיתקשר עם הרחפן!
הייתי ממש נלהב והחלטתי להזמין את החלקים הדרושים ולנסות לממש את הכיוון הזה.
באזור ספטבר הגיעו החלקים ובמקביל הגשתי הצעת מחקר לעבודה שנראתה ככה (אולי לצרף כתמונה)
אוקטובר
- לסיים את בניית המשדר
- להצליח לשלוט על הרחפן (ללא קשר לתמונה)
- גרסה ראשונית של עיבוד התמונה
דצמבר
- לסיים את תוכנת עיבוד התמונה
- להצליח לבצע את הקשר בין עיבוד התמונה לשליטה על הרחפן
ינואר
- לסיים את הקשר בין עיבוד התמונה והשליטה על הרחפן
פברואר
- פתירת בעיות שעדיין לא נפתרו
- גימור הפרויקט: ייעול, תיקון בעיות מינוריות ושיפור הקוד.
אין ספק שהתוכנית עבודה נראית מעולה ואני מסיים את הפרויקט חודש לפני המועד הגשה!!
ההתתפכחות
לקראת סוף אוקטובר שלחתי למנחה שלי הודעה ואני אצטט אותה :
"לאחרונה ישבתי ועשיתי חושבים, וראיתי שככל שאני נכנס יותר לעומקו של הפרויקט אני בעצם
מבין שלקחתי על עצמי פרויקט גדול מדי והבנתי לעומק את המורכבות העצומה שלו.
לכן הגעתי למסקנה לא להמשיך עם הפרויקט הזה כי הוא מועד לכישלון במסגרת הזמן הנתונה,
בגלל זה רציתי להחליף את הפרויקט לפרויקט הפשוט יותר שהוא הרעיון עם המקרר.
הרעיון הוא שיש מצלמה שמעדכנת כל פעם את מה שנכנס או יוצא וכך אתה יודע איפה נמצא כל דבר."
ואחרי עוד מעט התכתבויות החלפתי לפרויקט של המקרר החכם.
אני ממש מבסוט שיש את התיעוד של המיילים, אני ממש יכול לדמיין את התחושות שהיו לי אז כשכתבתי את המייל הזה, תחושות של ייאוש ושל רצון להפסיק את כל העיסוק בפרויקט.
המקרר החכם
אחרי ההתפכחות החלטתי לשנות את הפרויקט לרעיון אחר שהיה לי והוא רעיון המקרר החכם.
הרעיון בפרויקט הוא לצלם את המקרר כל פעם שמכניסים/מוציאים ממנו משהו, ובעזרת תוכנה להצליח ליצור גלריה של פריטים שיש לך במקרר.
כלומר להצליח לקחת את סדרת התמונות הבאה (זאת הסדרה המקורית), ולהצליח לבודד ממנה את כל הפריטים. (לשים סדרה תמונות)
הרעיון היה נראה לי ממש מעניין ולא יצא לי לראות משהו שעושה את זה באותה התקופה. זה היה בשנת 2015 שכל עניין ה IoT (internet of things – אינטרנט של הדברים) לא היה מאוד נפוץ.
רק כשסיימתי את הפרויקט ראיתי כתבה על מקרר חכם ראשון שהושק על ידי סמסונג.
מניח שאם מחפשים ממש טוב אז מוצאים גם לפני אבל לאותה העת זה היה ממש חדשני אז זה הוסיף עוד למוטיבציה שלי כי זה היה ליצור משהו שלא קיים בשוק בכלל.
יצרתי גם לפרויקט הזה תוכנית עבודה יפה עם לוחות זמנים שמאפשרים לי הרבה מרווח נשימה. כמובן שנכנסו עוד הרבה דברים אחרים במהלך החודשים הבאים עם תעדוף יותר גבוה, לימודים, תחביבים והשתתפות בתחרות Skillz (שגם עליה אכתוב בהמשך), וכך הגעתי לחופשת פסח (22-29.4) עם התקדמות מועטה, כאשר מועד ההגשה (אחרי דחייה שקיבלתי) הוא ה 30.4.2015
פרויקט בשבוע
באותה הזמן לא ידעתי אם אספיק לסיים את הפרויקט ואני זוכר שחשבתי עם עצמי כבר לוותר עליו בכללי.
בסופו של דבר החלטתי להקדיש את חופשת פסח לפרויקט ולראות כמה אצליח להתקדם בתקופה הזו.
כיוון לפתרון
לפני שאצלול לפתרון, אזכיר שהתוכנה אמורה להצליח לחלץ את הפריטים השונים שנמצאים במקרר ולהציג גלריה שלהם.
הרעיון הכללי לדרך הפתרון הבעיה נשמע יחסית ישיר, פשוט נצלם תמונה של המקרר, נכניס מוצר מסוים ונצלם שוב. כעת ניקח את שתי התמונות (לפני/אחרי) שאמורות להיות זהות מלבד מה שהוספנו, נחסר בינהן ונמצא את המוצר.
בפועל זה אכן הרעיון שיצא לפועל אבל המימוש שלו היה קצת יותר מורכב.
סביבת עבודה
הבעיה הראשונה שנתקלתי בה היה סביבת העבודה. עשיתי את הפרויקט ב C++, כאשר באותו הזמן תכנתתי בעיקר ב Java.
עקרונית אם מכירים שפת תכנות אחת בצורה טובה המעבר פחות מורכב אבל בכל זאת הייתי צריך ללמוד שפה חדשה עם חוקים משלה וצורת כתיבה אחרת.
החלק השני היה עיבוד התמונה. כדי לממש עיבוד תמונה יש ספרייה שנקראת OpenCV (Open computer vision), שפותחה בהתחלה על ידי אינטל ולאחר מכן הפכה לפרויקט קוד פתוח.
הספרייה מאגדת בתוכה המון פונקציות שונות שקשורות לעיבוד תמונה ומאפשרת ממשק מאוד נוח לעבודה עם אלוגריתמים שקשורים לראייה ממוחשבת (היא תומכת ב Java, Python ו C++).
עקרונית ההתקנה של הספרייה לא אמורה להיות בעיה אבל אני נתקלתי בהרבה קשיים ולקח לי יחסית הרבה שזמן עד שהצלחתי לגרום לזה לעבוד.
זה אחד השלבים שהכי מייאשים אותי כשאני עושה פרויקט תוכנתי, כי אני מרגיש שאני מתעסק בכל המסביב ולא בפרויקט עצמו. העיסוק בהתקנת התוכנות השונות מרגיש מאוד טכני ובזבוז זמן.
אבל אין מה לעשות, בכל פרויקט יש עבודה שחורה שלא נהנים ממנה. אבל בסוף הצלחתי לגרום לזה לעבוד עמו שצריך
מה בתכלס עושים
השלב הבא היה להתחיל להכיר ולהעמיק בפונקציות השונות ש OpenCV מציעה. יש המון דברים שאפשר לעשות על התמונות ועיקר הזמן שהשקעתי היה להבין איזה דברים רלוונטים ואיך להשתמש הם כמו שצריך.
למזלי יש תיעוד ממש ממש טוב של הספרייה ומוסבר בצורה מקיפה איך להשתמש בכל פונקצייה ומה היא עושה.
בסופו של דבר הפתרון שהגעתי אליו היה:
- לחסר בין שתי התמונות
- להמיר את התמונה ל GREYSCALE, (בעיקר עוזר לעיבוד לאחר מכן של מכן)
- הפעלה של פונקציית threshold על התמונה (אפשר לחשוב על זה שאם פיקסל הוא מעל ערך מסוים (נגיד 80) אז תהפוך אותו לשחור ואם לא אז ללבן.
- הפעלת erosion ו dialation – "התרחבות וצמצום" , פעולות שעוזרות לאפשר הפרדה בצורה ברורה בין אובייקטים
- שימוש בפונקצייה Canny edge detection שמוצאת גבולות של אובייקטים
- שימוש בפונקצייה findContours שמוציאה את הגבולות מהתמונה
- שימוש ב boundingRect שלוקח גבול של אובייקט ומקיף אותו במלבן המינמלי שמכיל אותו
- לוקחים את את המלבן הכי גדול, בהנחה שהוא מייצג את החפץ שנוסף, חותכים אותו ושומרים לגלריה
והנה התמונות שמייצגות את העיבוד תמונה בפרויקט
תמונות ראשוניות


חיסור בין התמונות

המרה ל GREYSCALE

הפעלת THRESHOLD (ערך סף על הפיקסלים בתמונה)

erosion & dilation

זיהוי קווי מתאר של אובייקטים (canny edge detection)

סימון גבולות על גבי התמונה המקורית

סימון המלבן המינימלי סביב האובייקט

חיתוך התמונה ושמירה לגלריה

נקודה שבטח שמתם לב שרוב הפונקציות נראות כמו קסם, אתה מכניס תמונה ומקבל כפלט תמונה עם עיבוד שקרה עליה.
יחסית מאוד קל להשתמש בעיבוד תמונה, צריך לדעת לתכנת, להתקין OpenCV ומשם אפשר לעבוד עם הרבה מהאלגורתמים הקיימים.
סיום הפרויקט
אז שבוע של עבודה ללא הפסקה בפסח, הפרויקט הגיע לסיום כשהצלחתי להוציא את הפריטים ולהגיע לתצאות יפות.
מחשבות ותובנות
עיבוד תמונה
זה שלא קל לגרום לעיבוד תמונה לעבוד על מקרים כלליים. נגיד בפרויקט הזה התוכנה עבדה כמו שצריך על סט ספיציפי של תמונות. אם הייתי מכניס סט אחר של תמונות עם תנאים שונים (תאורה שונה, גדלים שונים) אז התוכנה לא הייתה עובדת כמו שצריך.
וכן, הפרויקט הזה קצת שקרי כי התוכנה מתיימרת לעשות משהו רחב יותר מניתוח סט ספיציפי של תמונות, אבל לאותה תקופה אני חושב שזה היה הישג מספק.
באופן כללי עיבוד תמונה זה תחום שמהחוויה שלי הוא תחום מאוד מאתגר לעבודה וצריך להתעמק בפרטים השונים ולהבין בצורה רחבה מה הכלים שנמצאים ואתה יכול להשתמש בהם.
נכון שליצור אלגוריתם לעיבוד תמונה זה הרבה יותר קשה, דורש שנים של מחקר ורמה אקדמית גבוהה, אבל כל היופי הוא שהידע מונגש בצורה מאוד נוחה ופרקטית וכל אחד יכול להשתמש באלגוריתמים האלו בלי הרבה ידע (וזה לאו דווקא הופך את זה לפשוט יותר).
וגם אם הצלחתי לממש עיבוד תמונה על מקרה מסוים אז להפוך אותו לכזה שיעבוד על כל סט של תמונות זה עבודה קשה מאוד ועד עצם היום הזה לא היה לי פרויקט שקשור לעיבוד תמונה שהצלחתי לעשות זאת.
פרויקט שגדול עליי
היה לי חשוב להקדיש את החלק הראשון לרחפן כדי לתאר את התהליך האותנטי של הפרויקט ואיך הכל התחיל. אני חושב שהפרויקט של הרחפן באמת היה גדול עליי בכמה מידות בזמנו (וגם עכשיו אולי במידה אחת).
לפי דעתי ההתפכחות שבאה אחרי שלושה חודשים הייתה הכרחית ואני שמח שהיא קרתה ושלא חשבתי לעצמי שהכל יסתדר ושאני אצליח, כי אחרת הייתי נשאר בלי פרויקט בכלל.
לפי דעתי היה מקום למנחה שלי להוריד אותי יותר לקרקע ולהגיד לי שזה פרויקט גדול בכמה רמות וששווה להתחיל במשהו קטן יותר.
זה מתקשר לעובדה שברוב הפרויקטים שאני עושה אין לי יד מכוונת שעוזרת בהכוונה של מה שצריך לעשות. מצד אחד אני מאוד אוהב לעבוד לבד וללמוד בעצמי, מצד שני אני מרגיש שרוב הפעמים הייתי נתרם מהכוונה מקצועית של מישהו שמבין בתחום, וככה גם מקצר לי את הזמן שלוקח לי לעבוד על הדברים.
דוגמות טובות לפעמים שכן הייתה לי הכוונה אכתוב בהמשך בהקשרים של פרויקטים תכנותיים יותר גדולים שעשיתי.
אני חושב שגם הכתיבה של הבלוג מגיע מהחסך הזה ומהרצון לאפשר יד מכוונת לאנשים שעובדים על פרויקטים וצריכים הכוונה.
אני חושב שכאשר אני ניגש לפרויקט יש את החלק של החזון, של איך שאני מדמיין את התוצר במצב אידיאלי כשהכל עובד כמו שצריך. החזון הזה הוא חשוב כי הוא באמת מעין זרקור שעוזר להכווין ולהזכיר את המטרה הסופית.
אבל הרבה פעמים הוא יכול גם לגרום לירידה במוטיבציה משום שקשה מאוד להגיע אליו. אחרי שאני חושב על החזון ומתחיל להתעסק פרקטית בפרויקט אני בדרך כלל מצמצם אותו למספר מטרות ראשוניות ברות השגה.
ככה יותר ריאלי להגיע אליהן ואם אני רואה שעדיין יש לי מוטיבציה להמשיך ולעשות את הפרויקט אז אני מתקדם איתו הלאה ומציב עוד מטרות בדרך.
פרויקטים לטווח הארוך
זו נקודה שאף פעם לא יצא לי באמת להתעסק בה לעומק. רוב הפרויקטים שאני עושה הם בלי דדליין ואני קובע לעצמי את מסגרות העבודה.
התכנונים לטווח הארוך לא עובדים לי כל כך ממה שיצא לי להתנסות, אני בדרך כלל עובד בתכנון זמנים של כמה שבועות ולכל היותר חודשים. כאשר גם הפרויקטים שאני מתכנן לעשות אין לי לוז מסודר לגבי הזמנים שלהם, אלא בעיקר סדר עדיפויות של הדברים שאני רוצה להתעמק בהם וזורם איתם.
יצירה מחדש של הפרויקט עכשיו
כשעבדתי על הפוסט אז לא היו לי תמונות של התהליך של עיבוד התמונה. הרבה זמן התלבטתי אם לכתוב על הפרויקט כי חשבתי שהוא יחסית רדוד אם לא אסביר יותר לעומק על תהליך הפתרון עצמו,
אז החלטתי שאנסה לכתוב מחדש את התהליך שעשיתי בעזרת התיעוד שהיה לי של הפרויקט.
בהתחלה חשבתי לקחת ממש את הקוד שכתבתי בזמנו, אבל לצערי היה לי רק עותק שלו ב WORD וזה נראה לי קשוח להתחיל לנסות להעתיק משם את הקבצים ולנסות לגרום לזה לעבוד מחדש.
אז החלטתי לכתוב את הפרויקט מאפס ב Python ואחרי שעתיים של עבודה ובערך 40 שורות קוד סיימתי 🙂
זה היה ממש מדהים, פרויקט שהתפרס על חודשים בי"ב ובעיקרו היה שבוע שלם של עבודה מבוקר עד לילה הסתיים עכשיו בשעתיים.
אז קודם כל זה היה נחמד לראות שיש התקדמות, אני חושב שזה נבע בעיקר מהניסיון שצברתי מעוד פרויקטים שקשורים לעיבוד תמונה, בכך שהסביבת עבודה כבר הייתה קיימת
ובכך שהשתפרתי בחיפוש יעיל יותר של בעיות שעולות לי במהלך הדרך.
מצורף הקוד למי שמעוניינ/ת
import cv2 as cv
import numpy as np
import glob
import os
def main():
def nothing(x):
pass
cv.namedWindow('controls')
cv.createTrackbar('r', 'controls', 110, 255, nothing)
paths = glob.glob("images_fridge/*Copy*")
for i in range(0,len(paths) - 1):
img1 = cv.imread(paths[i])
img1 = cv.resize(img1, (750, 540))
img2 = cv.imread(paths[i+1])
img2 = cv.resize(img2, (750, 540))
img_diff = cv.absdiff(img1, img2)
img_gray = cv.cvtColor(img_diff, cv.COLOR_BGR2GRAY)
img3 = cv.imread(paths[i+1])
img3 = cv.resize(img2, (750, 540))
img4 = cv.imread(paths[i+1])
img4 = cv.resize(img2, (750, 540))
img_grey_threshold = cv.inRange(img_gray,110, 255)
kernel = np.ones((7, 7), np.uint8)
img_dilation = cv.dilate(img_grey_threshold, kernel, iterations=3)
img_erosion = cv.erode(img_dilation, kernel, iterations=1)
edged = cv.Canny(img_erosion, 30, 200)
contours, hierarchy = cv.findContours(edged,
cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)
for c in contours:
x,y,w,h = cv.boundingRect(c)
if w*h > 7000:
cv.rectangle(img4,(x,y),(x+w,y+h),(155,155,0),1)
cropped_img = img2[y:y+h, x:x+w]
cv.imshow('cropped_img', cropped_img)
path = "images_cropped/{}".format(os.path.basename(paths[i]))
cv.imwrite(path,cropped_img)
cv.drawContours(img3, contours, -1, (0, 255, 0), 3)
cv.imwrite("process/1.img1.jpg", img1)
cv.imwrite("process/2.img2.jpg", img2)
cv.imwrite("process/3.img_diff.jpg", img_diff)
cv.imwrite("process/4.img_grey.jpg", img_gray)
cv.imwrite("process/5.img_grey_thres.jpg", img_grey_threshold)
cv.imwrite("process/6.Erosion.jpg", img_erosion)
cv.imwrite("process/7.Dilation.jpg", img_dilation)
cv.imwrite("process/8.edged.jpg", edged)
cv.imwrite("process/9.Contours.jpg", img3)
cv.imwrite("process/10.bounding_rect.jpg", img4)
cv.imwrite("process/11.cropped_img.jpg", cropped_img)
cv.imshow("img1", img1)
cv.imshow("img2", img2)
cv.imshow("img_diff",img_diff )
cv.imshow("img_grey",img_gray)
cv.imshow("img_grey_thres",img_grey_threshold)
cv.imshow('Erosion', img_erosion)
cv.imshow('Dilation', img_dilation)
cv.imshow("edged",edged)
cv.imshow('Contours', img3)
cv.imshow('bounding_rect',img4)
while True:
k = cv.waitKey(1) & 0xFF
if k == 27:
break
cv.destroyAllWindows()
if __name__ == '__main__':
main()
עד הפרויקט הבא, רותו