به نام خدا : این مطلب کلا مربوطه به کانتور ( contour )، از پیدا کردنش گرفته تا هر تابعی که باهاش مرتبط باشه؛ البته تابع رسم کانتور ( drawContours )، تو مطلب آموزش Drawing Functions در OpenCV قرار داره!.
1) چند ضلعی محدب ( Convex Polygon ) و چند ضلعی مقعر ( Concave Polygon ) : اگه چند ضلعی، زاویه داخلی بیشتر از 180 درجه نداشته باشه، محدب هستش؛ در غیر اینصورت، مقعر هستش.
2) نحوه محاسبه محیط ( perimeter ) و مساحت ( area ) اشکال هندسی : تکمیل شود؟؟؟؟؟؟؟؟؟؟؟?????????????
3) فرق تصاویر Raster با Vector : تکمیل شود؟؟؟؟؟؟؟؟؟؟؟?????????????
1) تابع approxPolyDP : یک منحنی / چند-ضلعی ( curve / polygonal ) را با منحنی / چند-ضلعی دیگری با رئوس کمتر تقریب میزند به طوری که فاصله بین آنها کمتر یا برابر با دقت مشخص شده باشد؛ این تابع از الگوریتم Douglas-Peucker استفاده میکند.
تعریف تابع :
1 2 3 4 5 |
CV_EXPORTS_W void approxPolyDP( InputArray curve, OutputArray approxCurve, double epsilon, bool closed ); |
توضیح پارامترها :
- curve : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector یا Mat.
- approxCurve : نتیجه تقریب؛ با نوعی برابر با پارامتر curve.
- epsilon : دقت تقریبی ( approximation accuracy ) را مشخص میکند؛ این پارامتر، حداکثر فاصله بین منحنی اصلی و تقریب آن است.
- closed : اگر true باشد، منحنی تقریبی بسته است ( راس اول و آخر آن به هم متصل است )، در غیر این صورت بسته نیست.
کد نمونه : در کد زیر، هر contour به همراه تعداد نقاطش با رنگ قرمز نمایش داده شده و مقدار تقریب زده شده و تعداد نقاطش با رنگ سبز!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void structural_analysis_and_shape_descriptors::approxPolyDP_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/src.png"); cv::Mat gray, thresh; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::threshold(gray, thresh, 127, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(thresh, contours, hierarchy, cv::RetrievalModes::RETR_CCOMP, cv::ContourApproximationModes::CHAIN_APPROX_NONE); std::cout << "contours = " << contours.size() << std::endl; for(int i=0; i<contours.size(); i++) { // filter small / big contours or noise cv::Rect rect = cv::boundingRect(contours[i]); if(rect.width < 10 || rect.height < 10 || rect.width > 400 || rect.height > 400) continue; double epsilon = 0.1 * cv::arcLength(contours[i], true); std::vector<cv::Point> approx; cv::approxPolyDP(contours[i], approx, epsilon, true); // center of contour double cx = rect.x + 0.5 * rect.width; double cy = rect.y + 0.5 * rect.height; // draw1 cv::polylines(src, contours[i], true, cv::Scalar(0,0,255), 10); cv::putText(src, std::to_string(contours[i].size()), cv::Point(cx-10, cy-10), cv::HersheyFonts::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0,0,255), 2); // draw2 cv::polylines(src, approx, true, cv::Scalar(0,255,0), 2); cv::putText(src, std::to_string(approx.size()), cv::Point(cx, cy+30), cv::HersheyFonts::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0,255,0), 2); } cv::imshow("dst", src); } |
نتیجه کد بالا :
مثالی دیگر در مسیر زیر :
[opencv]/samples/cpp/contours2.cpp
2) تابع arcLength : طول منحنی ( curve length ) یا محیط کانتور بسته ( closed contour perimeter ) را محاسبه میکند.
تعریف تابع :
1 |
CV_EXPORTS_W double arcLength( InputArray curve, bool closed ); |
توضیح پارامترها :
- curve : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector یا Mat.
- closed : این پرچم تعیین میکند که منحنی بسته است یا خیر.
کد نمونه : به تابع approxPolyDP مراجعه کنید.
3) تابع boundingRect : حداقل مستطیل مرزی ( به سمت بالا راست ) مجموعه ای از نقاط یا پیکسل های غیر صفر تصویری در مقیاس خاکستری را محاسبه میکند.
تعریف تابع :
1 |
CV_EXPORTS_W Rect boundingRect( InputArray array ); |
توضیح پارامترها :
- array : تصویری در مقیاس خاکستری ( نوع cv::Mat ) یا مجموعه ای از نقطه 2-بعدی ( std::vector ).
کد نمونه : همون کد نمونه تابع approxPolyDP هستش فقط یکم ویرایشش کردم!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void structural_analysis_and_shape_descriptors::boundingRect_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/src.png"); cv::Mat gray, thresh; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::threshold(gray, thresh, 127, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(thresh, contours, hierarchy, cv::RetrievalModes::RETR_CCOMP, cv::ContourApproximationModes::CHAIN_APPROX_NONE); for(int i=0; i<contours.size(); i++) { cv::Rect rect = cv::boundingRect(contours[i]); // filter small / big contours if(rect.width < 10 || rect.height < 10 || rect.width > 400 || rect.height > 400) continue; // draw rect cv::rectangle(src,rect,cv::Scalar(255,0,0),5); } cv::imshow("dst", src); } |
نتیجه کد بالا :
توجه : شاید با این مثال درست توجه این تابع نشید، هم برای این که کامل متوجه عملکرد این تابع بشید و هم فرق توابع minAreaRect و boundingRect رو متوجه بشید، به کد نمونه موجود در تابع minAreaRect مراجعه کنید. ( همین مطلب! )
4) تابع boxPoints : چهار راس یک مستطیل چرخیده شده را پیدا میکند؛ این تابع برای رسم مستطیل چرخیده شده ( کلاس RotatedRect ) مفید است؛ در C++ به جای استفاده از این تابع، می توانید مستقیماً از متد RotatedRect::points استفاده کنید!
تعریف تابع :
1 |
CV_EXPORTS_W void boxPoints(RotatedRect box, OutputArray points); |
توضیح پارامترها :
- box : ورودی مستطیل چرخیده شده.
- points : آرایه خروجی چهار راس مستطیل.
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
void structural_analysis_and_shape_descriptors::boxPoints_test1() { cv::Mat src(cv::Size(900, 500), CV_8UC3, cv::Scalar(80, 62, 44)); cv::Point2f rCenter(src.cols/2, src.rows/2); cv::Size2f rSize(src.cols/3, src.rows/3); cv::RotatedRect rRect = cv::RotatedRect(rCenter, rSize, 30); // Draw Rect cv::Rect rect = rRect.boundingRect(); cv::rectangle(src, rect, cv::Scalar(255,0,0), 10); // blue // Draw RotatedRect, Method-1 cv::Point2f points1[4]; rRect.points(points1); for (int i = 0; i < 4; i++) cv::line(src, points1[i], points1[(i+1)%4], cv::Scalar(0,255,0), 10); // green // Draw RotatedRect, Method-2 cv::Mat points2; cv::boxPoints(rRect, points2); for (int i = 0; i < 4; i++) cv::line(src, cv::Point(points2.at<float>(i,0), points2.at<float>(i,1)), cv::Point(points2.at<float>((i+1)%4,0), points2.at<float>((i+1)%4,1)), cv::Scalar(0,0,255), // red 2); cv::imshow("dst", src); } |
نتیجه کد بالا :
5) تابع connectedComponents : اجزای متصل را با تصویر-برچسب-گذاری-شده ( labeled image ) در یک تصویر-باینری ( boolean image ) محاسبه میکند.
اگر حداقل یک چارچوب موازی مجاز فعال باشد و اگر سطرهای تصویر حداقل دو برابر تعداد بازگردانده شده توسط getNumberOfCPU ها باشد، این تابع از نسخه موازی الگوریتم ها استفاده میکند.
تعریف توابع :
1 2 3 4 5 6 7 8 9 10 11 12 |
CV_EXPORTS_AS(connectedComponentsWithAlgorithm) int connectedComponents( InputArray image, OutputArray labels, int connectivity, int ltype, int ccltype ); CV_EXPORTS_W int connectedComponents( InputArray image, OutputArray labels, int connectivity = 8, int ltype = CV_32S ); |
توضیح پارامترها :
- 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 این کار را نمیکنند.
1 2 3 4 5 6 7 8 9 10 |
enum ConnectedComponentsAlgorithmsTypes { CCL_DEFAULT = -1, CCL_WU = 0, CCL_GRANA = 1, CCL_BOLELLI = 2, CCL_SAUF = 3, CCL_BBDT = 4, CCL_SPAGHETTI = 5, }; |
توضیح گزینه ها :
- 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 ) استفاده شود.
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
void structural_analysis_and_shape_descriptors::on_horizontalSlider_connectedComponents_threshold_valueChanged(int value) { connectedComponents_test1(); } void structural_analysis_and_shape_descriptors::on_comboBox_connectedComponents_connectivity_currentIndexChanged(int index) { connectedComponents_test1(); } void structural_analysis_and_shape_descriptors::on_comboBox_connectedComponents_algorithm_currentIndexChanged(int index) { connectedComponents_test1(); } //--- void structural_analysis_and_shape_descriptors::connectedComponents_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/src.jpg", cv::IMREAD_GRAYSCALE); cv::imshow( "1) src", src ); // threshold cv::Mat thresh; int thresh_value = ui->horizontalSlider_connectedComponents_threshold->value(); cv::threshold(src, thresh, thresh_value, 255, cv::THRESH_BINARY_INV); cv::imshow( "2) threshold", thresh ); // connectedComponents cv::Mat labels; int connectivity = ui->comboBox_connectedComponents_connectivity->currentIndex() == 0 ? 4 : 8; int ltype = CV_32S; // CV_16U or CV_32S int ccltype = ui->comboBox_connectedComponents_algorithm->currentIndex() + 3; // cv::ConnectedComponentsAlgorithmsTypes int nLabels = cv::connectedComponents(thresh, labels, connectivity, ltype, ccltype); ui->label_connectedComponents_nLabels->setText(QString::number(nLabels)); // Define multiple random colors std::vector<cv::Vec3b> colors(nLabels); cv::randu(colors, cv::Scalar(50, 50, 50), cv::Scalar(255, 255, 255)); colors[0] = cv::Vec3b(0, 0, 0); // background // Change the color of areas with unique labels cv::Mat dst(src.size(), CV_8UC3); for(int r = 0; r < dst.rows; ++r) { for(int c = 0; c < dst.cols; ++c) { int label = labels.at<int>(r, c); dst.at<cv::Vec3b>(r, c) = colors[label]; } } cv::imshow("3) Connected-Components", dst); } |
نتیجه کد بالا : برای کد بالا، یه تصویر ساده استفاده کردم، میتونید تصاویر دیگه رو هم تست کنید، اما با تغییر الگوریتم من تغییری در خروجی یا تعداد برچسب ها ندیدم!؟!
6) تابع connectedComponentsWithStats : مثل تابع connectedComponents هستش، با این تفاوت که : یک خروجی آماری برای هر برچسب تولید میکند.
اگر حداقل یک چارچوب موازی مجاز فعال باشد و اگر ردیفهای تصویر حداقل دو برابر تعداد بازگشت شده توسط getNumberOfCPU ها باشد، این تابع از نسخه موازی الگوریتمها ( شامل آمار ) استفاده میکند.
تعریف تابع :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
CV_EXPORTS_AS(connectedComponentsWithStatsWithAlgorithm) int connectedComponentsWithStats( InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity, int ltype, int ccltype ); CV_EXPORTS_W int connectedComponentsWithStats( InputArray image, OutputArray labels, OutputArray stats, OutputArray centroids, int connectivity = 8, int ltype = CV_32S ); |
توضیح پارامترها :
- stats : آمار برای هر برچسب، از جمله برچسب پس زمینه ( background )؛ نوع داده CV_32S میباشد؛ این آمار شامل داده هایی نظیر : مختصات نقطه بالا-چپ مستطیل، طول و عرض مستطیل و مساحت مستطیل ( هر برچسب داخل یه مستطیلی قرار داره! )؛ جهت دسترسی داده های این پارامتر، از نوع شمارشی ConnectedComponentsTypes استفاده میکنیم ( کمک میگیریم!، جهت خوانایی بیشتر کد! )؛ برای دیدن نحوه استفاده از پارامترهای stats و centroids به کد نمونه مراجعه کنید!
- centroids : نقطه مرکزی ( centroids ) برای هر برچسب، از جمله برچسب پس زمینه ( background )؛ نوع داده CV_64F.
نوع شمارشی ConnectedComponentsTypes : آمار اجزای متصل.
1 2 3 4 5 6 7 8 |
enum ConnectedComponentsTypes { CC_STAT_LEFT = 0, CC_STAT_TOP = 1, CC_STAT_WIDTH = 2, CC_STAT_HEIGHT = 3, CC_STAT_AREA = 4, }; |
توضیح گزینه ها :
- 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 : مساحت کل ( بر حسب پیکسل ) جزء متصل.
کد نمونه : -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
void structural_analysis_and_shape_descriptors::on_horizontalSlider_connectedComponentsWithStats_threshold_valueChanged(int value) { connectedComponentsWithStats_test1(); } void structural_analysis_and_shape_descriptors::on_comboBox_connectedComponentsWithStats_connectivity_currentIndexChanged(int index) { connectedComponentsWithStats_test1(); } void structural_analysis_and_shape_descriptors::on_comboBox_connectedComponentsWithStats_algorithm_currentIndexChanged(int index) { connectedComponentsWithStats_test1(); } void structural_analysis_and_shape_descriptors::on_checkBox_connectedComponentsWithStats_Fill_stateChanged(int arg1) { connectedComponentsWithStats_test1(); } void structural_analysis_and_shape_descriptors::on_checkBox_connectedComponentsWithStats_Rect_stateChanged(int arg1) { connectedComponentsWithStats_test1(); } //--- void structural_analysis_and_shape_descriptors::connectedComponentsWithStats_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/DMF313_IR.png"); cv::imshow( "1) src", src ); // threshold cv::Mat thresh, gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); int thresh_value = ui->horizontalSlider_connectedComponentsWithStats_threshold->value(); //--- cv::threshold(gray, thresh, thresh_value, 255, cv::THRESH_BINARY_INV); //--- cv::imshow( "2) threshold", thresh ); // connectedComponents cv::Mat labels, stats, centroids; int connectivity = ui->comboBox_connectedComponentsWithStats_connectivity->currentIndex() == 0 ? 4 : 8; int ltype = CV_32S; // CV_16U or CV_32S int ccltype = ui->comboBox_connectedComponentsWithStats_algorithm->currentIndex() + 3; // cv::ConnectedComponentsAlgorithmsTypes //--- int nLabels = cv::connectedComponentsWithStats(thresh, labels, stats, centroids, connectivity, ltype, ccltype); //--- ui->label_connectedComponentsWithStats_nLabels->setText(QString::number(nLabels)); // Define multiple random colors std::vector<cv::Scalar> colors(nLabels); cv::randu(colors, cv::Scalar(50, 50, 50), cv::Scalar(255, 255, 255)); colors[0] = cv::Scalar(0, 0, 0); // background cv::Mat dst = src.clone(); //--- if(ui->checkBox_connectedComponentsWithStats_Fill->isChecked()) { for(int r = 0; r < dst.rows; ++r) { for(int c = 0; c < dst.cols; ++c) { int label = labels.at<int>(r, c); dst.at<cv::Vec3b>(r, c) = cv::Vec3b(colors[label][0], colors[label][1], colors[label][2]); } } } //--- if(ui->checkBox_connectedComponentsWithStats_Rect->isChecked()) { for (int i = 0; i < nLabels; i++) { if (stats.at<int>(i, cv::CC_STAT_AREA) > 100 && stats.at<int>(i, cv::CC_STAT_AREA) < 10000) { int left = stats.at<int>(i, cv::CC_STAT_HEIGHT) / 4; cv::Rect rect = cv::Rect( stats.at<int>(i, cv::CC_STAT_LEFT) - left, stats.at<int>(i, cv::CC_STAT_TOP) - left, stats.at<int>(i, cv::CC_STAT_WIDTH) + 2 * left, stats.at<int>(i, cv::CC_STAT_HEIGHT) + 2 * left); cv::rectangle(dst, rect, colors[i], 2); } } } //--- cv::imshow("3) Connected-Components-With-Stats", dst); } |
نتیجه کد بالا :
7) تابع contourArea : مساحت کانتور ( contour area ) را محاسبه میکند.
تعریف تابع :
1 |
CV_EXPORTS_W double contourArea( InputArray contour, bool oriented = false ); |
توضیح پارامترها :
- contour : بردار ورودی حاوی نقاط دو بعدی ( رئوس خطوط )، ذخیره شده در std::vector یا cv::Mat.
- oriented : عملکرد این پارامتر رو متوجه نشدم، جهت تو محاسبه مساحت دخلی نداره آخه!؟
کد نمونه : تو کد زیر یه contour ایجاد کردیم و بعد مساحت شو محاسبه کردیم و بعد تقریب contour فوق رو محاسبه کردیم و بعد مساحت جدید رو دوباره محاسبه کردیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void structural_analysis_and_shape_descriptors::contourArea_test1() { cv::Mat img(cv::Size(900, 500), CV_8UC3, cv::Scalar(80, 62, 44)); std::vector<cv::Point> contour = { cv::Point2f(450, 100), cv::Point2f(675, 400), cv::Point2f(450, 350), cv::Point2f(225, 400) }; cv::polylines(img, contour, true, cv::Scalar(0,0,255), 10); double area0 = cv::contourArea(contour); std::vector<cv::Point> approx; cv::approxPolyDP(contour, approx, 50, true); cv::polylines(img, approx, true, cv::Scalar(0,255,0), 2); double area1 = cv::contourArea(approx); std::cout << "area0 = " << area0 << ", area1 = " << area1 << std::endl; cv::imshow("img", img); } |
نتیجه کد بالا : area0 = 56250, area1 = 67500
8) تابع convexHull : بدنه محدب ( convex hull ) مجموعه نقاط 2-بعدی را با استفاده از الگوریتم اسکلانسکی [228] ( که دارای پیچیدگی O(N logN) در اجرای فعلی است )، پیدا میکند.
تعریف تابع :
1 2 3 4 5 |
CV_EXPORTS_W void convexHull( InputArray points, OutputArray hull, bool clockwise = false, bool returnPoints = true ); |
توضیح پارامترها :
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void structural_analysis_and_shape_descriptors::convexHull_test1() { cv::Mat img(500, 900, CV_8UC3, cv::Scalar::all(0)); // convexHull cv::RNG& rng = cv::theRNG(); int count = 10; std::vector<cv::Point> points; for( int i = 0; i < count; i++ ) { cv::Point pt; pt.x = rng.uniform(img.cols/5, img.cols*4/5); pt.y = rng.uniform(img.rows/5, img.rows*4/5); points.push_back(pt); } //--- std::vector<cv::Point> hull; //--- cv::convexHull(points, hull, true); // draw for( int i = 0; i < count; i++ ) cv:: circle(img, points[i], 3, cv::Scalar(0, 0, 255), cv::FILLED, cv::LINE_AA); cv::polylines(img, hull, true, cv::Scalar(0, 255, 0), 1, cv::LINE_AA); // show cv::imshow("hull", img); } |
کد نمونه 2 : وقتی که : returnPoints = false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void structural_analysis_and_shape_descriptors::convexHull_test2() { cv::Mat img(500, 900, CV_8UC3, cv::Scalar::all(0)); // convexHull cv::RNG& rng = cv::theRNG(); int count = 10; std::vector<cv::Point> points; for( int i = 0; i < count; i++ ) { cv::Point pt; pt.x = rng.uniform(img.cols/5, img.cols*4/5); pt.y = rng.uniform(img.rows/5, img.rows*4/5); points.push_back(pt); } //--- std::vector<int> hull; //--- cv::convexHull(points, hull, false); // draw for( int i = 0; i < count; i++ ) cv:: circle(img, points[i], 3, cv::Scalar(0, 0, 255), cv::FILLED, cv::LINE_AA); for( int i = 0; i < hull.size(); i++ ) cv::line(img, points[hull[i]], points[hull[(i+1)%hull.size()]], cv::Scalar(0, 255, 0), 1, cv::LINE_AA); // show cv::imshow("hull", img); } |
نتیجه کدهای بالا : نتیجه هر دو کد یکسان هستش.
9) تابع convexityDefects : عیوب تحدب یک کانتور را پیدا میکند؛ شکل زیر عیوب تحدب کانتور دست را نشان میدهد :
تعریف تابع :
1 2 3 4 |
CV_EXPORTS_W void convexityDefects( InputArray contour, InputArray convexhull, OutputArray convexityDefects ); |
توضیح پارامترها :
- contour : کانتور ورودی.
- convexhull : بدنه محدب، به دست آمده با استفاده از تابع convexHull، که باید شامل شاخص هایی ( indices ) از نقاط کانتوری باشد که بدنه را میسازند ( پارامتر returnPoints از تابع convexHull، باید false باشه ).
- convexityDefects : بردار خروجی عیوب تحدب ( convexity defects )؛ هر نقص تحدب به عنوان بردار عدد صحیح 4 عنصری ( معروف به Vec4i ) نشان داده میشود : ( start_index، end_index، farthest_pt_index، fixpt_depth ).
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
void structural_analysis_and_shape_descriptors::convexityDefects_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/GrayHand.png", cv::IMREAD_GRAYSCALE); cv::Mat dst = cv::Mat::zeros(src.rows, src.cols, CV_8UC3); cv::threshold(src, src, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(src, contours, hierarchy, cv::RETR_CCOMP, cv::CHAIN_APPROX_SIMPLE); cv::Mat hull; cv::Mat defect; std::vector<cv::Point> contour = contours[0]; cv::convexHull(contour, hull, false, false); cv::convexityDefects(contour, hull, defect); for(int r = 0; r < defect.rows; ++r) { // start_index، end_index، farthest_pt_index، fixpt_depth cv::Point start = contour[ defect.at<cv::Vec4i>(r,0)[0] ]; cv::Point end = contour[ defect.at<cv::Vec4i>(r,1)[1] ]; cv::Point far = contour[ defect.at<cv::Vec4i>(r,2)[2] ]; cv::line(dst, start, end, cv::Scalar(0, 0, 255), 2, cv::LINE_AA); cv::circle(dst, far, 3, cv::Scalar(255, 255, 255), -1); } cv::imshow("src", src); cv::imshow("dst", dst); } |
نتیجه کد بالا :
10) تابع createGeneralizedHoughBallard : تابع createGeneralizedHoughBallard، یه شیء از کلاس cv::GeneralizedHoughBallard ایجاد میکنه و مقداردهی اولیه اش میکنه؛ این کلاس : الگوی دلخواه را در تصویر خاکستری پیدا میکند؛ این کلاس : فقط موقعیت ( position ) را تشخیص میدهد ( ترجمه ( translation ) و چرخش ( rotation ) را تشخیص نمیدهد ) [15].
11) تابع createGeneralizedHoughGuil : تابع createGeneralizedHoughGuil هم یه شیء از کلاس cv::GeneralizedHoughGuil ایجاد میکنه و مقداردهی اولیه اش میکنه؛ این کلاس : الگوی دلخواه را در تصویر خاکستری پیدا میکند؛ این کلاس : موقعیت ( position )، ترجمه ( translation ) و چرخش ( rotation ) را تشخیص میدهد [102].
توجه : هر دو کلاس cv::GeneralizedHoughBallard و cv::GeneralizedHoughGuil، از کلاس cv::GeneralizedHough مشتق شدن،
تعریف توابع :
1 2 |
CV_EXPORTS_W Ptr<GeneralizedHoughBallard> createGeneralizedHoughBallard(); CV_EXPORTS_W Ptr<GeneralizedHoughGuil> createGeneralizedHoughGuil(); |
کد نمونه : منبع این کد ( که البته ویرایشش کردم یکم ) این لینک هستش : opencv-GeneralizedHough-example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
void structural_analysis_and_shape_descriptors::createGeneralizedHough_Ballard_Guil_test1() { // load source images cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/mini_image.jpg"); cv::Mat tmp = cv::imread("C:/Users/Mahdi/Desktop/mini_template.jpg"); cv::Mat dst = src.clone(); // create grayscale image and template cv::Mat src_gray, tmp_gray; cv::cvtColor(tmp, tmp_gray, cv::COLOR_BGR2GRAY); cv::cvtColor(src, src_gray, cv::COLOR_BGR2GRAY); // create variable for location, scale and rotation of detected templates std::vector<cv::Vec4f> position_ballard, position_guil; // template width and height int w = tmp_gray.cols; int h = tmp_gray.rows; // create ballard and set options cv::Ptr<cv::GeneralizedHoughBallard> ballard = cv::createGeneralizedHoughBallard(); ballard->setMinDist(10); ballard->setLevels(360); ballard->setDp(2); ballard->setMaxBufferSize(1000); ballard->setVotesThreshold(40); ballard->setCannyLowThresh(30); ballard->setCannyHighThresh(110); ballard->setTemplate(tmp_gray); // execute ballard detection ballard->detect(src_gray, position_ballard); // create guil and set options cv::Ptr<cv::GeneralizedHoughGuil> guil = cv::createGeneralizedHoughGuil(); guil->setMinDist(10); guil->setLevels(360); guil->setDp(3); guil->setMaxBufferSize(1000); guil->setMinAngle(0); guil->setMaxAngle(360); guil->setAngleStep(1); guil->setAngleThresh(1500); guil->setMinScale(0.5); guil->setMaxScale(2.0); guil->setScaleStep(0.05); guil->setScaleThresh(50); guil->setPosThresh(10); guil->setCannyLowThresh(30); guil->setCannyHighThresh(110); guil->setTemplate(tmp_gray); // execute guil detection guil->detect(src_gray, position_guil); // draw ballard for(int i = 0; i < position_ballard.size(); ++i) { // create rRect cv::RotatedRect rRect = cv::RotatedRect( cv::Point2f(position_ballard[i][0], position_ballard[i][1]), cv::Size2f(tmp.cols * position_ballard[i][2], tmp.rows * position_ballard[i][2]), position_ballard[i][3]); // draw rRect cv::Point2f rrPoints[4]; rRect.points(rrPoints); for (int i = 0; i < 4; i++) cv::line(dst, rrPoints[i], rrPoints[(i+1)%4], cv::Scalar(255,0,0), 3, cv::LINE_AA); } // draw guil for(int i = 0; i < position_guil.size(); ++i) { // create rRect cv::RotatedRect rRect = cv::RotatedRect( cv::Point2f(position_guil[i][0], position_guil[i][1]), cv::Size2f(tmp.cols * position_guil[i][2], tmp.rows * position_guil[i][2]), position_guil[i][3]); // draw rRect cv::Point2f rrPoints[4]; rRect.points(rrPoints); for (int i = 0; i < 4; i++) cv::line(dst, rrPoints[i], rrPoints[(i+1)%4], cv::Scalar(0,255,0), 3, cv::LINE_AA); } // show images cv::imshow("src", src); cv::imshow("tmp", tmp); cv::imshow("dst", dst); } |
نتیجه کد بالا :
تکمیل شود : توضیح کامل کلاس های فوق.
12) تابع findContours : با استفاده از الگوریتم [237]، کانتورها را در یک تصویر باینری پیدا میکند؛ کانتورها یک ابزار مفید برای تجزیه و تحلیل شکل ( shape analysis ) و تشخیص اشیا ( object detection ) و recognition هستند. ( اگه درباره فرق object detection و recognition میخواید مطالعه کنید، میتونید به این مطلب مراجعه کنید : Object Detection vs Object Recognition vs Image Segmentation )
تعاریف تابع :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
CV_EXPORTS_W void findContours( InputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point() ); CV_EXPORTS void findContours( InputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point() ); |
توضیح پارامترها :
- 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، از کد زیر میتویند استفاده کنید :
1 |
binary.convertTo(binary,CV_32SC1); |
نوع شمارشی RetrievalModes :
1 2 3 4 5 6 7 8 |
enum RetrievalModes { RETR_EXTERNAL = 0, RETR_LIST = 1, RETR_CCOMP = 2, RETR_TREE = 3, RETR_FLOODFILL = 4 }; |
توضیح گزینه ها :
- 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 :
1 2 3 4 5 6 7 |
enum ContourApproximationModes { CHAIN_APPROX_NONE = 1, CHAIN_APPROX_SIMPLE = 2, CHAIN_APPROX_TC89_L1 = 3, CHAIN_APPROX_TC89_KCOS = 4 }; |
توضیح گزینه ها :
- 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 ) نامیده میشود.
تصویر زیر را در نظر بگیرید :
- در این تصویر چند شکل وجود دارد که از 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void structural_analysis_and_shape_descriptors::findContours_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/1.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); std::cout << contours.size() << std::endl; std::cout << contours[0].size() << std::endl; cv::Mat dst = src.clone(); cv::drawContours(dst, contours, -1, cv::Scalar(0, 255, 0), 3, cv::LINE_AA); cv::imshow("dst", dst); } |
نتیجه کد بالا برای تصویر ورودی 1.png :
نتیجه کد بالا برای تصویر ورودی 2.png :
کد نمونه 2 : حالا بریم سراغ تغییر پارامتر mode، و ببینیم تغییرش چه تاثیری در خروجی میزاره؛ با تغییر این پارامتر، تعداد کانتورها ممکنه تغییر کنه، ترتیبشون ممکنه تغییر کنه، اما تعداد نقاط هر کانتور تغییر نمیکنه.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
void structural_analysis_and_shape_descriptors::findContours_test2() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/3.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; //binary.convertTo(binary,CV_32SC1); // only for cv::RetrievalModes::RETR_FLOODFILL cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); std::cout << "hierarchy size = " << hierarchy.size() << std::endl; std::cout << "contours size = " << contours.size() << std::endl; // create some random colors std::vector<cv::Scalar> colors(contours.size()); cv::randu(colors, cv::Scalar(50, 50, 50), cv::Scalar(255, 255, 255)); cv::Mat dst = src.clone(); cv::drawContours(dst, contours, -1, cv::Scalar(0,255,0), 3, cv::LINE_AA); for(int i=0; i<contours.size(); ++i) { std::cout << "contour" << i << ", Size = " << contours[i].size() << std::endl; cv::Rect rect = cv::boundingRect(contours[i]); cv::Size textSize = cv::getTextSize(std::to_string(i), cv::HersheyFonts::FONT_HERSHEY_SIMPLEX, 0.7, 1, 0); cv::Point center(rect.x + rect.width / 2 - textSize.width / 2, rect.y + rect.height / 2 + textSize.height / 2); cv::putText(dst, std::to_string(i), center, cv::HersheyFonts::FONT_HERSHEY_SIMPLEX, 0.7, cv::Scalar(0,0,255), 1); } cv::imshow("dst", dst); } |
نتیجه کد بالا :
13 و 14 و 15) توابع fitEllipse و fitEllipseAMS و fitEllipseDirect : یک بیضی در اطراف مجموعه ای از نقاط 2-بعدی محاسبه میکند؛ این تابع، مستطیل چرخشی ( کلاس RotatedRect ) را که بیضی در آن حک شده است برمی گرداند؛ برا دیدن تفاوت های این 3تا تابع، به سایت OpenCV مراجعه کنید، توضحش میره تو فرمول های ریاضی که خارج از حوصله من، شما و این مطلبه!
- تابع fitEllipse : از اولین الگوریتم توصیف شده توسط [80] استفاده شده است.
- تابع fitEllipseAMS : از میانگین مربع تقریبی ( AMS ) پیشنهاد شده توسط [244] استفاده شده است.
- تابع fitEllipseDirect : از روش حداقل مربع مستقیم ( Direct least square ) توسط [81] استفاده شده است.
توجه : برنامهنویس باید در نظر داشته باشد که ممکن است دادههای بیضی ( rotatedRect ) برگشتی دارای شاخصهای منفی باشند، زیرا نقاط داده به مرز عنصر Mat نزدیک هستند.
تعریف توابع :
1 2 3 |
CV_EXPORTS_W RotatedRect fitEllipse( InputArray points ); CV_EXPORTS_W RotatedRect fitEllipseAMS( InputArray points ); CV_EXPORTS_W RotatedRect fitEllipseDirect( InputArray points ); |
توضیح پارامترها :
- points : مجموعه نقطه 2-بعدی ورودی، ذخیره شده در std::vector<cv::Point> یا cv::Mat؛ باید حداقل حاوی 5 نقطه باشد!
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void structural_analysis_and_shape_descriptors::fitEllipse_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/6.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_NONE); //std::cout << "contours count = " << contours.size() << std::endl; //std::cout << "contour[0] size = " << contours[0].size() << std::endl; cv::drawContours(src, contours, -1, cv::Scalar(0, 0, 255), 3, cv::LINE_AA); cv::RotatedRect rRect = cv::fitEllipse(contours[0]); //std::cout << "rRect.angle = " << rRect.angle << std::endl; //std::cout << "rRect.center = " << rRect.center << std::endl; //std::cout << "rRect.size = " << rRect.size << std::endl; cv::ellipse(src, rRect, cv::Scalar(0,255,0), 3, cv::LINE_AA); cv::imshow("dst", src); } |
نتیجه کد بالا :
توجه : یک کد نمونه برای این 3 تا تابع در مسیر زیر قرار داره :
[opencv]/samples/cpp/fitellipse.cpp
16) تابع fitLine : یک خط را به مجموعه نقاط 2-بعدی یا 3-بعدی منطبق میکند.
تعریف تابع :
1 2 3 4 5 6 7 |
CV_EXPORTS_W void fitLine( InputArray points, OutputArray line, int distType, double param, double reps, double aeps ); |
توضیح پارامترها :
- 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 :
1 2 3 4 5 6 7 8 9 10 11 |
enum DistanceTypes { DIST_USER = -1, //!< User defined distance DIST_L1 = 1, //!< distance = |x1-x2| + |y1-y2| DIST_L2 = 2, //!< the simple euclidean distance DIST_C = 3, //!< distance = max(|x1-x2|,|y1-y2|) DIST_L12 = 4, //!< L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1)) DIST_FAIR = 5, //!< distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998 DIST_WELSCH = 6, //!< distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846 DIST_HUBER = 7 //!< distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345 }; |
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
void structural_analysis_and_shape_descriptors::fitLine_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/2.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); cv::drawContours(src, contours, -1, cv::Scalar(0,0,255), 3, cv::LINE_AA); cv::Vec4d line; //--- cv::fitLine(contours[0], line, cv::DIST_L2, 0, 0.01, 0.01); //--- double vx = line[0], vy = line[1], x = line[2], y = line[3]; double lefty = std::round((-x * vy / vx) + y); double righty = std::round(((src.cols - x) * vy / vx) + y); cv::Point point1(src.cols - 1, righty); cv::Point point2(0, lefty); cv::line(src, point1, point2, cv::Scalar(0,255,0), 3, cv::LINE_AA, 0); cv::imshow("dst", src); } |
نتیجه کد بالا :
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 محاسبه شده برای تصاویر اصلی و تبدیل شده کمی متفاوت است.
تعاریف تابع :
1 2 |
CV_EXPORTS void HuMoments( const Moments& moments, double hu[7] ); CV_EXPORTS_W void HuMoments( const Moments& m, OutputArray hu ); |
توضیح پارامترها :
- moments و m : لحظات ( moments ) ورودی!؛ که توسط تابع cv::moments محاسبه میشود.
- hu : متغیرهای خروجی hu.
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void structural_analysis_and_shape_descriptors::HuMoments_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/1.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); cv::drawContours(src, contours, -1, cv::Scalar(0,0,255), 3, cv::LINE_AA); std::vector<double> hu; cv::Moments mom = cv::moments(contours[0]); cv::HuMoments(mom, hu); std::cout << "hu.size() = " << hu.size() << std::endl; for(int i = 0; i < hu.size(); ++i) { std::cout << "hu[" << i << "] = " << hu[i] << std::endl; } cv::imshow("dst", src); } |
نتیجه کد بالا :
تصویر ورودی |
داده خروجی |
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 : تقاطع دو چند ضلعی محدب ( convex polygons ) را پیدا میکند.
توجه : این تابع، محدب بودن هر دو چند ضلعی را تأیید نمیکند!، فلذا اگر محدب نباشند، نتایج نامعتبر را برمی گرداند.
تعریف تابع :
1 2 3 4 5 |
CV_EXPORTS_W float intersectConvexConvex( InputArray p1, InputArray p2, OutputArray p12, bool handleNested = true ); |
توضیح پارامترها :
- p1 و p2 : چند ضلعی اول و دوم.
- p12 : چند ضلعی خروجی که مساحت متقاطع ( intersecting area ) را توصیف میکند.
- handleNested : وقتی true است، اگر یکی از چند ضلعی ها به طور کامل در دیگری محصور باشد، یک تقاطع پیدا میشود؛ وقتی false است، هیچ تقاطعی پیدا نمی شود؛ اگر چند ضلعی ها یک ضلع مشترک داشته باشند یا راس یک چند ضلعی در لبه دیگری قرار گیرد، آنها تودرتو در نظر گرفته نمیشوند و بدون توجه به مقدار handleNested یک تقاطع پیدا میشود.
- return : مقدار مطلق مساحت چندضلعی متقاطع.
کد نمونه : این کد در مسیر زیر قرار داره :
[opencv]/samples/cpp/intersectExample.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
/* * Author: Steve Nicholson * * A program that illustrates intersectConvexConvex in various scenarios */ #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" using namespace cv; using namespace std; // Create a vector of points describing a rectangle with the given corners static vector<Point> makeRectangle(Point topLeft, Point bottomRight) { vector<Point> rectangle; rectangle.push_back(topLeft); rectangle.push_back(Point(bottomRight.x, topLeft.y)); rectangle.push_back(bottomRight); rectangle.push_back(Point(topLeft.x, bottomRight.y)); return rectangle; } static vector<Point> makeTriangle(Point point1, Point point2, Point point3) { vector<Point> triangle; triangle.push_back(point1); triangle.push_back(point2); triangle.push_back(point3); return triangle; } // Run intersectConvexConvex on two polygons then draw the polygons and their intersection (if there is one) // Return the area of the intersection static float drawIntersection(Mat &image, vector<Point> polygon1, vector<Point> polygon2, bool handleNested = true) { vector<Point> intersectionPolygon; vector<vector<Point> > polygons; polygons.push_back(polygon1); polygons.push_back(polygon2); float intersectArea = intersectConvexConvex(polygon1, polygon2, intersectionPolygon, handleNested); if (intersectArea > 0) { Scalar fillColor(200, 200, 200); // If the input is invalid, draw the intersection in red if (!isContourConvex(polygon1) || !isContourConvex(polygon2)) { fillColor = Scalar(0, 0, 255); } fillPoly(image, intersectionPolygon, fillColor); } polylines(image, polygons, true, Scalar(0, 0, 0)); return intersectArea; } static void drawDescription(Mat &image, int intersectionArea, string description, Point origin) { const size_t bufSize=1024; char caption[bufSize]; snprintf(caption, bufSize, "Intersection area: %d%s", intersectionArea, description.c_str()); putText(image, caption, origin, FONT_HERSHEY_SIMPLEX, 0.6, Scalar(0, 0, 0)); } static void intersectConvexExample() { Mat image(610, 550, CV_8UC3, Scalar(255, 255, 255)); float intersectionArea; intersectionArea = drawIntersection(image, makeRectangle(Point(10, 10), Point(50, 50)), makeRectangle(Point(20, 20), Point(60, 60))); drawDescription(image, (int)intersectionArea, "", Point(70, 40)); intersectionArea = drawIntersection(image, makeRectangle(Point(10, 70), Point(35, 95)), makeRectangle(Point(35, 95), Point(60, 120))); drawDescription(image, (int)intersectionArea, "", Point(70, 100)); intersectionArea = drawIntersection(image, makeRectangle(Point(10, 130), Point(60, 180)), makeRectangle(Point(20, 140), Point(50, 170)), true); drawDescription(image, (int)intersectionArea, " (handleNested true)", Point(70, 160)); intersectionArea = drawIntersection(image, makeRectangle(Point(10, 190), Point(60, 240)), makeRectangle(Point(20, 200), Point(50, 230)), false); drawDescription(image, (int)intersectionArea, " (handleNested false)", Point(70, 220)); intersectionArea = drawIntersection(image, makeRectangle(Point(10, 250), Point(60, 300)), makeRectangle(Point(20, 250), Point(50, 290)), true); drawDescription(image, (int)intersectionArea, " (handleNested true)", Point(70, 280)); // These rectangles share an edge so handleNested can be false and an intersection is still found intersectionArea = drawIntersection(image, makeRectangle(Point(10, 310), Point(60, 360)), makeRectangle(Point(20, 310), Point(50, 350)), false); drawDescription(image, (int)intersectionArea, " (handleNested false)", Point(70, 340)); intersectionArea = drawIntersection(image, makeRectangle(Point(10, 370), Point(60, 420)), makeRectangle(Point(20, 371), Point(50, 410)), false); drawDescription(image, (int)intersectionArea, " (handleNested false)", Point(70, 400)); // A vertex of the triangle lies on an edge of the rectangle so handleNested can be false and an intersection is still found intersectionArea = drawIntersection(image, makeRectangle(Point(10, 430), Point(60, 480)), makeTriangle(Point(35, 430), Point(20, 470), Point(50, 470)), false); drawDescription(image, (int)intersectionArea, " (handleNested false)", Point(70, 460)); // Show intersection of overlapping rectangle and triangle intersectionArea = drawIntersection(image, makeRectangle(Point(10, 490), Point(40, 540)), makeTriangle(Point(25, 500), Point(25, 530), Point(60, 515)), false); drawDescription(image, (int)intersectionArea, "", Point(70, 520)); // This concave polygon is invalid input to intersectConvexConvex so it returns an invalid intersection vector<Point> notConvex; notConvex.push_back(Point(25, 560)); notConvex.push_back(Point(25, 590)); notConvex.push_back(Point(45, 580)); notConvex.push_back(Point(60, 600)); notConvex.push_back(Point(60, 550)); notConvex.push_back(Point(45, 570)); intersectionArea = drawIntersection(image, makeRectangle(Point(10, 550), Point(50, 600)), notConvex, false); drawDescription(image, (int)intersectionArea, " (invalid input: not convex)", Point(70, 580)); imshow("Intersections", image); waitKey(0); } int main() { intersectConvexExample(); } |
19) تابع isContourConvex : تحدب ( convexity ) کانتور را بررسی میکند؛ کانتور باید ساده باشد، یعنی بدون خود تقاطع ( self-intersections )؛ در غیر این صورت، خروجی تابع تعریف نشده است.
تعریف تابع :
1 |
CV_EXPORTS_W bool isContourConvex( InputArray contour ); |
توضیح پارامترها :
- contour : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
- return : مقدار true یعنی کانتور محدب هستش، مقدار false هم یعنی کانتور محدب نیست!!!
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
void structural_analysis_and_shape_descriptors::isContourConvex_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/1.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); for(int i = 0; i< contours.size(); ++i) { cv::drawContours(src, contours, i, cv::Scalar(0,0,255), 3, cv::LINE_AA); std::cout << "contours[" << i << "].size() = " << contours[i].size() << std::endl; std::vector<cv::Point> approx; double epsilon = 0.01 * cv::arcLength(contours[i], true); cv::approxPolyDP(contours[i], approx, epsilon, true); for(int j = 0; j< approx.size(); ++j) cv::circle(src, approx[j], 5, cv::Scalar(0,255,0), -1, cv::LINE_AA); std::cout << "contours[" << i << "].size() = " << approx.size() << " --> after using approxPolyDP." << std::endl; bool result = cv::isContourConvex(approx); std::string text = ""; if(result) text = "Convex"; else text = "Concave"; cv::Size text_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 1, 2, 0); // center of contour cv::Rect rect = cv::boundingRect(approx); double cx = rect.x + 0.5 * rect.width; double cy = rect.y + 0.5 * rect.height; cv::Point center(cx - text_size.width/2, cy + 50); cv::putText(src, text, center, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255,0,0), 2, cv::LINE_AA); } cv::imshow("dst", src); } |
نتیجه کد بالا :
20) تابع matchShapes : دو شکل را با هم مقایسه میکند؛ هر سه روش پیاده سازی شده از متغیرهای Hu استفاده می کنند ( به تابع HuMoments مراجعه کنید )
تعریف تابع :
1 2 3 4 5 |
CV_EXPORTS_W double matchShapes( InputArray contour1, InputArray contour2, int method, double parameter ); |
توضیح پارامترها :
- contour1 : اولین تصویر خاکستری یا کانتور.
- contour2 : دومین تصویر خاکستری یا کانتور.
- method : روش مقایسه، به کمک نوع شمارشی ShapeMatchModes مقدار دهی میشه.
- parameter : پارامتر خاص روش ( اکنون پشتیبانی نمیشود !!!!!!!!!!!!!!!!!!!!!! ).
- return : مقدار برگشتی تابع؛ هر چی نزدیگ 0 باشه یعنی شباهت بیشتر.
نوع شمارشی ShapeMatchModes : -
1 2 3 4 5 6 |
enum ShapeMatchModes { CONTOURS_MATCH_I1 =1, CONTOURS_MATCH_I2 =2, CONTOURS_MATCH_I3 =3 }; |
توضیح گزینه ها :
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 | $$ |
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
void structural_analysis_and_shape_descriptors::matchShapes_test1() { cv::Mat img1 = cv::imread("C:/Users/Mahdi/Desktop/1.png", cv::IMREAD_GRAYSCALE); // main image cv::Mat img2 = cv::imread("C:/Users/Mahdi/Desktop/2.png", cv::IMREAD_GRAYSCALE); // rotated cv::Mat img3 = cv::imread("C:/Users/Mahdi/Desktop/3.png", cv::IMREAD_GRAYSCALE); // flip horizontal cv::Mat img4 = cv::imread("C:/Users/Mahdi/Desktop/4.png", cv::IMREAD_GRAYSCALE); // flip vertical cv::Mat img5 = cv::imread("C:/Users/Mahdi/Desktop/5.png", cv::IMREAD_GRAYSCALE); // flip both cv::Mat img6 = cv::imread("C:/Users/Mahdi/Desktop/6.png", cv::IMREAD_GRAYSCALE); // scale cv::Mat img7 = cv::imread("C:/Users/Mahdi/Desktop/7.png", cv::IMREAD_GRAYSCALE); // ??? cv::Mat img8 = cv::imread("C:/Users/Mahdi/Desktop/8.png", cv::IMREAD_GRAYSCALE); // ??? //cv::ShapeMatchModes double img1_1 = cv::matchShapes(img1, img1, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); double img1_2 = cv::matchShapes(img1, img2, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); double img1_3 = cv::matchShapes(img1, img3, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); double img1_4 = cv::matchShapes(img1, img4, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); double img1_5 = cv::matchShapes(img1, img5, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); double img1_6 = cv::matchShapes(img1, img6, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); double img1_7 = cv::matchShapes(img1, img7, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); double img1_8 = cv::matchShapes(img1, img8, cv::ShapeMatchModes::CONTOURS_MATCH_I1, 0); std::cout << "img1 vs img1 = " << img1_1 << std::endl; std::cout << "img1 vs img2 = " << img1_2 << std::endl; std::cout << "img1 vs img3 = " << img1_3 << std::endl; std::cout << "img1 vs img4 = " << img1_4 << std::endl; std::cout << "img1 vs img5 = " << img1_5 << std::endl; std::cout << "img1 vs img6 = " << img1_6 << std::endl; std::cout << "img1 vs img7 = " << img1_7 << std::endl; std::cout << "img1 vs img8 = " << img1_8 << std::endl; } |
نتیجه کد بالا : نتایج خروجی، گویای همه چیز هستش!!!
21) تابع minAreaRect : یک مستطیل چرخشی از حداقل مساحت محصور ( minimum area enclosing ) که مجموعه نقاط 2-بعدی ورودی را دربر میگیرد، پیدا میکند ( return میکند! )؛ برنامهنویس باید در نظر داشته باشد که RotatedRect برگشتی میتواند حاوی شاخصهای منفی باشد، زمانی که دادهها نزدیک به مرز عنصر Mat باشد.
تعریف تابع :
1 |
CV_EXPORTS_W RotatedRect minAreaRect( InputArray points ); |
توضیح پارامترها :
- points : بردار ورودی نقاط 2-بعدی، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
- return : در بالا توضیح دادم!
کد نمونه ( فرق توابع minAreaRect و boundingRect ) : در کد زیر از تابع minAreaRect استفاده کردم و مستطیل-چرخیده-شده رو محاسبه کردم و بعد برای نمایش مستطیل-چرخیده-شده ( خروجی تابع minAreaRect، از نوع کلاس RotatedRect هستش ) از دو روش استفاده کردیم، که خب یکیشون به کمک کلاس RotatedRect و دیگری به کمک تابع boxPoints که تو این مطلب معرفی کردم؛ بعد اومدم از تابع boundingRect استفاده کردم تا فرق تابع minAreaRect و boundingRect رو در عمل متوجه بشید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
void structural_analysis_and_shape_descriptors::minAreaRect_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/1.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); std::cout << "contours.size() = " << contours.size() << std::endl; cv::drawContours(src, contours, -1, cv::Scalar(0,0,255), 3, cv::LINE_AA); // red cv::RotatedRect rRect = cv::minAreaRect(contours[0]); // draw minAreaRect result : Method-1 cv::Point2f points1[4]; rRect.points(points1); for (int i = 0; i < 4; i++) cv::line(src, points1[i], points1[(i+1)%4], cv::Scalar(0,255,0), 10, cv::LINE_AA); // green // draw minAreaRect result : Method-2 cv::Mat points2; cv::boxPoints(rRect, points2); for (int i = 0; i < 4; i++) cv::line(src, cv::Point(points2.at<float>(i,0), points2.at<float>(i,1)), cv::Point(points2.at<float>((i+1)%4,0), points2.at<float>((i+1)%4,1)), cv::Scalar(255,0,0), 3, cv::LINE_AA); // blue cv::Rect rect = cv::boundingRect(contours[0]); // draw boundingRect result cv::rectangle(src, rect, cv::Scalar(255,0,255), 3, cv::LINE_AA); // pink cv::imshow("dst", src); } |
نتیجه کد بالا :
22) تابع minEnclosingCircle : دایره ای از حداقل مساحت محصور ( minimum area enclosing ) را که مجموعه نقاط 2-بعدی را در بر میگیرد، با استفاده از یک الگوریتم تکراری ( iterative algorithm ) پیدا میکند.
تعریف تابع :
1 2 3 4 |
CV_EXPORTS_W void minEnclosingCircle( InputArray points, CV_OUT Point2f& center, CV_OUT float& radius ); |
توضیح پارامترها :
- points : بردار حاوی نقاط 2-بعدی ( پارامتر ورودی )، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
- center : مرکز دایره ( پارامتر خروجی ).
- radius : شعاع دایره ( پارامتر خروجی ).
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void structural_analysis_and_shape_descriptors::minEnclosingCircle_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/1.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); std::cout << "contours.size() = " << contours.size() << std::endl; cv::drawContours(src, contours, -1, cv::Scalar(0,0,255), 3, cv::LINE_AA); // red cv::Point2f center; float radius; cv::minEnclosingCircle(contours[0], center, radius); cv::circle(src, center, radius, cv::Scalar(0,255,0), 3, cv::LINE_AA); cv::imshow("dst", src); } |
نتیجه کد بالا :
23) تابع minEnclosingTriangle : مثلثی با حداقل مساحت محصور ( minimum area enclosing ) که مجموعه نقاط 2-بعدی را در بر میگیرد، پیدا می کند و مساحت آن را برمی گرداند ( return میکند! )؛ اجرای الگوریتم بر اساس مقالات O'Rourke [192] و Klee و Laskowski [132] است.
تعریف تابع :
1 |
CV_EXPORTS_W double minEnclosingTriangle( InputArray points, CV_OUT OutputArray triangle ); |
توضیح پارامترها :
- points : ( پارامتر ورودی )؛ بردار حاوی نقاط 2-بعدی، با عمق CV_32S یا CV_32F، ذخیره شده در std::vector<cv::Point> یا cv::Mat.
- triangle : ( پارامتر خروجی )؛ بردار حاوی سه نقطه دو-بعدی که رئوس مثلث را مشخص میکند، عمق باید CV_32F باشد.
- return : در بالا توضیح دادم.
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void structural_analysis_and_shape_descriptors::minEnclosingTriangle_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/1.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); std::cout << "contours.size() = " << contours.size() << std::endl; cv::drawContours(src, contours, -1, cv::Scalar(0,0,255), 3, cv::LINE_AA); // red std::vector<cv::Point> triangle; double area = cv::minEnclosingTriangle(contours[0], triangle); std::cout << "Triangle Area = " << area << std::endl; cv::polylines(src, triangle, true, cv::Scalar(0,255,0), 3, cv::LINE_AA); cv::imshow("dst", src); } |
نتیجه کد بالا :
24) تابع moments : گشتاورهای ( moment های ) یک شکل برداری یا یک شکل شطرنجی را تا مرتبه 3 محاسبه میکند؛ نتایج در ساختار ( کلاس ) cv::Moments برگردانده میشوند.
تعریف تابع :
1 |
CV_EXPORTS_W Moments moments( InputArray array, bool binaryImage = false ); |
توضیح پارامترها :
- array : تصویر شطرنجی ( Raster image ) ( آرایه ای 2-بعدی، 1-کاناله، 8-بیتی یا اعشاری ) یا آرایه ای ( 1×N یا N×1 ) از نقاط دو بعدی ( cv::Point یا cv::Point2f ).
- binaryImage : اگر درست باشد، تمام پیکسل های تصویر غیر صفر به عنوان 1 در نظر گرفته میشوند؛ این پارامتر فقط برای تصاویر استفاده میشود.
- return : در بالا توضیح داده شده.
تکمیل شود : توضیح کامل کلاس cv::Moments
25) تابع pointPolygonTest : تست نقطه در کانتور ( point-in-contour ) را انجام میدهد؛ تابع تعیین میکند که آیا نقطه در داخل یک کانتور، خارج یا روی یک لبه قرار دارد ( یا منطبق با یک راس )؛ به ترتیب مقداری مثبت ( داخل )، منفی ( خارج )، یا صفر ( روی لبه ) را برمی گرداند؛ هنگامی که measureDist = false باشد، مقدار بازگشتی به ترتیب +1، -1 و 0 است؛ و اگر هنگامی که measureDist = true باشد، مقدار بازگشتی یک فاصله علامت گذاری شده بین نقطه و نزدیکترین لبه کانتور است.
تعریف تابع :
1 |
CV_EXPORTS_W double pointPolygonTest( InputArray contour, Point2f pt, bool measureDist ); |
توضیح پارامترها :
- contour : کانتور ورودی.
- pt : نقطه ای که قرار است در برابر کانتور تست شود.
- measureDist : این که تابع در شرایط مختلف، چه مقداری رو return کنه رو تعیین میکنه، در بالا دربارش صحبت کردیم.
- return : در بالا دربارش صحبت کردیم.
کد نمونه : در کد زیر تمام پیکسل های تصویر ورودی رو در برابر کانتور آزمایش کردم! ( برای تمام پیکسل های تصویر ورودی، از تابع pointPolygonTest استفاده کردم!! )؛ پارامتر measureDist رو true کردم تا بجای +1 و -1، فاصله های + و - به من بده ( 0 هم که در هر دو حالت 0 هستش! )؛ تو این قسمت یه تصویر از فاصله ها ( با نام raw_dist ) ایجاد کردیم؛ بعد اومدم یه تصویر خروجی ( dst ) ایجاد کردم تا طبق فاصله هر پیکسل، یه رنگی بهش اطلاق کنم :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
void structural_analysis_and_shape_descriptors::pointPolygonTest_test1() { cv::Mat src = cv::imread("C:/Users/Mahdi/Desktop/4.png"); cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat binary; cv::threshold(gray, binary, 100, 255, cv::THRESH_BINARY); std::vector<std::vector<cv::Point>> contours; std::vector<cv::Vec4i> hierarchy; cv::findContours(binary, contours, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE); std::cout << "contours.size() = " << contours.size() << std::endl; std::cout << "contours[0].size() = " << contours[0].size() << std::endl; cv::Mat raw_dist( src.size(), CV_32F ); for( int r = 0; r < src.rows; r++ ) { for( int c = 0; c < src.cols; c++ ) { raw_dist.at<float>(r,c) = (float)cv::pointPolygonTest( contours[0], cv::Point2f((float)c, (float)r), true ); } } cv::Mat dst = cv::Mat( src.size(), CV_8UC1 ); for( int i = 0; i < src.rows; i++ ) { for( int j = 0; j < src.cols; j++ ) { if( raw_dist.at<float>(i,j) == 0 ) { dst.at<uchar>(i,j) = 0; } else { dst.at<uchar>(i,j) = (uchar)(255 - cv::abs(raw_dist.at<float>(i,j))); } } } cv::imshow("src", src); cv::imshow("dst", dst); } |
نتیجه کد بالا : بنظرتون چرا تو تصویر خروجی، اون قسمت خارج از فیل، اون دایره هه ایجاد شده؟ بله سرریز رخ داده، تصویر raw_dist حاوی فاصله ها هستش، که من اومدم تو یه فرمول ساده قرارش دادم، بدون این که مقیاس اعداد موجود تو این آرایه/تصویر رو تغییر بدم، مثلا فاصله ها بین مقادیر -1000 تا +1000 هستش که خب من از تابع cv::abs استفاده کردم ( همون قدر مطلق )، فلذا فاصله ها بین 0 تا 1000 هستند؛ تصویر خروجی من هم از نوع 8-بیتی هستش، یعنی مقادیرش بین 0 تا 255 هستش، خب حالا منی که مستقیما اومدم از فاصله های تصویر raw_dist استفاده کردم و تغییر مقیاس هم ندادم اعدادش رو، خب از یه جایی به بعد مقادیر فاصله از 255 بیشتر میشه و سرریز رخ میده که دقیقا همون موضوع رو تو تصویر میبینید، برا حل این مشکل میتونید مقادیر رو تغییر مقیاس بدید.
تغییر مقیاس : تو کد زیر محدوده 0-255 رو به 100-150 منتقل کردم ( تغییر مقیاس دادم، تغییر محدوده، اصطلاح درستش یادم نی ) :
1 2 3 4 5 6 7 8 9 10 |
int OldMin = 0, OldMax = 255; int OldRange = OldMax - OldMin; int NewMin = 100, NewMax = 150; int NewRange = NewMax - NewMin; //--- for (int i = 0; i < 256; i++) { int OldValue = i; int NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin; } |
توجه : همین کد رو در مسیر زیر میتونید پیدا کنید، بهتر از کد بالا و البته خروجی رنگی!؛ من سعی کردم این کد رو تا حد امکان ساده کنم.
[opencv]/samples/cpp/tutorial_code/ShapeDescriptors/pointPolygonTest_demo.cpp
26) تابع rotatedRectangleIntersection : بررسی میکند که آیا بین دو مستطیل-چرخیده-شده تلاقی/تقاطع ( intersection ) وجود دارد یا نه؛ اگر وجود داشته باشد، رئوس ناحیه متقاطع را به ما میدهد؛ در زیر چند نمونه از تقاطع ها نمایش داده شده است، الگوی هاشور خورده ( hatched pattern )، ناحیه متقاطع را نشان میدهد ( که وضعیتش، توسط تابع return میشه ) و رئوس قرمز ( توسط پارامتر intersectingRegion ) به ما داده میشود!
تعریف تابع :
1 2 3 4 |
CV_EXPORTS_W int rotatedRectangleIntersection( const RotatedRect& rect1, const RotatedRect& rect2, OutputArray intersectingRegion ); |
توضیح پارامترها :
- rect1 و rect2 : مستطیل های چرخشی ( کلاس RotatedRect ) داستان ما !!!
- intersectingRegion : آرایه خروجی رئوس ناحیه متقاطع؛ حداکثر 8 راس برمی گرداند؛ ذخیره شده در std::vector<cv::Point2f> یا cv::Mat ( به عنوان Mx1 از نوع CV_32FC2 ).
- return : تابع، یکی از مقادیر نوع شمارشی RectanglesIntersectTypes را ارسال میکند؛ که وضعیت اون قسمت هاشور خورده ( مشترک بین مستطیل های ورودی ) رو مشخص میکنه.
نوع شمارشی RectanglesIntersectTypes :
1 2 3 4 5 6 |
enum RectanglesIntersectTypes { INTERSECT_NONE = 0, INTERSECT_PARTIAL = 1, INTERSECT_FULL = 2 }; |
توضیح گزینه ها :
- INTERSECT_NONE : بدون تقاطع.
- INTERSECT_PARTIAL : یک تقاطع جزئی وجود دارد.
- INTERSECT_FULL : یکی از مستطیل ها به طور کامل در دیگری محصور شده است.
کد نمونه :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
void structural_analysis_and_shape_descriptors::rotatedRectangleIntersection_test1() { cv::Mat img(500, 900, CV_8UC3, cv::Scalar(94, 73, 52)); // create 2 rect cv::RotatedRect rRect1(cv::Point2f(450,250), cv::Size(300,300), 0); cv::RotatedRect rRect2(cv::Point2f(450,250), cv::Size(300,300), 45); // draw rRect1, rRect2 cv::Point2f points1[4], points2[4]; rRect1.points(points1); rRect2.points(points2); for (int i = 0; i < 4; i++) { cv::line(img, points1[i], points1[(i+1)%4], cv::Scalar(60,76,231), 5, cv::LINE_AA); cv::line(img, points2[i], points2[(i+1)%4], cv::Scalar(219,152,52), 5, cv::LINE_AA); } // rotatedRectangleIntersection std::vector<cv::Point2f> intersectingRegion; int result = cv::rotatedRectangleIntersection(rRect1, rRect2, intersectingRegion); // draw intersectingRegion for(int i = 0; i< intersectingRegion.size(); ++i) cv::circle(img, intersectingRegion[i], 10, cv::Scalar(113,204,46), -1, cv::LINE_AA); // draw 'hatched pattern' status! std::string text = ""; switch (result) { case cv::RectanglesIntersectTypes::INTERSECT_NONE : text = "INTERSECT_NONE"; break; case cv::RectanglesIntersectTypes::INTERSECT_PARTIAL : text = "INTERSECT_PARTIAL"; break; case cv::RectanglesIntersectTypes::INTERSECT_FULL : text = "INTERSECT_FULL"; break; } cv::putText(img, text, cv::Point(20, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(241,240,236), 2, cv::LINE_AA); cv::imshow("img", img); } |
نتیجه کد بالا :
منابع :
مطالب مرتبط :
تا مطلب بعد اگه زنده بودیم و قسمت شد، یا علی.