نمایش/عدم نمایش سایدبار
رفتن به بالای صفحه
أَللّهُمَّ ارْزُقْنی شَفاعَةَ الْحُسَیْنِ یَومَ الْوُرُودِ
مهدی دمیرچیلو

آموزش ماژول Structural Analysis and Shape Descriptors در OpenCV

501

به نام خدا : این مطلب کلا مربوطه به کانتور ( contour )، از پیدا کردنش گرفته تا هر تابعی که باهاش مرتبط باشه؛ البته تابع رسم کانتور ( drawContours )، تو مطلب آموزش Drawing Functions در OpenCV قرار داره!.

آموزش ماژول Structural Analysis and Shape Descriptors در OpenCV

 

اصطلاحات مورد نیاز

1) چند ضلعی محدب ( Convex Polygon ) و چند ضلعی مقعر ( Concave Polygon ) : اگه چند ضلعی، زاویه داخلی بیشتر از 180 درجه نداشته باشه، محدب هستش؛ در غیر اینصورت، مقعر هستش.

Convex Polygon vs Concave Polygon

 

2) نحوه محاسبه محیط ( perimeter ) و مساحت ( area ) اشکال هندسی : تکمیل شود؟؟؟؟؟؟؟؟؟؟؟?????????????

 

3) فرق تصاویر Raster با Vector : تکمیل شود؟؟؟؟؟؟؟؟؟؟؟?????????????

 

1) تابع approxPolyDP

1) تابع approxPolyDP : یک منحنی / چند-ضلعی ( curve / polygonal ) را با منحنی / چند-ضلعی دیگری با رئوس کمتر تقریب میزند به طوری که فاصله بین آنها کمتر یا برابر با دقت مشخص شده باشد؛ این تابع از الگوریتم Douglas-Peucker استفاده میکند.

 

تعریف تابع :

توضیح پارامترها :

  • curve : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector یا Mat.
  • approxCurve : نتیجه تقریب؛ با نوعی برابر با پارامتر curve.
  • epsilon : دقت تقریبی ( approximation accuracy ) را مشخص میکند؛ این پارامتر، حداکثر فاصله بین منحنی اصلی و تقریب آن است.
  • closed : اگر true باشد، منحنی تقریبی بسته است ( راس اول و آخر آن به هم متصل است )، در غیر این صورت بسته نیست.

 

کد نمونه : در کد زیر، هر contour به همراه تعداد نقاطش با رنگ قرمز نمایش داده شده و مقدار تقریب زده شده و تعداد نقاطش با رنگ سبز!

نتیجه کد بالا :

تصویر خروجی
تصویر ورودی

 

مثالی دیگر در مسیر زیر :

[opencv]/samples/cpp/contours2.cpp

2) تابع arcLength

2) تابع arcLength : طول منحنی ( curve length ) یا محیط کانتور بسته ( closed contour perimeter ) را محاسبه میکند.

 

تعریف تابع :

توضیح پارامترها :

  • curve : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector یا Mat.
  • closed : این پرچم تعیین میکند که منحنی بسته است یا خیر.

 

کد نمونه : به تابع approxPolyDP مراجعه کنید.

3) تابع boundingRect

3) تابع boundingRect : حداقل مستطیل مرزی ( به سمت بالا راست ) مجموعه ای از نقاط یا پیکسل های غیر صفر تصویری در مقیاس خاکستری را محاسبه میکند.

 

تعریف تابع :

توضیح پارامترها :

  • array : تصویری در مقیاس خاکستری ( نوع cv::Mat ) یا مجموعه ای از نقطه 2-بعدی ( std::vector ).

 

کد نمونه : همون کد نمونه تابع approxPolyDP هستش فقط یکم ویرایشش کردم!

نتیجه کد بالا :

تصویر خروجی تصویر ورودی
آموزش OpenCV آموزش OpenCV

 

توجه : شاید با این مثال درست توجه این تابع نشید، هم برای این که کامل متوجه عملکرد این تابع بشید و هم فرق توابع minAreaRect و boundingRect رو متوجه بشید، به کد نمونه موجود در تابع minAreaRect مراجعه کنید. ( همین مطلب! )

4) تابع boxPoints

4) تابع boxPoints : چهار راس یک مستطیل چرخیده شده را پیدا میکند؛ این تابع برای رسم مستطیل چرخیده شده ( کلاس RotatedRect ) مفید است؛ در C++ به جای استفاده از این تابع، می توانید مستقیماً از متد RotatedRect::points استفاده کنید!

 

تعریف تابع :

توضیح پارامترها :

  • box : ورودی مستطیل چرخیده شده.
  • points : آرایه خروجی چهار راس مستطیل.

 

کد نمونه :

نتیجه کد بالا :

آموزش OpenCV

5) تابع connectedComponents

5) تابع connectedComponents : اجزای متصل را با تصویر-برچسب-گذاری-شده ( labeled image ) در یک تصویر-باینری ( boolean image ) محاسبه میکند.

اگر حداقل یک چارچوب موازی مجاز فعال باشد و اگر سطرهای تصویر حداقل دو برابر تعداد بازگردانده شده توسط getNumberOfCPU ها باشد، این تابع از نسخه موازی الگوریتم ها استفاده میکند.

 

تعریف توابع :

توضیح پارامترها :

  • image : تصویر ورودی باینری،  1-کانال و 8-بیتی که باید برچسب گذاری شود؛ مناطقی که مقدارشون 0 ( سیاه رنگ! ) باشه، به عنوان زمینه در نظر گرفته میشوند و در پارامتر labels هم با 0، مقدار دهی میشوند.
  • labels : تصویر خروجی برچسب گزاری شده.
  • connectivity : نوع اتصال، 4 طرفه یا 8 طرفه.
  • ltype : نوع پارامتر labels را مشخص میکند؛ در حال حاضر CV_32S و CV_16U پشتیبانی میشوند.
  • ccltype : الگوریتم برچسب‌گذاری اجزای متصل ( connected components labeling algorithm ) مورد استفاده را مشخص میکند، در حال حاضر الگوریتم‌های Bolelli (Spaghetti) [27] و Grana (BBDT) [98] و Wu's (SAUF) [280] پشتیبانی میشوند؛ این پارامتر به کمک نوع شمارشی ConnectedComponentsAlgorithmsTypes مقداردهی میشود.
  • return : این تابع، تعداد کل برچسب‌ها را برمیگرداند که با N نمایش داده میشود، شماره گزاری برچسب ها در محدوده [0، N-1] میباشد، که در آن 0 نشان‌دهنده برچسب پس‌زمینه ( background label ) است؛ فلذا حواستون به نوع پارامتر labels و تعداد کل برچسب ها باشه تا سرریزی رخ نده ( منظور این که، بر طبق، تعداد کل برچسب‌ها، مقدار مناسبی برا پارامتر ltype انتخاب کنید! )

 

