ربات قسمت چهارم
![]() |
اشاره :
در مقاله قبلي با ساختار كلي فايل هاي تصويري و چگونگي قرار گرفتن بيت ها و بايت ها درون يك پيكسل، آشنا شديم. در اينجا قبل از آغاز كار بر روي موضوع روباتيك، مثال مخفي نمودن متون محرمانه در يك فايل تصويري را بصورت عملي در دو زبان برنامه نويسي مرور مي كنيم. زبان ها استفاده شده براي آموزش زبان های Delphi و C++ Builder هستند كه در هر محصول شركت Borland بشمار مي روند و بعنوان بهترين انتخاب براي كار روباتيك در جهان بكار مي روند.
در مثال هايي كه پيش روي خواهيم داشت، علاوه بر جذاب بودن آنها، تمامي مفاهيم پردازش تصوير را بصورتي مملوس از نظر خواهيم گذراند. در پايان اين مقاله شما مقوله پردازش تصوير را تماما درك نموده و با ابزارهاي آن آشنايي پيدا خواهيد نمود.
در اينجا مناسب مي دانم كه نيم نگاهي به برنامه بيندازيم تا با متد فعاليت، آشنايي لازم را كسب نموده و مراحل برنامه را بصورت سليسی دنبال كنيم. بمنظور مرور بخش هاي گذشته، مراحلي را كه مي بايست انجام گيرند با هم مرور مي كنيم.
به شكل 1 كه برنامه آموزشي ما با آن شروع مي شود، توجه كنيد .

شكل 1 : رابط گرافيگي برنامه الگو
با اجراي برنامه، فايل تصويري "Sample.bmp" بصورت پيش فرض در قسمت Original Image نشان داده مي شود. بعد از نوشتن متن محرمانه خود در قسمت Secret Message و زدن دكمه Encode، متن نوشته شده در درون تصوير جاسازي شده، و تصوير حامل متن در قسمت Image With Text قابل مشاهده است. بعد از اين كار، در صورتيكه دکمه Decode را فشار دهيد، متن جاسازي شده در تصوير، در قسمت Decoded Message نمايش داده مي شود.



شكل 2 : مراحل كار از بالا به پايين
حال كه با شيوه كار برنامه آشنا شديد، عملياتي را كه در اين برنامه انجام مي شود مرورمي كنيم.
مراحل و عملياتی كه با فشار دادن دكمه Encode انجام مي پذيرد:
• تبديل حروف نوشته شده در قسمت Secret Message به قالب کدهای ASCII
• قرار دادن كد اسكي معادل هر كاراكتر در خانه هاي آرايه اي بنام letters.
• تقسيم كاراكترها به قسمتهاي دو بيتي و قرار دادن هر دو بيت در يكي از خانه هاي آرايه dividedLetters بصورت جداگانه
• توزيع دو بيتي ها در دو بيت كم ارزش هر يك از بايتهاي تصويري (هر پيكسل توانايي ذخيره سه قسمت دو بيتي را در خود دارد، يعني شش بيت)
نكته : هنگام استخراج اطلاعات از تصوير، مي بايست تعداد پيكسل هايي كه اطلاعاتي در آنها ذخيره شده است مشخص شده باشد. براي اين كار شيوه اي را كه خود برگزيدم، اين است كه تعداد كاراكترهاي مورد نظر را در پيكسل های اول تصوير ذخيره مي نمايم تا قبل از شروع عمليات استخراج مشخص شود كه پردازش پيكسل ها را تا كجا بايد ادامه داد. در غير اينصورت مي بايست تا آخرين پيكسل فايل تصويري حركت نمود كه علاوه بر اتلاف زمان هيچگونه فايده اي نيز ندارد. شيوه ديگري را كه مي توان در نظر گرفت اين است كه بعد از جاسازي متون در تصوير و جاسازي آخرين دو بيت، در آخرين پيكسل، تعدادی از پيكسل هاي بعدي را علامت گذاري نمود كه اين شيوه توصيه نمي شود، زيرا ممكن است علامت گذاري ها با اصل متن تداخل ايجاد كنند.
همانطور مشاهده خواهيد نمود کدهای زير مربوط به محاسبه تعداد كاراكترها، تعداد پيكسل هاي مورد نياز براي ذخيره آنها و چگونگي تقسيم هر يك ار كاراكترها به چهار دو بيتي و قرار دادن آنها در آرايه اي جديد است. ابتدا نمونه ای از مجموع عمليات اشاره شده را از نظر مي گذرانيم، سپس هر قسمت را جداگانه مورد بررسی قرار می دهيم.


