תכנות וזיהוי/אלגורתמים נוספים
בעיות בחיתוך אות
עריכהשיטת הזיהוי באכ"כ המוזכרת בפרקים הקודמים ,
מבוססת על חיתוך האות על ידי קווים העוברים דרך התמונה של האות.
קו חותך קו אחר כאשר יש להם נקודה משותפת , כאן בתמונה קו חותך את האות פעם אחת ( בצד הימני התחתון שלה ) ,
אף על פי שהוא היה צריך לחתוך אותה פעמיים (גם בפינה השמאלית עליונה) .
וזה נגרם משום שהקווים מבוססים על נקודות (מלבנים המסודרים באלכסון) ,
ובנקודת החיתוך השמאלית עליונה המלבנים משתלבים ולא עולים זה על זה .
יוצא מכאן שיכולים להיות שני ישרים שונים במישור שלא חותכים זה את זה ולא מקבילים!
כדי לפתור את הבעיה התוכנה עוברת על המערך המכיל את האות הדגומה ,
ומחפש בקווים העוברים באלכסון נקודות הנוגעות זו בזו בפינות
(כלומר לא נמצאות זו מעל זו או זו בצד זו) מרחיבים את הקו במקום הנגיעה,
על ידי הוספת נקודה בצבע האות הצמודה למקום הנגיעה,
כך שבכל מקרה הנקודות ימצאו זו מעל זו או זו בצדי זו .
הנה קטע קו בעייתי :
הקו | רקע |
רקע | הקו |
התיקון:
הקו | הקו |
רקע | הקו |
מימוש האלגוריתם בפונקציה FillingGapsInC
עריכהvoid FillingGapsInC(
int GapsMat[RowsPerPic][ColsPerPic])
{int X,Y,X1,X2,Y1,Y2;
int W = BrightLevels -1; // יש 8 רמות בהירות מ - 0 עד 7
for(Y=0;Y<RowsPerPic-1;Y++)
for(X=0;X<ColsPerPic-1;X++)
{X1 = GapsMat[X][Y];
X2 = GapsMat[X+1][Y];
Y1 = GapsMat[X][Y+1];
Y2 = GapsMat[X+1][Y+1];
if (X1==W&&Y2==W&&Y1!=W&&X2!=W)
GapsMat[X][Y]=0;
if (X1!=W&&Y2!=W&&Y1==W&&X2==W)
GapsMat[X+1][Y]=0;
}
}
העדפת המרכז
עריכהבתוכנה קווים העוברים קרוב למרכז האות
מקבלים ציון גבוה יותר על מספר הפעמים שחתכו את האות
מקווים שחותכים את האות בפינות . כי נראה שהאדם מתרכז
במרכז האות כשהוא מנסה לזהות אותה , את זה אפשר ללמוד מספרי תורה :
בהם קיימים תגים בשולי האותיות , ובכל זאת אות עם תגים ואות ללא תגים
היא אותה אות .
בתוכנית כל קו שעובר ליד מרכז האות במרחק קטן מחצי אורך (וגם רוחב) המלבן החוסם את האות ,
נחשב לקו העובר קרוב למרכז. כדי לחשב מה מרחק הקו מהמרכז התוכנה משתמשת בנוסחת מרחק נקודה מישר.
מימוש האלגוריתם בפונקציה IsPassByTheCenterInL
עריכהבדיקה אם הישר עובר קרוב למרכז ולכן התוצאות שלו חשובות יותר .
מבוסס על המשוואות מהגאומטריה האנליטית .
- מציאת מרחק נקודה מישר :
נסמן את : פונקציית הערך המוחלט כך : y=|x|= abs(x)
נסמן את פונקציית השורש הריבועי כך:
אם נקודת המרכז : ( x1,y1 ) והישר הוא :Ax+By+C=0
המרחק הוא = abs(Ax1+By1+C /sqrt(A*A+B*B))
- משוואת ישר על פי שתי נקודות :(x1,y1),(x2,y2)
משוואת הישר היא : Y-y1=[(y2-x1)/(x2-x1)](X-x1)
#include <windows.h>
#include <math.h>
bool IsPassByTheCenterInL(
POINT Start, POINT End ,POINT Center)
{ float A,B,C,Mone,Mechane,Delta;
bool Out = false;
if (End.x-Start.x==0) return Out;
A = float(End.y-Start.y)/float(End.x-Start.x) ;
B = float(-1);
C = B*A* float(Start.x);
Mone= A*float(Center.x)+B*float(Center.y)+C;
Mechane = sqrt(A*A + B*B);
Delta =Mone /Mechane;
if (Delta<0) Delta = Delta*B;
if ( (Delta< float(Center.x) /2.0)&&
(Delta< float(Center.y) /2.0) )
Out = true;
return Out;
}
צרוף תכונות לתכונה אחת
עריכהבשיטת באכ"ך קו אחד יוצר תכונה כאשר הוא חותך את האות מכיוון מסוים,
והציון ניתן על מספר הפעמים שהוא חתך את האות .
עדיף להוסיף להוסיף עוד סוג של תכונה , כדי שלא יקרה מקרה שאין הבחנה בין שתי אותיות .
בתוכנה מוסיפים עוד סוג של תכונה על ידי חישוב ממוצע הציון של כמה קווים מקבילים על חצי מהמלבן החוסם את האות ,
זה יכול להיות בחלק העליון של האות ויכול להיות במשולש השמאלי תחתון של המלבן החוסם את האות וכולי.
עיגול התוצאה
עריכהנניח שבתשעה מתוך עשרת המופעים של האות הקו (המיצג תכונה) חתך את האות , אם נחשב במספרים שלמים מה הממוצע
לתכונה זו נקבל 0, אם ננסה לחשב את הממוצע במספרים ממשיים (משתנה מסוג Float) נסבך את החישוב . לכן במקרה
כזה מעגלים את התוצאה למעלה , ואומרים שהממוצע הוא אחד.
הנה דוגמה מהתוכנה לעיגול כלפי מעלה :
if ((Sum % NumVariants)*2 > NumVariants)
MeanMeanMap[TheFeature][PicRowNum]++ ;
הסבר הקוד
עריכה- חילוק שני משתנים מסוג שלם : Sum במשתנה NumVariants .
- לא מעניין אותי תוצאת החילוק רק תוצאת השארית (הפעולה % ).
- את השארית הכפל ב - 2.
- אם תוצאת ההכפלה גדולה מהמחלק סימן שהשארית קרובה בגודלה למחלק, ולכן צריך לעגל את התוצאה כלפי מעלה.
- התוצאה שצריך לעגל נמצאת במערך הדו ממדי MeanMeanMap, במקום ( TheFeature, PicRowNum). הגדל את הערך הזה באחד.
מציאת שורה חדשה בקובץ טקסט
עריכהבתוכנה מגיעים לשורה חדשה בקובץ הטקסט על ידי קריאת כל המשתנים בשורה , לכן לכל קובץ יש שורות קוד אחרות
למציאת שורה חדשה. לדוגמה הפונקציה NewLinesInC :
void NewLinesInC(FILE *TheFilePointer,
int NumLines, int Objects)
{int TheLine,TheAtt;
int Stam[1];
for(TheLine=0;TheLine<NumLines;TheLine++)
for(TheAtt=0;TheAtt<Objects;TheAtt++)
fscanf(TheFilePointer,"%d",&Stam[0]);
}
קוד לפונקציה NEW_LINE
עריכהכך יוצרים פונקציה בשם NEW_LINE (עבור לשורה חדשה) המתאימה לכל קבצי טקסט בעזרת הפונקציה fscanf המוזכרת בשורות הקודמות :
void NEW_LINE (FILE *TheFilePointer
)
{
MyLine char[MAX_PATH];
fscanf(TheFilePointer,"%[^\n]%*[\n]",&MyLine);
}
הסברים לפונקציה NEW_LINE
עריכה- הפונקציה מקבלת משתנה קלט אחד מסוג FILE , הוא מצביע לקובץ שנפתח מקודם.
- הפונקציה לא מחזירה משתנה פלט לכן היא פונקציה מסוג void .
- המשתנה הפנימי MyLine הוא מערך תווים, שגודלו( MAX_PATH ) כגודל השורה הארוכה ביותר האפשרית .
- "[n\^]%" אומר לקחת כקלט את כל התווים חוץ משורה חדשה 'n\' .
- "[n\]*%" אומר להתעלם מהתו של שורה חדשה 'n\' .
קישור תמונה לתו
עריכהכדי שהתוכנה תוכל לרשום קובץ טקסט המפענח את התמונות של האותיות, צריכה להיות בתוכנה תת מערכת
המאפשרת לקשור תו לתמונה של האות, לשם כך בחלון הפקדים (המוגדר בפרקים הקודמים)
יש רשימה של סימני הטקסט ומתוכם המשתמש בוחר סימן ומקשר אותו למספר הסידורי של תמונת העצם,
הקישור נשמר בתוך קובץ טקסט שיש בו רשימה של שתי עמודות :
- מספר הסודר של תמונת העצם .
- התו.
היכן מקלידים ?
עריכהעיין בתמונות בצד שמאל של העמוד : בתמונה הראשונה כתוב "סמן עצם באות" יש שם רשימה של סימנים.
או שבוחרים אחד מהם ובאופן אוטומטי המלבן מתחתיו (עם כותרת "בחר") מקבל את הסימן , או שמקלידים את הסימן במלבן.
במלבן מתחת (עם כותרת "קשר לעצם #" ) מקלידים את המספר הסידורי של העצם. כדי לדעת מה המספר הסידורי של העצם
עיןן בתמונה השנייה ("עצמים") : המשתמש לוחץ על כפתור "מקרא" ומקבל טבלה עם שתי עמודות:
- מספר הסודר של העצם.
- תמונה של העצם.
אם הוא כבר קישר כמה עצמים לתווים הוא מקבל עמודה נוספת המציגה גם את התווים (כמו בתמונה השלישית) .
איך יוצרים מקרא לעצמים כטבלה המכילה עמודות של אותיות מספרים ותמונות ?
עריכהיוצרים תמונת מפת סיביות חדשה ומשתמשים בעיקר בשתי פונקציות ספרייה :
BitBlt המאפשרת לקחת חלק מתמונה אחת ולהדביק על תמונה שנייה
(המוזכרת בפרקים הקודמים) המקבלת את המשתנים הללו :
- מצביע לחלק הגרפי של תמונת מפת הסיביות עליה יודבק חלק (או כל התמונה) ממפת סיביות אחרת.
- קואורדינטת הרוחב של הנקודה השמאלית עליונה שם תוצג התמונה.
- קואורדינטת הגובה של הנקודה השמאלית עליונה שם תוצג התמונה.
- רוחב הקטע (המלבן שהוא חלק מהתמונה) המוצג.
- גובה הקטע המוצג.
- מצביע לחלק הגרפי של תמונת מפת הסיביות ממנה יודבק חלק (או כולה) על מפת סיביות אחרת.
- קואורדינטת הרוחב של הנקודה השמאלית עליונה משם נלקח חלק מתמונה (או כל התמונה) .
- קואורדינטת הגובה של הנקודה השמאלית עליונה משם נלקח חלק מתמונה (או כל התמונה) .
- רוחב הקטע שנלקח.
- גובה הקטע שנלקח.
- שיטת עיבוד שתי התמונות בזמן ההדבקה, אצלנו זו הדבקה אחת על השנייה (מציבים את הקבוע SRCCOPY) .
TextOut משמשת לכתיבת טקסט על תמונת מפת סיביות היא מקבלת את המשתנים הללו : (HDC,int,int,LPCSTR,int)
- מצביע לחלק הגרפי של החלון.
- קואורדינטת הרוחב של מיקום הטקסט בתמונה .
- קואורדינטת הגובה.
- הטקסט.
- גודל הטקסט.
הסבר הפונקציות של קישור תמונה לתו
עריכהתו רחב
עריכהסתם כך יש 256 תווים בטקסט המוצבים במשתנה מסוג char , אבל בערך 30 הראשונים אי אפשר להקליד אותם ,
נראה ש - 226 זה לא מספיק תווים.
הרעיון היה להשתמש בספריה wchar.h שם מוגדר המשתנה wchar_t שאפשר לטעון אותו ב- 256*256 תווים ,
אבל לא הצלחתי ליצור רשימה שמראה תווי טקסט שונים מהסטנדרט.
אלו שגיאות המשתמש יכול לבצע
עריכה- הוא יכול לשבץ אותו מספר עצם לשני תווים שונים (אעפ"י שמותר שיהיו אותו מספר עצם לשתי שורות שונות בתמונת העצמים).
- הוא יכול להיכנס לקובץ הטקסט המכיל את מספר העצם מול התו (הקובץ Refer.txt ) ולהרוס את מבנהו.
- הוא יכול לשבץ מספר עצם שמספרו גדול מכמות העצמים.
- הוא יכול לשבץ מספר עצם הגדול מתחום התווים האפשריים ( לדוגמה 250 ).
- הוא יכול להקליד במקום סימן אחד שתי אותיות.
איך מונעים אותו מלעשות טעות
עריכהבודקים את המשתמש ומזהירים אותו ולא מעדכנים את הקובץ Refer.txt, (עיין בסוף הפונקציה MakeReferenceInG המוזכרת בהמשך).
אלו הבדיקות שעושים :
- כדי שלא ישבץ אותו מספר לשני תווים שונים , אחרי שהוא הקליד מספר בודקים בקובץ Refer.txt אם המספר נמצא שם.
- אי אפשר למנוע מהמשתמש להרוס את מבנה הקובץ Refer.txt, אבל אחרי שהמשתמש הקליד משהוא בודקים את מבנה הקובץ.
- בודקים אם נעשה שיבוץ של מספר גדול מכמות העצמים, או מתחום התווים האפשריים.
- כדי שלא תוקלד מילה בת שתי אותיות אלא רק תו אחד, בודקים את מספר האותיות שהוקלדו.
הפונקציות שעוזרות להזהיר את המשתמש
עריכהMessageBox(HWND,LPCSTR,LPCSTR,UINT)
הפונקציה יוצרת חלון נוסף בו כתובה אזהרה , היא מקבלת את המשתנים הללו :
(סוג חלון אזהרה,תוכן האזהרה, כותרת האזהרה, חלון האב של חלון האזהרה) MessageBox
הצגת התוכנה
עריכה#include <windows.h>
#include <wchar.h>
#include <stdio.h>
#include <stdlib.h>
#include "MyContR.h"
#include "MyProcs.h"
void ListCharTextInG(HWND hwnd)
{ const int Ascis = 272;
const int FirstTextChar = 33;
long listCounter;
char StBf[2];
SendDlgItemMessage(hwnd,
IDC_LETTERS_LIST,
LB_RESETCONTENT, 0, 0);
for(listCounter=FirstTextChar;
listCounter< Ascis;
listCounter++)
/* שבץ בדלגלג את האותיות האפשריות */
{ wsprintf(StBf,"%c",char(listCounter));
SendDlgItemMessage(hwnd,IDC_LETTERS_LIST,
LB_ADDSTRING,0, long(StBf));
}
}
void MakeReferenceInG(HWND hwnd)
{FILE *ReferF;
int TheCompo,MaxxComps;
int LineN,LiAtt[1]={0};
char LineC[1]={'?'};
LPSTR The_Text=" ",StBf;
LPSTR ReferFileName="Refer.txt";
bool Beseder=true;
The_Text = new CHAR;
StBf = new CHAR;
ReferF = fopen(ReferFileName,"r");
if (!ReferF) fopen(ReferFileName,"w");
fclose(ReferF);
MaxxComps=GetDlgItemInt(
hwnd,IDC_OBJECTS_NUM,
NULL,FALSE);
TheCompo=GetDlgItemInt(
hwnd,IDC_ELEM_REF,
NULL,FALSE);
if (TheCompo < 1 || TheCompo > MaxxComps )
{MessageBox(NULL,
"המפתח צריך להיות בתחום כמות העצמים",
"אזהרה",0);
SetFocus( GetDlgItem(hwnd,
IDC_OBJECTS_NUM));
Beseder =false;
}
GetWindowText(
GetDlgItem( hwnd,IDC_THE_CHAR),
The_Text,3);
wsprintf(StBf,"%d",The_Text[1]);
if (The_Text[1])// סומנו שתי אותיות רציפות
// במקום לבחור אות אחת
{ MessageBox(NULL,
"בחר רק אות אחת",
StBf,0);
SetFocus( GetDlgItem(hwnd,
IDC_THE_CHAR));
Beseder =false;
}
ReferF = fopen(ReferFileName,"r");
for(LineN=0;
fscanf(ReferF,"%d",&LiAtt[0])!= EOF;
LineN++)
{ if (TheCompo == LiAtt[0])
{MessageBox(NULL,
"אסור שהיה אותו מפתח פעמיים
,אם אתה רוצה לשנות
שנה את הקובץ
refer.txt
.בעזרת הפעלתו
בתפריט הראשי
בחר קובץ
אחרים",
"אזהרה",0);
SetFocus( GetDlgItem(hwnd,
IDC_THE_CHAR));
Beseder =false;
break;}
if( fscanf(ReferF,"%c",&LineC[0])
== EOF)
{ MessageBox(NULL,
"מבנה הקובץ לא טוב\
אין בכל שורה תו אחרי מספר שלם",
"אזהרה",0);
Beseder =false;
break;
}
}
fclose(ReferF);
if (Beseder)
{ ReferF = fopen(ReferFileName,"a");
fprintf(ReferF, "%4d %4s \n",
TheCompo,
The_Text );
fclose(ReferF);
SetDlgItemText(hwnd,
IDC_A_CHAR_SELECTED,"נקשר בהצלחה");
}
delete The_Text;
}