نوع شمارشی ConnectedComponentsAlgorithmsTypes : توجه داشته باشید که الگوریتم SAUF یک ردیف اصلی از برچسب ها را مجبور میکند در حالی که Spaghetti و BBDT این کار را نمیکنند.

توضیح گزینه ها :

  • CCL_DEFAULT : الگوریتم Spaghetti [27] برای اتصال 8 طرفه، الگوریتم Spaghetti4C [28] برای اتصال 4 طرفه.
  • CCL_WU : الگوریتم SAUF [280] برای اتصال 8 طرفه، الگوریتم SAUF برای اتصال 4 طرفه؛ پیاده سازی موازی شرح داده شده در [26] برای SAUF در دسترس است.
  • CCL_GRANA : الگوریتم BBDT [98] برای اتصال 8 طرفه، الگوریتم SAUF برای اتصال 4 طرفه؛ پیاده سازی موازی شرح داده شده در [26] برای BBDT و SAUF در دسترس است.
  • CCL_BOLELLI : الگوریتم Spaghetti [27] برای اتصال 8 طرفه، الگوریتم Spaghetti4C [28] برای اتصال 4 طرفه؛ اجرای موازی شرح داده شده در [26] برای Spaghetti و Spaghetti4C در دسترس است.
  • CCL_SAUF : مانند CCL_WU؛ ترجیحاً از پرچم با نام الگوریتم ( CCL_SAUF ) به جای پرچم با نام نویسنده اول ( CCL_WU ) استفاده شود!
  • CCL_BBDT : مانند CCL_GRANA؛ ترجیحاً از پرچم با نام الگوریتم ( CCL_BBDT ) به جای پرچم با نام نویسنده اول ( CCL_GRANA ) استفاده شود.
  • CCL_SPAGHETTI : مانند CCL_BOLELLI؛ ترجیحاً از پرچم با نام الگوریتم ( CCL_SPAGHETTI ) به جای پرچم با نام نویسنده اول ( CCL_BOLELLI ) استفاده شود.

 

کد نمونه :

نتیجه کد بالا : برای کد بالا، یه تصویر ساده استفاده کردم، میتونید تصاویر دیگه رو هم تست کنید، اما با تغییر الگوریتم من تغییری در خروجی یا تعداد برچسب ها ندیدم!؟!

UI src
آموزش OpenCV آموزش OpenCV
dst threshold
آموزش OpenCV آموزش OpenCV
6) تابع connectedComponentsWithStats

6) تابع connectedComponentsWithStats : مثل تابع connectedComponents هستش، با این تفاوت که : یک خروجی آماری برای هر برچسب تولید میکند.

اگر حداقل یک چارچوب موازی مجاز فعال باشد و اگر ردیف‌های تصویر حداقل دو برابر تعداد بازگشت شده توسط getNumberOfCPU‌ ها باشد، این تابع از نسخه موازی الگوریتم‌ها ( شامل آمار ) استفاده میکند.

 

تعریف تابع :

توضیح پارامترها :

  • stats : آمار برای هر برچسب، از جمله برچسب پس زمینه ( background )؛ نوع داده CV_32S میباشد؛ این آمار شامل داده هایی نظیر : مختصات نقطه بالا-چپ مستطیل، طول و عرض مستطیل و مساحت مستطیل ( هر برچسب داخل یه مستطیلی قرار داره! )؛  جهت دسترسی داده های این پارامتر، از نوع شمارشی ConnectedComponentsTypes استفاده میکنیم ( کمک میگیریم!، جهت خوانایی بیشتر کد! )؛ برای دیدن نحوه استفاده از پارامترهای stats و centroids به کد نمونه مراجعه کنید!
  • centroids : نقطه مرکزی ( centroids ) برای هر برچسب، از جمله برچسب پس زمینه ( background )؛ نوع داده CV_64F.

 

نوع شمارشی ConnectedComponentsTypes : آمار اجزای متصل.

توضیح گزینه ها :

  • CC_STAT_LEFT : سمت چپ ترین مختصات (x) که شروع شامل جعبه مرزی ( bounding box ) در جهت افقی است.
  • CC_STAT_TOP : بالاترین مختصات (y) که شروع شامل جعبه مرزی ( bounding box ) در جهت عمودی است.
  • CC_STAT_WIDTH : اندازه افقی جعبه مرزی ( bounding box ).
  • CC_STAT_HEIGHT : اندازه عمودی جعبه مرزی ( bounding box ).
  • CC_STAT_AREA : مساحت کل ( بر حسب پیکسل ) جزء متصل.

 

کد نمونه : -

نتیجه کد بالا :

Threshold SRC
آموزش تابع connectedComponentsWithStats در OpenCV آموزش تابع connectedComponentsWithStats در OpenCV
UI DST
آموزش تابع connectedComponentsWithStats در OpenCV آموزش تابع connectedComponentsWithStats در OpenCV
7) تابع contourArea

7) تابع contourArea : مساحت کانتور ( contour area ) را محاسبه میکند.

 

تعریف تابع :

توضیح پارامترها :

  • contour : بردار ورودی حاوی نقاط دو بعدی ( رئوس خطوط )، ذخیره شده در std::vector یا cv::Mat.
  • oriented : عملکرد این پارامتر رو متوجه نشدم، جهت تو محاسبه مساحت دخلی نداره آخه!؟

 

کد نمونه : تو کد زیر یه contour ایجاد کردیم و بعد مساحت شو محاسبه کردیم و بعد تقریب contour فوق رو محاسبه کردیم و بعد مساحت جدید رو دوباره محاسبه کردیم.

نتیجه کد بالا : area0 = 56250, area1 = 67500

آموزش تابع contourArea در OpenCV

8) تابع convexHull