جدول 1
حال لطفا به توضيحات خط به خط كدها توجه نماييد. اما قبل از آن مجددا به اين نكته اشاره مي كنم كه سعي نماييد تفاوت هاي ميان دو زبان بکار رفته را بمنظور آشنايي و استفاده از هر دو زبان مورد توجه قرار دهيد.
• در كدهاي زير، تعداد اوليه كاراكترها بصورت مبناي هشت در سه خانه اول آراية letters ذخيره مي شوند.
C++ Builder
letters[0] = (int)( len / (256*256) );
letters[1] = (int)( (len % (256*256)) / 256 );
letters[2] = (int)( (len % (256*256)) % 256 );
letters[0] := len div (256*256);
letters[1] := (len mod (256*256)) div 256;
letters[2] := (len mod (256*256)) mod 256;
در كدهاي ذيل، حلقه استفاده شده، كليه كاراكتر هاي متني را در آرايه letters و بعد از سه خانة اول قرار مي دهد.
C++ Builder
for (int i = 1; i <= len; i++)
{
String singleChar = secretM.SubString( i, 1);
letters[i+2] = (int)singleChar[1];
}
for i := 1 to len do
begin
singleChar := copy( secretM, i, 1);
letters[i+2] := ord(singleChar[1]);
end;
• در كدهاي زير، عدد 3 به متغيير len كه نشاندهنده تعداد خالص كاراكترها است، اضافه مي شود. اين سه خانه بمنظور ذخيره سازي تعداد كاراكترها در نظر گرفته شده اند.
C++ Builder
len = len + 3;
len := len + 3;
• از آنجا كه در برنامه، هر يك از پيكسل ها بصورت كامل (هر سه بايت) مورد بررسي قرار مي گيرند، بنابراين تدابيري انديشه شده تا تعداد دو بيتي هاي استفاده شده در فايل تصويري، مضربي از سه و چهار باشد. (بدليل اينكه هر پيكسل از سه بايت تشكيل شده است، بنابراين از سه تا دو بيتي استفاده مي نمايد و اينكه هر كاراكتر از هشت بيت تشكيل شده است و به چهارتا دو بيتي تبديل مي شود). در كدهاي خطوط زير، تعداد كاراكترها به گونه اي تعيين مي شود كه تعداد بيت هاي مورد استفاده مضربي از سه و چهار باشد (كاراكتر فاصله يا Space در صورت نياز به آخر كاراكترها افزوده مي شود). در نهايت تغييرات اعمال شده در خانه شماره سه يا letters[2] به روز مي شود.
C++ Builder
int diff = (len*4) % 3;
for ( int i = 1; i <= 3-diff; i++ )
letters[len+i-1] = 32;
len = len + (3-diff);
letterNo = len;
letters[2] = letters[2] + (3-diff);
diff := (len*4) mod 3;
for i := 1 to 3-diff do
letters[len+i-1] := 32;
len := len + (3-diff);
letterNo := len;
letters[2] := letters[2] + (3-diff);
• در كدهاي ذيل، كاراكترها و نشانگرهاي موجود در آرايه letters به قسمت هاي دو بيتي تقسيم شده (هر كاراكتر به چهار قسمت تبديل مي شود) و در خانه هاي آراية dividedLetters جاي مي گيرند.
C++ Builder
for ( int i = 0; i <= len-1; i++ )
{
dividedLetters[4*i] = divideLetter(letters[i], 0);
dividedLetters[4*i +1] = divideLetter(letters[i], 1);
dividedLetters[4*i +2] = divideLetter(letters[i], 2);
dividedLetters[4*i +3] = divideLetter(letters[i], 3);
}
for i := 0 to len-1 do
begin
dividedLetters[4*i] := divideLetter(letters[i], 0);
dividedLetters[4*i +1] := divideLetter(letters[i], 1);
dividedLetters[4*i +2] := divideLetter(letters[i], 2);
dividedLetters[4*i +3] := divideLetter(letters[i], 3);
end;
حال كدهايی را كه مربوط كه چگونگي جاسازي دو بيتي ها در پيكسل هاي تصوير مورد نظر است، مورد بررسي قرار مي دهيم:
• پس از تعريف و مقدار دهي اوليه متغيرهاي pixelCounter و blockCounter و بارگذاري متغير BitMap با تصويري بنام Sample.bmp، وارد مرحله آزمايش امكان يا عدم امكان جاسازي متون نوشته شده با تصوير مي شويم. در صورتيكه متن زياد و تصوير كوچك باشد، پيغام خطايي نمايش داده مي شود و مراتب را به اطلاع كاربر مي رساند.
C++ Builder
int pixelCounter = 0;
int blockCounter = 0;
Graphics::TBitmap *BitMap = new Graphics::TBitmap();
Byte *P;
try
{
BitMap->LoadFromFile("sample.bmp");
if ( letterNo*4 > (BitMap->Height*BitMap->Width*3) )
{
ShowMessage( "The Image cannot be loaded with this amount of text. Sorry!" );
Application->Terminate();
}
…
pixelCounter := 0;
blockCounter := 0;
BitMap := TBitMap.create;
try
BitMap.LoadFromFile('sample.bmp');
if ( letterNo*4 > (BitMap.Height*BitMap.Width*3) ) then
begin
ShowMessage('The Image cannot be loaded with this amount of text. Sorry!');
Application.Terminate;
end;
…
• حلقه هاي تو در توي y و x تمامي مراحل جاسازي را انجام مي دهند. فرمان ScanLine تمامي پيكسل هاي موجود در خط y را در متغيير p قرار مي دهد. سپس پردازش هاي مورد نياز بر روي پيكسل ها درون حلقه x انجام مي گيرد. البته قبل از انجام هر كاري در حلقه x، ابتدا اين مسئله چك مي شود كه تعداد پيكسل هاي پردازش شده از تعداد كاراكترها و نشانگرها تجاوز نكند.
C++ Builder
for ( int y = 0; y <= BitMap->Height-1; y++ )
{
P = (Byte*)BitMap->ScanLine[y];
for ( int x = 0; x <= BitMap->Width-1; x++ )
{
if (letterNo*4 >= pixelCounter)
{
P[3*x] = P[3*x] & 0xFC;
P[3*x] = P[3*x] | dividedLetters[blockCounter];
blockCounter = blockCounter + 1;
P[3*x+1] = P[3*x+1] & 0xFC;
P[3*x+1] = P[3*x+1] | dividedLetters[blockCounter];
blockCounter = blockCounter + 1;
P[3*x+2] = P[3*x+2] & 0xFC;
P[3*x+2] = P[3*x+2] | dividedLetters[blockCounter];
blockCounter = blockCounter + 1;
pixelCounter = pixelCounter + 1;
}
} // end FOR X
} // end FOR Y
for y := 0 to BitMap.Height -1 do
begin
P := BitMap.ScanLine[y];
for x := 0 to BitMap.Width -1 do
begin
if (letterNo*4 >= pixelCounter) then
begin
P[3*x] := P[3*x] and $FC;// data;
P[3*x] := P[3*x] or dividedLetters[blockCounter];
blockCounter := blockCounter + 1;
P[3*x+1] := P[3*x+1] and $FC;
P[3*x+1] := P[3*x+1] or dividedLetters[blockCounter];
blockCounter := blockCounter + 1;
P[3*x+2] := P[3*x+2] and $FC;
P[3*x+2] := P[3*x+2] or dividedLetters[blockCounter];
blockCounter := blockCounter + 1;
pixelCounter := pixelCounter + 1;
end;
end; // end FOR X
end ; // end FOR Y
• حال، ابتدا دو بيت كم ارزشِ هر سه بايت موجود در پيكسل فعلي با استفاده از عملگر AND تبديل به صفر مي شوند سپس اطلاعات مورد نياز در آن دو بيتي ها با استفاده از OR جاسازي مي شوند.
C++ Builder
P[3*x] = P[3*x] & 0xFC;
P[3*x] = P[3*x] | dividedLetters[blockCounter];
blockCounter = blockCounter + 1;
P[3*x+1] = P[3*x+1] & 0xFC;
P[3*x+1] = P[3*x+1] | dividedLetters[blockCounter];
blockCounter = blockCounter + 1;
P[3*x+2] = P[3*x+2] & 0xFC;
P[3*x+2] = P[3*x+2] | dividedLetters[blockCounter];
blockCounter = blockCounter + 1;
pixelCounter = pixelCounter + 1;
P[3*x] := P[3*x] and $FC;
P[3*x] := P[3*x] or dividedLetters[blockCounter];
blockCounter := blockCounter + 1;
P[3*x+1] := P[3*x+1] and $FC;
P[3*x+1] := P[3*x+1] or dividedLetters[blockCounter];
blockCounter := blockCounter + 1;
P[3*x+2] := P[3*x+2] and $FC;
P[3*x+2] := P[3*x+2] or dividedLetters[blockCounter];
blockCounter := blockCounter + 1;
توضيح تابع divideLetter
اين تابع دو ورودي دارد كه يكي كاراكتر currentLetterو ديگري عددي بين 0 و 3 است. كار تابع اين است كه بعد از گرفتن كاراكتر ورودي، تمامي دو بيتي هاي آن كاراكتر را بجز دو بيتي شماره n (شمارش از چپ به راست) صفر نمايند. سپس آن دو بيتي را بعنوان اولين دو بيتي قرار مي دهيد (توسط shr) و بعنوان خروجي تابع بر مي گرداند. بعنوان مثال اگر كاراكتر (01100100) وعدد 2 ورودي تابع باشد، ابتدا تمامي دو بيتي ها بغير از دو بيتي شماره سه تبديل به صفر مي شوند:
00 10 00 00
سپس با استفاده از شيفت راست، خروجي تابع بصورت زير در خواهد آمد:
00 00 00 10
به کدها توجه نماييد:
C++ Builder
int divideLetter( int currentLetter, int n )
{
int splitPart;
splitPart = currentLetter & (int)( 3*(pow(4,3-n)) );
splitPart = splitPart >> ( 2*(3-n) );
return splitPart;
}
function divideLetter( currentLetter : Integer; n : Integer ) : ® Integer;
var
splitPart : Integer;
begin
splitPart := currentLetter and ( 3*round(power(4,3-n)) );
splitPart := splitPart shr ( 2*(3-n) );
result := splitPart;
end;
• در اينجا بمنظور مشاهده تصويري كه متون محرمانه در آن جاسازي شده است، بكار رفته اند. همچنين خروجي تصويري برنامه نيز با نام SampleWithText.bmp در همان مسيري كه فايل اجرايي قرار دارد، ذخيره مي شود.
C++ Builder
newImage->Height = BitMap->Height;
newImage->Width = BitMap->Width;
newImage->Canvas->Draw(0,0,BitMap);
BitMap->SaveToFile( "SampleWithText.bmp" );
newImage.Height := BitMap.Height;
newImage.Width := BitMap.Width;
newImage.Canvas.draw(0,0,BitMap);
BitMap.SaveToFile( 'SampleWithText.bmp' );
در اين قسمت تنها به نكات جديد موجود اشاره مي نمايم و از اشاره به مطالبي كه قبلا به آنها پرداخته شده صرفه نظر مي كنيم زيرا بسياري از نكات را در قسمت قبل از نظر گذرانديم.
• در اينجا ابتدا فايل تصويري SampleWithText.bmp را در متغيير BitMap بارگذاري مي كنيم.
C++ Builder
BitMap->LoadFromFile( "SampleWithText.bmp" );
BitMap.LoadFromFile('SampleWithText.bmp');
• اينک، ابتدا مقادير جاسازي شده در چهار پيكسل اول را استخراج نموده و هر دو بيت را درون يكي از خانه هاي آرايه usefulBytesForSize قرار ميدهيم (در مجموع اين آرايه از 12 خانه تشكيل شده است و مسئول نگهداری تعداد فايل ها مي باشد)
C++ Builder
for ( int y = 0; y <= BitMap->Height-1; y++ )
{
P = (Byte*)BitMap->ScanLine[y];
for ( int x = 0; x <= BitMap->Width-1; x++ )
if (blockCounter <= 11) // please refer to usefulBytesForSize
{
usefulBytesForSize[blockCounter] = P[3*x];
blockCounter = blockCounter + 1;
usefulBytesForSize[blockCounter] = P[3*x+1];
blockCounter = blockCounter + 1;
usefulBytesForSize[blockCounter] = P[3*x+2];
blockCounter = blockCounter + 1;
}
}
for y := 0 to BitMap.Height - 1 do
begin
P := BitMap.ScanLine[y];
for x := 0 to BitMap.Width - 1 do
if (blockCounter <= 11) then
begin
usefulBytesForSize[blockCounter] := P[3*x];
blockCounter := blockCounter + 1;
usefulBytesForSize[blockCounter] := P[3*x+1];
blockCounter := blockCounter + 1;
usefulBytesForSize[blockCounter] := P[3*x+2];
blockCounter := blockCounter + 1;
end;
end;
• حال از طريق حلقه موجود، مقادير دو بيتي موجود در آرايه مذكور را بصورت كاراكتر در آورده (هشت بيتي) و مقدار عددي را كه در مبناي هشت ذخيره شده است به مبناي ده تبديل مي كنيم و درون متغير letterSize قرار مي دهيم.
C++ Builder
for ( int i = 1; i <= 3; i++ ) // 3 is storage size
{
jointLetters[4*(i-1)] = joinLetter(usefulBytesForSize[4*(i-1)], 0);
jointLetters[4*(i-1)+1] = joinLetter(usefulBytesForSize[4*(i-1)+1], 1);
jointLetters[4*(i-1)+2] = joinLetter(usefulBytesForSize[4*(i-1)+2], 2);
jointLetters[4*(i-1)+3] = joinLetter(usefulBytesForSize[4*(i-1)+3], 3);
if ( i == 1 )
letterSize = 256*256 * ( jointLetters[4*(i-1)] |
jointLetters[4*(i-1)+1] |
jointLetters[4*(i-1)+2] |
jointLetters[4*(i-1)+3] );
else if ( i == 2 )
letterSize = letterSize + 256 * ( jointLetters[4*(i-1)] |
jointLetters[4*(i-1)+1] |
jointLetters[4*(i-1)+2] |
jointLetters[4*(i-1)+3] );
else if ( i == 3 )
letterSize = letterSize + ( jointLetters[4*(i-1)] |
jointLetters[4*(i-1)+1] |
jointLetters[4*(i-1)+2] |
jointLetters[4*(i-1)+3] );
}
for i := 1 to 3 do // 3 is storage size
begin
jointLetters[4*(i-1)] := joinLetter(usefulBytesForSize[4*(i-1)], 0);
jointLetters[4*(i-1)+1] := joinLetter(usefulBytesForSize[4*(i-1)+1], 1);
jointLetters[4*(i-1)+2] := joinLetter(usefulBytesForSize[4*(i-1)+2], 2);
jointLetters[4*(i-1)+3] := joinLetter(usefulBytesForSize[4*(i-1)+3], 3);
if ( i = 1 ) then
letterSize := 256*256 * ( jointLetters[4*(i-1)] or jointLetters[4*(i-1)+1] or
jointLetters[4*(i-1)+2] or jointLetters[4*(i-1)+3] )
else if ( i = 2 ) then
letterSize := letterSize + 256 * ( jointLetters[4*(i-1)] or jointLetters[4*(i-1)+1] or
jointLetters[4*(i-1)+2] or jointLetters[4*(i-1)+3] )
else if ( i = 3 ) then
letterSize := letterSize + ( jointLetters[4*(i-1)] or jointLetters[4*(i-1)+1] or
jointLetters[4*(i-1)+2] or jointLetters[4*(i-1)+3] );
end;
• حال كه تعداد كاراكتر هاي مورد نياز براي استخراج را در دست داريم مي توانيم عمليات را آغاز نماييم. ابتدا تمام دو بيتي هاي موجود در بايتهاي پيكسل را درون خانه هاي آرايه jointLetters قرار مي دهيم. اينكار را با استفاده از تابع joinLetter (كه بعدا به آن اشاره مي نماييم) انجام مي دهيم.
C++ Builder
for ( int y = 0; y <= BitMap->Height-1; y++ )
{
P = (Byte*)BitMap->ScanLine[y];
for ( int x = 0; x <= BitMap->Width-1; x++ )
{
if ( blockCounter <= (letterSize+3)*4 )
{
jointLetters[blockCounter] = joinLetter( P[3*x], blockCounter % 4 );
blockCounter = blockCounter + 1;
jointLetters[blockCounter] = joinLetter( P[3*x+1], blockCounter % 4 );
blockCounter = blockCounter + 1;
jointLetters[blockCounter] = joinLetter( P[3*x+2], blockCounter % 4 );
blockCounter = blockCounter + 1;
}
} // end FOR X
} // end FOR Y
for y := 0 to BitMap.height -1 do
begin
P := BitMap.ScanLine[y];
for x := 0 to BitMap.width -1 do
begin
if ( blockCounter <= (letterSize+3)*4 ) then
begin
jointLetters[blockCounter] := joinLetter( P[3*x], blockCounter mod 4 );
blockCounter := blockCounter + 1;
jointLetters[blockCounter] := joinLetter( P[3*x+1], blockCounter mod 4 );
blockCounter := blockCounter + 1;
jointLetters[blockCounter] := joinLetter( P[3*x+2], blockCounter mod 4 );
blockCounter := blockCounter + 1;
end;
end; // end FOR X
end ; // end FOR Y
• درکدهای خطوط زير كه تمامي دو بيتي ها را در اختيار خود داريم، هر چهار دو بيتي را تبديل به يك كاراكتر مي نماييم و كاراكتر هاي حاصل را پشت سر هم قرار داده تا متن جاسازي شده در تصوير را بصورت كامل مشاهده نماييم.
C++ Builder
for ( int i = 4; i <= letterSize+2; i++ )
// Starting from 4 because the first 3 letters store the size of the message
{
singleChar = char( jointLetters[4*(i-1)] |
jointLetters[4*(i-1)+1] |
jointLetters[4*(i-1)+2] |
jointLetters[4*(i-1)+3] );
m = m + singleChar;
}
MessageMemo->Lines->Add(m);
for i := 4 to letterSize+2 do
// Starting from 4 because the first 3 letters store the size of the message
begin
singleChar := char( jointLetters[4*(i-1)] or jointLetters[4*(i-1)+1] or ®
jointLetters[4*(i-1)+2] or jointLetters[4*(i-1)+3] );
m := m + singleChar;
end;
MessageMemo.Lines.Add(m);