8) تابع convexHull : بدنه محدب ( convex hull ) مجموعه نقاط 2-بعدی را با استفاده از الگوریتم اسکلانسکی [228] ( که دارای پیچیدگی O(N logN) در اجرای فعلی است )، پیدا میکند.

 

تعریف تابع :

توضیح پارامترها :

  • points : مجموعه نقاط 2-بعدی ورودی، ذخیره شده در std::vector یا cv::Mat.
  • hull : خروجی بدنه محدب.
  • clockwise : پرچم جهت گیری؛ اگر true باشد، بدنه محدب خروجی در جهت عقربه های ساعت است؛ در غیر این صورت، خلاف جهت عقربه های ساعت است.
  • returnPoints : نوع داده خروجی پارامتر hull را تعیین میکند؛ وقتی true باشد، تابع نقاط بدنه محدب را برمی‌گرداند، نوع داده std::vector<cv::Point> میباشد؛ در غیر این صورت، تابع تعدادی عدد برمیگرداند که شماره خونه نقاط بدنه محدب از آرایه ورودی ( points ) میباشند، نوع داده std::vector<int> میباشد؛به کدهای نمونه نگاه کنید تا کامل متوجه بشید ( اگه متوجه نشدید هنوز ).

توجه : پارامترهای points و hull باید آرایه‌های متفاوتی باشند، پردازش داخلی / درجا ( inplace processing ) پشتیبانی نمیشود.

 

کد نمونه 1 : وقتی که : returnPoints = true

کد نمونه 2 : وقتی که : returnPoints = false

نتیجه کدهای بالا : نتیجه هر دو کد یکسان هستش.

آموزش تابع convexHull در OpenCV

9) تابع convexityDefects

9) تابع convexityDefects : عیوب تحدب یک کانتور را پیدا میکند؛ شکل زیر عیوب تحدب کانتور دست را نشان میدهد :

OpenCV convexityDefects

 

تعریف تابع :

توضیح پارامترها :

  • contour : کانتور ورودی.
  • convexhull : بدنه محدب، به دست آمده با استفاده از تابع convexHull، که باید شامل شاخص هایی ( indices ) از نقاط کانتوری باشد که بدنه را میسازند ( پارامتر returnPoints از تابع convexHull، باید false باشه ).
  • convexityDefects : بردار خروجی عیوب تحدب ( convexity defects )؛ هر نقص تحدب به عنوان بردار عدد صحیح 4 عنصری ( معروف به Vec4i ) نشان داده میشود : ( start_index، end_index، farthest_pt_index، fixpt_depth ).

 

کد نمونه :

نتیجه کد بالا :

تصویر خروجی تصویر ورودی
آموزش تابع convexityDefects در OpenCV آموزش تابع convexityDefects در OpenCV
10 و 11) توابع createGeneralizedHoughBallard و createGeneralizedHoughGuil

10) تابع createGeneralizedHoughBallard : تابع createGeneralizedHoughBallard، یه شیء از کلاس cv::GeneralizedHoughBallard ایجاد میکنه و مقداردهی اولیه اش میکنه؛ این کلاس : الگوی دلخواه را در تصویر خاکستری پیدا میکند؛ این کلاس : فقط موقعیت ( position ) را تشخیص میدهد ( ترجمه ( translation ) و چرخش ( rotation ) را تشخیص نمیدهد ) [15].

11) تابع createGeneralizedHoughGuil : تابع createGeneralizedHoughGuil هم یه شیء از کلاس cv::GeneralizedHoughGuil ایجاد میکنه و مقداردهی اولیه اش میکنه؛ این کلاس : الگوی دلخواه را در تصویر خاکستری پیدا میکند؛ این کلاس : موقعیت ( position )، ترجمه ( translation ) و چرخش ( rotation ) را تشخیص میدهد [102].

توجه : هر دو کلاس cv::GeneralizedHoughBallard و cv::GeneralizedHoughGuil، از کلاس cv::GeneralizedHough مشتق شدن،

 

تعریف توابع :

 

کد نمونه : منبع این کد ( که البته ویرایشش کردم یکم ) این لینک هستش : opencv-GeneralizedHough-example

نتیجه کد بالا :

dst tmp src
آموزش GeneralizedHough در OpenCV آموزش GeneralizedHough در OpenCV آموزش GeneralizedHough در OpenCV

 

تکمیل شود : توضیح کامل کلاس های فوق.

12) تابع findContours

12) تابع findContours : با استفاده از الگوریتم [237]، کانتورها را در یک تصویر باینری پیدا میکند؛ کانتورها یک ابزار مفید برای تجزیه و تحلیل شکل ( shape analysis ) و تشخیص اشیا ( object detection ) و recognition هستند. ( اگه درباره فرق object detection و recognition میخواید مطالعه کنید، میتونید به این مطلب مراجعه کنید : Object Detection vs Object Recognition vs Image Segmentation )

 

تعاریف تابع :

توضیح پارامترها :

  • image : تصویر ورودی؛ 1-کاناله و 8-بیتی؛ پیکسل های غیر صفر به عنوان 1 در نظر گرفته میشوند؛ پیکسل های صفر، 0 باقی می مانند، بنابراین تصویر به عنوان باینری در نظر گرفته میشود؛ میتوانید از توابعی نظیر inRange، threshold، adaptiveThreshold، Canny و... برای ایجاد یک تصویر باینری از مقیاس خاکستری یا رنگی استفاده کنید؛ اگر پارامتر mode برابر با RETR_CCOMP یا RETR_FLOODFILL باشد، این پارامتر، همچنین میتواند یک تصویر عدد صحیح 32 بیتی از برچسب ها باشد ( CV_32SC1 ).
  • contours : کانتورهای شناسایی شده؛ کانتورها به عنوان بردار نقاط ذخیره میشوند ( std::vector<std::vector<cv::Point>> ).
  • hierarchy : بردار خروجی اختیاری ( std::vector<cv::Vec4i> )، حاوی اطلاعاتی در مورد توپولوژی تصویر ( image topology )؛ به تعداد کانتورها، عناصر دارد؛ برای هر کانتور i ام یی داریم : hierarchy[i][0] = Next و hierarchy[i][1] = Previous و hierarchy[i][2] = First_Child و hierarchy[i][3] = Parent؛ اگر برای کانتور i ام، هیچ کانتور بعدی ( next )، قبلی ( previous )، والد ( parent ) یا تودرتو ( nested ) وجود نداشته باشد، عناصر مربوط به hierarchy[i] منفی ( -1 ) خواهند بود.
  • mode : حالت الگوریتم بازیابی کانتور؛ به کمک نوع شمارشی RetrievalModes مقدار دهی میشود.
  • method : الگوریتم تقریب کانتور؛ به کمک نوع شمارشی ContourApproximationModes مقدار دهی میشود.
  • offset : آفست اختیاری که توسط آن هر نقطه کانتور جابه‌جا میشود؛ این در صورتی مفید است که کانتورها از ROI تصویر استخراج شوند و سپس آنها باید در کل زمینه تصویر تجزیه و تحلیل شوند.

 

توجه : اگر mode = RETR_FLOODFILL باشد، تصویر ورودی تابع findContours باید از نوع CV_32SC1 باشد، وگرنه تابع خطا میدهد ( اما تابع برا mode = RETR_CCOMP، خطایی نمیده! )؛ برای تبدیل نوع تصویر ورودی به CV_32SC1، از کد زیر میتویند استفاده کنید :

 

نوع شمارشی RetrievalModes :

توضیح گزینه ها :

  • RETR_EXTERNAL : فقط کانتورهای بیرونی شدید را بازیابی میکند؛ برای تمام کانتورها داریم : hierarchy[i][2] = hierarchy[i][3] = -1
  • RETR_LIST : تمام کانتورها را بدون ایجاد هیچ گونه روابطه ( relationships ) سلسله مراتبی ( hierarchical ) بازیابی میکند.
  • RETR_CCOMP : تمام کانتورها را بازیابی میکند و آنها را در یک سلسله مراتب دو سطحی ( two-level hierarchy ) سازماندهی میکند؛ در سطح بالا، مرزهای خارجی اجزا وجود دارد؛ در سطح دوم، حدود سوراخ ها وجود دارد؛ اگر کانتور دیگری در داخل سوراخ یک جزء متصل وجود داشته باشد، همچنان در سطح بالایی قرار میگیرد.
  • RETR_TREE : تمام کانتورها را بازیابی میکند و یک سلسله مراتب کامل ( full hierarchy ) از کانتورهای  تو در تو را بازسازی میکند.
  • RETR_FLOODFILL : توضیحی درباره این گزینه در سایت OpenCV داده نشده است.

 

نوع شمارشی ContourApproximationModes :

توضیح گزینه ها :

  • CHAIN_APPROX_NONE : تمام نقاط کانتور را کاملاً ذخیره میکند؛ کاملاً تمام نقاط کانتور را ذخیره می کند. یعنی هر 2 نقطه متوالی ( 2 subsequent points ) (x1,y1) و (x2,y2) از کانتور، همسایه افقی، عمودی یا مورب خواهند بود، یعنی max(abs(x1-x2), abs(y2-y1)) == 1.
  • CHAIN_APPROX_SIMPLE : بخش های افقی، عمودی و مورب را فشرده میکند و تنها نقاط انتهایی آنها را باقی میگذارد؛ به عنوان مثال، یک کانتور مستطیلی به سمت راست با 4 نقطه کدگذاری میشود.
  • CHAIN_APPROX_TC89_L1 : یکی از طعم های الگوریتم تقریب زنجیره Teh-Chin [245] را اعمال میکند.
  • CHAIN_APPROX_TC89_KCOS : یکی از طعم های الگوریتم تقریب زنجیره Teh-Chin [245] را اعمال میکند.

 

hierarchy چیست ( توضیحاتی درباره پارامتر hierarchy ) : به طور معمول از تابع findContours برای تشخیص اشیاء در یک تصویر استفاده میکنیم؛ که خب اشیا در مکان های مختلفی قرار دارند؛ در برخی موارد، برخی از اشکال در داخل اشکال دیگر قرار میگیرند ( nested )؛ در این صورت، شکل بیرونی را والد ( parent ) و شکل درونی را فرزند ( child ) می‌نامیم؛ به این ترتیب، کانتورها در یک تصویر تا حدی با یکدیگر ارتباط دارند؛ و می‌توانیم نحوه اتصال یک کانتور به یکدیگر را مشخص کنیم، مثلاً آیا فرزند کانتور دیگری است یا والد است و...؛ نمایش این رابطه، سلسله مراتب ( Hierarchy ) نامیده میشود.

تصویر زیر را در نظر بگیرید :

What is Hierarchy

  • در این تصویر چند شکل وجود دارد که از 0 تا 5 شماره گذاری کردم؛ 2 و 2a کانتور خارجی و داخلی بیرونی ترین جعبه را نشان میدهد.
  • کانتورهای 0 و 1 و 2 خارجی یا بیرونی ترین هستند؛ میتوان گفت، آنها در سلسله مراتب-0 ( hierarchy-0 ) یا به عبارت ساده تر، در یک سطح سلسله مراتبی ( hierarchy level ) هستند.
  • کانتور 2a را میتوان فرزند کانتور 2 در نظر گرفت ( یا برعکس، کانتور 2 والد کانتور 2a است! )؛ پس بگذارید در سلسله مراتب-1 ( hierarchy-1 ) باشد؛ به طور مشابه کانتور 3 فرزند کانتور 2 است و در سلسله مراتب بعدی قرار میگیرد.
  • در نهایت کانتورهای 4 و 5 فرزندان کانتور 3a هستند و در آخرین سطح سلسله مراتبی قرار دارند.

بنابراین هر کانتور اطلاعات مربوط به سلسله مراتب خود را دارد، فرزندش کیست، والدینش کیست و...؛ OpenCV آن را به صورت آرایه ای چهار مقداره ( cv::Vec4i ) نشان میدهد :

  • Next : نشان دهنده کانتور بعدی در همان سطح سلسله مراتبی است؛ به عنوان مثال، کانتور-0 را در عکس بالا بگیرید، چه کسی کانتور بعدی در همان سطح آن است؟ کانتور-1، بنابراین داریم Next = 1؛ به طور مشابه برای کانتور-1، بعدی کانتور-2 است، پس Next = 2.
  • Previous : نشان دهنده کانتور قبلی در همان سطح سلسله مراتبی است؛ به عنوان مثال، در تصویر بالا، کانتور قبلیِ کانتور-1، کانتور-0 در همان سطح است؛ به طور مشابه برای کانتور-2، کانتور-1 است؛ و برای کانتور-0 هیچ کانتور قبلی وجود ندارد، پس مقدار آن -1 خواهد بود.
  • First_Child : نشان دهنده اولین فرزند کانتور است؛ برای کانتور-2، فرزند، کانتور-2a است؛ بنابراین مقدار شاخص مربوط به کانتور-2a را دریافت میکند؛ در مورد کانتور-3a چطور؟ دو فرزند دارد، اما ما تنها فرزند اول را می‌گیریم، یعنی کانتور-4، بنابراین برای کانتور-3a داریم First_Child = 4.
  • Parent : نشان دهنده کانتور والد است؛ برای کانتور-4 و هم برای کانتور-5، کانتور والد، کانتور-3a است؛ برای کانتور-3a، کانتور-3 است و...

توجه : اگر هیچ مقداری برای هر یک از Next / Previous / First_Child / Parent، وجود نداشته باشد، مقدار مربوطه -1 خواهد بود.

فک کنم همین مقدار توضیح برای hierarchy کافی باشه.

 

کد نمونه 1 : در این مثال، علاوه بر بحث پیدا کردن کانتورها و رسمشون و...، هدف اصلیم اینه که تاثیر تغییر پارامتر method بر تعداد نقاط هر کانتور رو ببینیم، که خب انتخاب مقدار مناسب برای این پارامتر بعضا خیلی مهمه؛ تغییر این پارامتر باعث تغییر تعداد نقاط هر کانتور میشه، تاثیری تو تعداد کانتورها و یا ترتیبشون نداره.

نتیجه کد بالا برای تصویر ورودی 1.png :

تصویر خروجی تصویر ورودی
آموزش تابع findContours در OpenCV آموزش تابع findContours در OpenCV
تعداد نقاط کانتور به ازای مقادیر مختلف پارامتر method
  • cv::ContourApproximationModes::CHAIN_APPROX_NONE == 2425
  • cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE == 1019
  • cv::ContourApproximationModes::CHAIN_APPROX_TC89_KCOS == 278
  • cv::ContourApproximationModes::CHAIN_APPROX_TC89_L1 == 434

نتیجه کد بالا برای تصویر ورودی 2.png :

تصویر خروجی تصویر ورودی
آموزش تابع findContours در OpenCV آموزش تابع findContours در OpenCV
تعداد نقاط کانتور به ازای مقادیر مختلف پارامتر method
cv::ContourApproximationModes::CHAIN_APPROX_NONE == 2196
cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE == 4
cv::ContourApproximationModes::CHAIN_APPROX_TC89_KCOS == 4
cv::ContourApproximationModes::CHAIN_APPROX_TC89_L1 == 4

 

کد نمونه 2 : حالا بریم سراغ تغییر پارامتر mode، و ببینیم تغییرش چه تاثیری در خروجی میزاره؛ با تغییر این پارامتر، تعداد کانتورها ممکنه تغییر کنه، ترتیبشون ممکنه تغییر کنه، اما تعداد نقاط هر کانتور تغییر نمیکنه.

نتیجه کد بالا :

RETR_LIST RETR_EXTERNAL تصویر ورودی
آموزش تابع findContours در OpenCV آموزش تابع findContours در OpenCV آموزش تابع findContours در OpenCV
RETR_FLOODFILL RETR_TREE RETR_CCOMP
آموزش تابع findContours در OpenCV آموزش تابع findContours در OpenCV آموزش تابع findContours در OpenCV
13 و 14 و 15) توابع fitEllipse و fitEllipseAMS و fitEllipseDirect

13 و 14 و 15) توابع fitEllipse و fitEllipseAMS و fitEllipseDirect : یک بیضی در اطراف مجموعه ای از نقاط 2-بعدی محاسبه میکند؛ این تابع، مستطیل چرخشی ( کلاس RotatedRect ) را که بیضی در آن حک شده است برمی گرداند؛ برا دیدن تفاوت های این 3تا تابع، به سایت OpenCV مراجعه کنید، توضحش میره تو فرمول های ریاضی که خارج از حوصله من، شما و این مطلبه!

  • تابع fitEllipse : از اولین الگوریتم توصیف شده توسط [80] استفاده شده است.
  • تابع fitEllipseAMS : از میانگین مربع تقریبی ( AMS ) پیشنهاد شده توسط [244] استفاده شده است.
  • تابع fitEllipseDirect : از روش حداقل مربع مستقیم ( Direct least square ) توسط [81] استفاده شده است.

توجه : برنامه‌نویس باید در نظر داشته باشد که ممکن است داده‌های بیضی ( rotatedRect ) برگشتی دارای شاخص‌های منفی باشند، زیرا نقاط داده به مرز عنصر Mat نزدیک هستند.

 

تعریف توابع :

توضیح پارامترها :

  • points : مجموعه نقطه 2-بعدی ورودی، ذخیره شده در std::vector<cv::Point> یا cv::Mat؛ باید حداقل حاوی 5 نقطه باشد!

 

کد نمونه :

نتیجه کد بالا :

تصاویر ورودی
آموزش تابع fitEllipse در OpenCV آموزش تابع fitEllipse در OpenCV آموزش تابع fitEllipse در OpenCV
تصاویر خروجی
آموزش تابع fitEllipse در OpenCV آموزش تابع fitEllipse در OpenCV آموزش تابع fitEllipse در OpenCV

 

توجه : یک کد نمونه برای این 3 تا تابع در مسیر زیر قرار داره :

[opencv]/samples/cpp/fitellipse.cpp

16) تابع fitLine

16) تابع fitLine : یک خط را به مجموعه نقاط 2-بعدی یا 3-بعدی منطبق میکند.

 

تعریف تابع :

توضیح پارامترها :

  • points : بردار ورودی نقاط 2-بعدی یا 3-بعدی، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
  • line : پارامترهای خط خروجی؛ در مورد برازش 2-بعدی ( 2D fitting )، باید برداری 4 عنصره ( مانند Vec4f ) باشد : (vx، vy، x0، y0)، که در آن (vx، vy) یک بردار نرمال شده ( normalized vector ) هم خط به خط ( collinear to the line ) و (x0، y0) یک نقطه روی خط است؛ در مورد برازش 3-بعدی ( 3D fitting )، باید برداری 6 عنصره ( مانند Vec6f ) باشد : (vx، vy، vz، x0، y0، z0)، که در آن (vx، vy، vz) یک بردار نرمال شده هم خط به خط است و (x0، y0، z0) یک نقطه روی خط است.
  • distType : فاصله استفاده شده توسط M-estimator؛ به کمک نوع شمارشی DistanceTypes مقدار دهی میشود.
  • param : پارامتر عددی ( C ) برای برخی از مقادیر پارامتر distType؛ اگر 0 باشد، یک مقدار بهینه انتخاب میشود.
  • reps : دقت کافی ( Sufficient accuracy ) برای شعاع ( فاصله بین مبدأ مختصات و خط ).
  • aeps : دقت کافی برای زاویه؛ 0.01 یک مقدار پیش فرض خوب برای پارامترهای reps و aeps خواهد بود.

 

نوع شمارشی DistanceTypes :

 

کد نمونه :

نتیجه کد بالا :

تصاویر ورودی
آموزش تابع fitLine در OpenCV آموزش تابع fitLine در OpenCV
تصاویر خروجی
آموزش تابع fitLine در OpenCV آموزش تابع fitLine در OpenCV
17) تابع HuMoments

17) تابع HuMoments : هفت متغیر Hu را محاسبه میکند ( معرفی شده در [118]؛ همچنین به Image moment مراجعه کنید )؛ که به صورت زیر تعریف شده اند ( \( \eta_{ji} \) مخفف \( \texttt{Moments::nu}_{ji} \) است ) :

\( hu[0] = \eta _{20} + \eta _{02} \)

\( hu[1] = (\eta _{20} - \eta _{02})^{2} + 4 \eta _{11}^{2} \)

\( hu[2] = (\eta _{30} - 3 \eta _{12})^{2} + (3 \eta _{21} - \eta _{03})^{2} \)

\( hu[3] = (\eta _{30} + \eta _{12})^{2} + ( \eta _{21} + \eta _{03})^{2} \)

\( hu[4] = (\eta _{30} - 3 \eta _{12})( \eta _{30} + \eta _{12})[( \eta _{30} + \eta _{12})^{2} - 3( \eta _{21} + \eta _{03})^{2}] + (3 \eta _{21} - \eta _{03})( \eta _{21} + \eta _{03})[3( \eta _{30} + \eta _{12})^{2} - ( \eta _{21} + \eta _{03})^{2}] \)

\( hu[5] = (\eta _{20} - \eta _{02})[( \eta _{30} + \eta _{12})^{2} - ( \eta _{21} + \eta _{03})^{2}] + 4 \eta _{11}( \eta _{30} + \eta _{12})( \eta _{21} + \eta _{03}) \)

\( hu[6] = (3 \eta _{21} - \eta _{03})( \eta _{21} + \eta _{03})[3( \eta _{30} + \eta _{12})^{2} - ( \eta _{21} + \eta _{03})^{2}]-( \eta _{30} - 3 \eta _{12})( \eta _{21} + \eta _{03})[3( \eta _{30} + \eta _{12})^{2} - ( \eta _{21} + \eta _{03})^{2}] \)

ثابت شده است که این مقادیر نسبت به تغیر مقیاس، چرخش و انعکاس تصویر ثابت هستند؛ به جز مورد هفتم که علامت آن با انعکاس تغییر میکند؛ این تغییر ناپذیری با فرض وضوح بینهایت تصویر ( infinite image resolution )، ثابت میشود؛ در مورد تصاویر شطرنجی، متغیرهای Hu محاسبه شده برای تصاویر اصلی و تبدیل شده کمی متفاوت است.

 

تعاریف تابع :

توضیح پارامترها :

  • moments و m : لحظات ( moments ) ورودی!؛ که توسط تابع cv::moments محاسبه میشود.
  • hu : متغیرهای خروجی hu.

 

کد نمونه :

نتیجه کد بالا :

تصویر ورودی
آموزش تابع HuMoments در OpenCV
داده خروجی
hu.size() = 7
hu[0] = 0.290953
hu[1] = 0.0512276
hu[2] = 0.00227685
hu[3] = 0.00188651
hu[4] = 3.89369e-06
hu[5] = 0.000426684
hu[6] = 3.54666e-07

 

تکمیل شود : کاربرد 7 متغییر فوق، در پردازش تصویر و فرولهای مختلف و کاربردشون.

18) تابع intersectConvexConvex

18) تابع intersectConvexConvex : تقاطع دو چند ضلعی محدب ( convex polygons ) را پیدا میکند.

 

توجه : این تابع، محدب بودن هر دو چند ضلعی را تأیید نمیکند!، فلذا اگر محدب نباشند، نتایج نامعتبر را برمی گرداند.

 

تعریف تابع :

توضیح پارامترها :

  • p1 و p2 : چند ضلعی اول و دوم.
  • p12 : چند ضلعی خروجی که مساحت متقاطع ( intersecting area ) را توصیف میکند.
  • handleNested : وقتی true است، اگر یکی از چند ضلعی ها به طور کامل در دیگری محصور باشد، یک تقاطع پیدا میشود؛ وقتی false است، هیچ تقاطعی پیدا نمی شود؛ اگر چند ضلعی ها یک ضلع مشترک داشته باشند یا راس یک چند ضلعی در لبه دیگری قرار گیرد، آنها تودرتو در نظر گرفته نمی‌شوند و بدون توجه به مقدار handleNested یک تقاطع پیدا میشود.
  • return : مقدار مطلق مساحت چندضلعی متقاطع.

 

کد نمونه : این کد در مسیر زیر قرار داره :

[opencv]/samples/cpp/intersectExample.cpp

نتیجه کد بالا :

آموزش تابع intersectConvexConvex در OpenCV

19) تابع isContourConvex

19) تابع isContourConvex : تحدب ( convexity ) کانتور را بررسی میکند؛ کانتور باید ساده باشد، یعنی بدون خود تقاطع ( self-intersections )؛ در غیر این صورت، خروجی تابع تعریف نشده است.

 

تعریف تابع :

توضیح پارامترها :

  • contour : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
  • return : مقدار true یعنی کانتور محدب هستش، مقدار false هم یعنی کانتور محدب نیست!!!

 

کد نمونه :

نتیجه کد بالا :

تصویر خروجی تصویر ورودی
آموزش تابع isContourConvex در OpenCV آموزش تابع isContourConvex در OpenCV
20) تابع matchShapes

20) تابع matchShapes : دو شکل را با هم مقایسه میکند؛ هر سه روش پیاده سازی شده از متغیرهای Hu استفاده می کنند ( به تابع HuMoments مراجعه کنید )

 

تعریف تابع :

توضیح پارامترها :

  • contour1 : اولین تصویر خاکستری یا کانتور.
  • contour2 : دومین تصویر خاکستری یا کانتور.
  • method : روش مقایسه، به کمک نوع شمارشی ShapeMatchModes مقدار دهی میشه.
  • parameter : پارامتر خاص روش ( اکنون پشتیبانی نمیشود !!!!!!!!!!!!!!!!!!!!!! ).
  • return : مقدار برگشتی تابع؛ هر چی نزدیگ 0 باشه یعنی شباهت بیشتر.

 

نوع شمارشی ShapeMatchModes : -

توضیح گزینه ها :

CONTOURS_MATCH_I3 CONTOURS_MATCH_I2 CONTOURS_MATCH_I1
$$ I_3(A,B) = \max _{i=1...7} \frac{ \left| m^A_i - m^B_i \right| }{ \left| m^A_i \right| } $$ $$ I_2(A,B) = \sum _{i=1...7} \left | m^A_i - m^B_i \right | $$ $$ I_1(A,B) = \sum _{i=1...7} \left | \frac{1}{m^A_i} - \frac{1}{m^B_i} \right | $$

 

کد نمونه :

نتیجه کد بالا : نتایج خروجی، گویای همه چیز هستش!!!

تصاویر ورودی
img4 img3 img2 img1
آموزش تابع matchShapes در OpenCV آموزش تابع matchShapes در OpenCV آموزش تابع matchShapes در OpenCV آموزش تابع matchShapes در OpenCV
img8 img7 img6 img5
آموزش تابع matchShapes در OpenCV آموزش تابع matchShapes در OpenCV آموزش تابع matchShapes در OpenCV آموزش تابع matchShapes در OpenCV
نتیجه کد بالا
img1 vs img1 = 0
img1 vs img2 = 2.43836e-05
img1 vs img3 = 8.05214e-05
img1 vs img4 = 8.03113e-05
img1 vs img5 = 8.03231e-05
img1 vs img6 = 2.15973e-05
img1 vs img7 = 0.00916173
img1 vs img8 = 0.00605361
21) تابع minAreaRect

21) تابع minAreaRect : یک مستطیل چرخشی از حداقل مساحت محصور ( minimum area enclosing ) که مجموعه نقاط 2-بعدی ورودی را دربر میگیرد، پیدا میکند ( return میکند! )؛ برنامه‌نویس باید در نظر داشته باشد که RotatedRect برگشتی میتواند حاوی شاخص‌های منفی باشد، زمانی که داده‌ها نزدیک به مرز عنصر Mat باشد.

 

تعریف تابع :

توضیح پارامترها :

  • points : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
  • return : در بالا توضیح دادم!

 

کد نمونه ( فرق توابع minAreaRect و boundingRect ) : در کد زیر از تابع minAreaRect استفاده کردم و مستطیل-چرخیده-شده رو محاسبه کردم و بعد برای نمایش مستطیل-چرخیده-شده ( خروجی تابع minAreaRect، از نوع کلاس RotatedRect هستش ) از دو روش استفاده کردیم، که خب یکیشون به کمک کلاس RotatedRect و دیگری به کمک تابع boxPoints که تو این مطلب معرفی کردم؛ بعد اومدم از تابع boundingRect استفاده کردم تا فرق تابع minAreaRect و boundingRect رو در عمل متوجه بشید.

نتیجه کد بالا :

تصویر خروجی تصویر ورودی
فرق توابع minAreaRect و boundingRect در OpenCV فرق توابع minAreaRect و boundingRect در OpenCV
22) تابع minEnclosingCircle

22) تابع minEnclosingCircle : دایره ای از حداقل مساحت محصور ( minimum area enclosing ) را که مجموعه نقاط 2-بعدی را در بر میگیرد، با استفاده از یک الگوریتم تکراری ( iterative algorithm ) پیدا میکند.

 

تعریف تابع :

توضیح پارامترها :

  • points : بردار حاوی نقاط 2-بعدی ( پارامتر ورودی )، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
  • center : مرکز دایره ( پارامتر خروجی ).
  • radius : شعاع دایره ( پارامتر خروجی ).

 

کد نمونه :

نتیجه کد بالا :

تصویر خروجی تصویر ورودی
آموزش تابع minEnclosingCircle در OpenCV آموزش تابع minEnclosingCircle در OpenCV
23) تابع minEnclosingTriangle

23) تابع minEnclosingTriangle : مثلثی با حداقل مساحت محصور ( minimum area enclosing ) که مجموعه نقاط 2-بعدی را در بر میگیرد، پیدا می کند و مساحت آن را برمی گرداند ( return میکند! )؛ اجرای الگوریتم بر اساس مقالات O'Rourke [192] و Klee و Laskowski [132] است.

 

تعریف تابع :

توضیح پارامترها :

  • points : ( پارامتر ورودی )؛ بردار حاوی نقاط 2-بعدی، با عمق CV_32S یا CV_32F، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
  • triangle : ( پارامتر خروجی )؛ بردار حاوی سه نقطه دو-بعدی که رئوس مثلث را مشخص میکند، عمق باید CV_32F باشد.
  • return : در بالا توضیح دادم.

 

کد نمونه :

نتیجه کد بالا :

تصویر خروجی تصویر ورودی
آموزش تابع minEnclosingTriangle در OpenCV آموزش تابع minEnclosingTriangle در OpenCV
24) تابع moments

24) تابع moments : گشتاورهای ( moment های ) یک شکل برداری یا یک شکل شطرنجی را تا مرتبه 3 محاسبه میکند؛ نتایج در ساختار ( کلاس ) cv::Moments برگردانده میشوند.

 

تعریف تابع :

توضیح پارامترها :

  • array : تصویر شطرنجی ( Raster image ) ( آرایه ای 2-بعدی، 1-کاناله، 8-بیتی یا اعشاری ) یا آرایه ای ( 1×N یا N×1 ) از نقاط دو بعدی ( cv::Point یا cv::Point2f ).
  • binaryImage : اگر درست باشد، تمام پیکسل های تصویر غیر صفر به عنوان 1 در نظر گرفته میشوند؛ این پارامتر فقط برای تصاویر استفاده میشود.
  • return : در بالا توضیح داده شده.

 

تکمیل شود : توضیح کامل کلاس cv::Moments

25) تابع pointPolygonTest

25) تابع pointPolygonTest : تست نقطه در کانتور ( point-in-contour ) را انجام میدهد؛ تابع تعیین میکند که آیا نقطه در داخل یک کانتور، خارج یا روی یک لبه قرار دارد ( یا منطبق با یک راس )؛ به ترتیب مقداری مثبت ( داخل )، منفی ( خارج )، یا صفر ( روی لبه ) را برمی گرداند؛ هنگامی که measureDist = false باشد، مقدار بازگشتی به ترتیب +1، -1 و 0 است؛ و اگر هنگامی که measureDist = true باشد، مقدار بازگشتی یک فاصله علامت گذاری شده بین نقطه و نزدیکترین لبه کانتور است.

 

تعریف تابع :

توضیح پارامترها :

  • contour : کانتور ورودی.
  • pt : نقطه ای که قرار است در برابر کانتور تست شود.
  • measureDist : این که تابع در شرایط مختلف، چه مقداری رو return کنه رو تعیین میکنه، در بالا دربارش صحبت کردیم.
  • return : در بالا دربارش صحبت کردیم.

 

کد نمونه : در کد زیر تمام پیکسل های تصویر ورودی رو در برابر کانتور آزمایش کردم! ( برای تمام پیکسل های تصویر ورودی، از تابع pointPolygonTest استفاده کردم!! )؛ پارامتر measureDist رو true کردم تا بجای +1 و -1، فاصله های + و - به من بده ( 0 هم که در هر دو حالت 0 هستش! )؛ تو این قسمت یه تصویر از فاصله ها ( با نام raw_dist ) ایجاد کردیم؛ بعد اومدم یه تصویر خروجی ( dst ) ایجاد کردم تا طبق فاصله هر پیکسل، یه رنگی بهش اطلاق کنم :

نتیجه کد بالا : بنظرتون چرا تو تصویر خروجی، اون قسمت خارج از فیل، اون دایره هه ایجاد شده؟ بله سرریز رخ داده، تصویر raw_dist حاوی فاصله ها هستش، که من اومدم تو یه فرمول ساده قرارش دادم، بدون این که مقیاس اعداد موجود تو این آرایه/تصویر رو تغییر بدم، مثلا فاصله ها بین مقادیر -1000 تا +1000 هستش که خب من از تابع cv::abs استفاده کردم ( همون قدر مطلق )، فلذا فاصله ها بین 0 تا 1000 هستند؛ تصویر خروجی من هم از نوع 8-بیتی هستش، یعنی مقادیرش بین 0 تا 255 هستش، خب حالا منی که مستقیما اومدم از فاصله های تصویر raw_dist استفاده کردم و تغییر مقیاس هم ندادم اعدادش رو، خب از یه جایی به بعد مقادیر فاصله از 255 بیشتر میشه و سرریز رخ میده که دقیقا همون موضوع رو تو تصویر میبینید، برا حل این مشکل میتونید مقادیر رو تغییر مقیاس بدید.

تصویر خروجی تصویر ورودی
آموزش تابع pointPolygonTest در OpenCV آموزش تابع pointPolygonTest در OpenCV

 

تغییر مقیاس : تو کد زیر محدوده 0-255 رو به 100-150 منتقل کردم ( تغییر مقیاس دادم، تغییر محدوده، اصطلاح درستش یادم نی ) :

 

توجه : همین کد رو در مسیر زیر میتونید پیدا کنید، بهتر از کد بالا و البته خروجی رنگی!؛ من سعی کردم این کد رو تا حد امکان ساده کنم.

[opencv]/samples/cpp/tutorial_code/ShapeDescriptors/pointPolygonTest_demo.cpp

26) تابع rotatedRectangleIntersection

26) تابع rotatedRectangleIntersection : بررسی میکند که آیا بین دو مستطیل-چرخیده-شده تلاقی/تقاطع ( intersection ) وجود دارد یا نه؛ اگر وجود داشته باشد، رئوس ناحیه متقاطع را به ما میدهد؛ در زیر چند نمونه از تقاطع ها نمایش داده شده است، الگوی هاشور خورده ( hatched pattern )، ناحیه متقاطع را نشان میدهد ( که وضعیتش، توسط تابع return میشه ) و رئوس قرمز ( توسط پارامتر intersectingRegion ) به ما داده میشود!

آموزش تابع rotatedRectangleIntersection در OpenCV

 

تعریف تابع :

توضیح پارامترها :

  • rect1 و rect2 : مستطیل های چرخشی ( کلاس RotatedRect ) داستان ما !!!
  • intersectingRegion : آرایه خروجی رئوس ناحیه متقاطع؛ حداکثر 8 راس برمی گرداند؛ ذخیره شده در std::vector<cv::Point2f> یا cv::Mat ( به عنوان Mx1 از نوع CV_32FC2 ).
  • return : تابع، یکی از مقادیر نوع شمارشی RectanglesIntersectTypes را ارسال میکند؛ که وضعیت اون قسمت هاشور خورده ( مشترک بین مستطیل های ورودی ) رو مشخص میکنه.

 

نوع شمارشی RectanglesIntersectTypes :

توضیح گزینه ها :

  • INTERSECT_NONE : بدون تقاطع.
  • INTERSECT_PARTIAL : یک تقاطع جزئی وجود دارد.
  • INTERSECT_FULL : یکی از مستطیل ها به طور کامل در دیگری محصور شده است.

 

کد نمونه :

نتیجه کد بالا :

آموزش تابع rotatedRectangleIntersection در OpenCV

 

 

منابع :

 

مطالب مرتبط : 

 

تا مطلب بعد اگه زنده بودیم و قسمت شد، یا علی.

 

تعداد مطالب : 367 تا
جنگ ما فتح قدس را به همراه خواهد داشت. [ امام خمینی (ره) ]
بقیه جلسات : آموزش OpenCV
ارسال دیدگاه
0