nonblizz 2 Жалоба Опубликовано January 21, 2015 Доброго времени суток всем! Пишу диплом, где мне необходимо посчитать количество зубьев шестерни на изображении, а также диаметр самой шестеренки. Есть Я выделил контуры изображения вот так (код на Java-обёртке, но не суть) : Mat sourceMat = new Mat(), defaultMat = new Mat(); Utils.bitmapToMat(src, sourceMat); // Матрица изображения для обработки Utils.bitmapToMat(src, defaultMat);// Матрица исходная, для конечной отрисовки Imgproc.cvtColor(sourceMat, sourceMat, Imgproc.COLOR_RGB2GRAY, 4); //Преобразование матрицы в чб Mat blurMat = new Mat(); Mat canMat = new Mat(); Imgproc.blur(sourceMat, canMat, new Size(7,5)); //Сглаживание Imgproc.Canny(canMat, blurMat, 0, 60, 3, false); //Детектор границ Кенни Mat hierarchy = new Mat(); List<MatOfPoint> contours = new ArrayList<MatOfPoint>(); Imgproc.findContours(blurMat, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE); Что делать дальше, я не особо понимаю. Прошу вашего совета и помощи. Примеры изображений Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано January 21, 2015 Я бы попробовал сгладить, бинаризировать, найти внешнюю окружность через преобразование Хафа, немного ее уменьшить (примерно на половину высоты зуба), засемплить точки с этой окружности и посчитать отрезки. 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
mrgloom 242 Жалоба Опубликовано January 21, 2015 Я бы сделал так, найти окружность преобразованием хафа, расширить окружность (т.к. зубцы скорее всего не попадут), получить блоб шестеренки(т.е. бинарную картинку где фон черный шестеренка полностью закрашенная белая),потом развернуть окружность в полоску относительно центра окружности(logpolar), и потом у вас будет такой "забор" у которого можно будет найти кол-во пиков простым суммированием пикселей по вертикали. На самом деле задача очень похожа на то как получают iris code. http://stackoverflow.com/questions/23127769/linearpolar-logpolar-transformation-distoring-an-iris-in-opencv 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
nonblizz 2 Жалоба Опубликовано February 24, 2015 Спасибо за ваши советы. Сделал бинаризацию, выделил границы через Кенни, затем сделал поиск кругов Хафа. Код: Imgproc.cvtColor(sourceImg, sourceImg, Imgproc.COLOR_RGB2GRAY, 4); Imgproc.GaussianBlur(sourceImg,des, new Size(9,9),3,3); Imgproc.threshold(des, des, 30, 1, Imgproc.THRESH_TOZERO); Imgproc.Canny(des, des, 0 , 60); Mat circles = new Mat(); Imgproc.HoughCircles(des, circles, Imgproc.CV_HOUGH_GRADIENT, 1.0, 50, 70.0, 30.0, 100, 0); Вот что получилось (для каждого изображения, что выкладывал в 1ом посте): 1. (Почему-то вообще ни одного круга так и не нашел) 2. (не влезла полностью картинка) 3. 4. И соответственно ряд вопросов: как автоматически подбирать трешхолды (для кенни, сглаживания и бинаризации)? как расширить окружность (вернее, как узнать, насколько расширять окружность)? Заранее извиняюсь, если вопросы глупые, я новичок в openCV. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано February 24, 2015 Главное чтобы внешний контур нормально определился. Чтобы найти окружность можно действовать как здесь: http://docs.opencv.org/doc/tutorials/imgproc/shapedescriptors/bounding_rotated_ellipses/bounding_rotated_ellipses.html Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
mrgloom 242 Жалоба Опубликовано February 24, 2015 Да, если шестерёнки большие, окружность не найдется(можно попробовать так же блюр и\или уменьшение размеров изображения, но это малодейственно думаю). Threshold никак не подобрать, используйте адаптивный метод Otsu, так же можно подбирать на глаз, собирать статистику(взяли N картинок настроили как вам надо, записали все значения threshold, потом взяли например медиану для применения к неизвестной картинке, так же можно собрать статистику с того сколько контуров выделилось при каком threshold и потом при применении к новому изображению подкручивать автоматически threshold до нужного эффекта (подразумевается, что если много контуров выделило, то это излишняя детализация или шум)) Если рассматривать контуры как точки то можно найти minimum bounding circle или как то так. Можно еще попробовать что то типа МНК(LMS) для 2D point cloud circle fit http://people.cas.uab.edu/~mosya/cl/MATLABcircle.html http://people.cas.uab.edu/~mosya/cl/index.html Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
nonblizz 2 Жалоба Опубликовано February 24, 2015 Метод Отсу даёт такую картину: У меня так и не получилось убрать эту тень, а также убрать "засветы" Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
mrgloom 242 Жалоба Опубликовано February 24, 2015 Да еще надо сказать, что проблема картинок в плохой подсветке, т.е. наличие теней(неравномерная засветка), но по идее с этим можно справиться локальной бинаризацией. Если пойти от бинаризации: Глобальная Otsu: habrastorage.org/files/fd7/ebb/4f5/fd7ebb4f53a24be3b42c3b8a30241afb.PNGhabrastorage.org/files/3e3/4f4/b9f/3e34f4b9ffbd44c4aaeccc100447fda0.PNGhabrastorage.org/files/cd3/e28/6d2/cd3e286d2c4d489e9596b31c2f9289cc.PNGhabrastorage.org/files/ea1/5f6/d8d/ea15f6d8d0144cafbe4add9cddc5b9b9.PNGhabrastorage.org/files/984/e50/ef6/984e50ef60f64f1fabde583e354709fd.PNG Разные локальные бинаризации, видно что лучше выделяет зубцы, но все равно не идеально. habrastorage.org/files/4e1/005/724/4e1005724d3b42c39130b55fccd44f62.PNGhabrastorage.org/files/dab/48f/2aa/dab48f2aa4db43b0859d358e04e353cf.PNGhabrastorage.org/files/59b/ed6/e1f/59bed6e1faf6434fba0e4a60ac7217e3.PNGhabrastorage.org/files/a6a/d0d/731/a6ad0d731f904af29d9ea5617d9b74ca.PNGhabrastorage.org/files/28e/22b/1c7/28e22b1c7add41bbbd7090fe72acd771.PNG Еще нашел в HALCON, правда он закрытый, но демку можно погонять: http://compumodules.com/image-processing/high-speed-camera/Machine-Vision-Software/HALCON-software-application-examples.html Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
nonblizz 2 Жалоба Опубликовано February 24, 2015 А если взять такие шестеренки: Как выделить зубья? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано February 25, 2015 Я точно знаю, что зубчатое колесо можно аналитически построить по нескольким параметрам. Следовательно имеем модель колеса, управляемую несколькими параметрами. Теперь, если определим функцию качества совпадения (например градиент изображения, расположенный в окрестности границы расчетного контура колеса), то можно провести оптимизацию, и получить очень даже хорошее качество совпадения + параметры геометрии зубчатого колеса. Эта оптимизация должна проводиться с хорошим начальным приближением. (хотя бы расположение и радиус внешней окружности). Задаем параметры -> строим -> оцениваем качество совпадения меняем параметры, и все по новой. Вот такой брутфорс получается. Также возможны вариации с контурами, моделями активной формы и т.д. . Есть вариант с разложением в частотный спектр (для определения шага зубьев), но для этого надо знать окружность вершин зубьев. Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
nonblizz 2 Жалоба Опубликовано February 25, 2015 Я точно знаю, что зубчатое колесо можно аналитически построить по нескольким параметрам. Следовательно имеем модель колеса, управляемую несколькими параметрами. Теперь, если определим функцию качества совпадения (например градиент изображения, расположенный в окрестности границы расчетного контура колеса), то можно провести оптимизацию, и получить очень даже хорошее качество совпадения + параметры геометрии зубчатого колеса. Эта оптимизация должна проводиться с хорошим начальным приближением. (хотя бы расположение и радиус внешней окружности). Задаем параметры -> строим -> оцениваем качество совпадения меняем параметры, и все по новой. Вот такой брутфорс получается. Также возможны вариации с контурами, моделями активной формы и т.д. . Есть вариант с разложением в частотный спектр (для определения шага зубьев), но для этого надо знать окружность вершин зубьев. Проблема в том, что на одном и том же радиусе может быть разное количество зубьев (от 17 до 40), это подбирается исключительно шагом зубьев(его можно было бы вычислить, если бы как-то выделить центр шестерни и радиус высоты головки зуба, чтобы там четко выделялось хотя бы 2 стоящих рядом зуба(затем 360/угол между 2 зубьями и есть количество). Все упирается в эту окружность высоты головки зуба, которую надо как-то выделить (как - я придумать категорически не могу, единственный вариант который приходит в голову (в идеальных условиях), это взять крайний верхний, затем правый, затем левый пиксели (они придутся (по идее) на край одного из зубьев, затем построить окружность по 3м точкам. Нормальная ли это идея? Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
mrgloom 242 Жалоба Опубликовано February 25, 2015 вот вспомнил Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано February 25, 2015 Да, по трем точкам можно, ведь они все лежат на описанной окружности. Ну для простого случая что-то такое (правда в моей версии какой то глюк с minEnclosingCircle, выдает окружность немного больше). Количество полос на развертке соответствует количеству зубьев. #include "opencv2/opencv.hpp" #include <iostream> #include <vector> using namespace cv; using namespace std; int main(int ac, char** av) { vector<vector<Point> > contours; vector<Vec4i> hierarchy; vector<RotatedRect> minEllipse; vector<RotatedRect> minRect; Mat frame=imread("gear.png"); Mat drawing=frame.clone(); frame=266-frame; namedWindow("result"); cvtColor(frame,frame,cv::COLOR_BGR2GRAY); threshold( frame, frame, 1, 255, THRESH_BINARY ); Mat thr=frame.clone(); /// Find contours findContours( frame, contours, hierarchy, cv::RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) ); vector<Point2f>center( contours.size() ); vector<float>radius( contours.size() ); vector<vector<Point> > contours_poly( contours.size() ); for( int i = 0; i < contours.size(); i++ ) { approxPolyDP( Mat(contours[i]), contours_poly[i], 5, true ); minEnclosingCircle( (Mat)contours_poly[i], center[i], radius[i] ); minRect.push_back(minAreaRect( Mat(contours[i]) )); if( contours[i].size() > 5 ) { minEllipse.push_back(fitEllipse( Mat(contours[i]) )); } } RNG rng(12345); for( int i = 0; i< contours.size(); i++ ) { Scalar color = Scalar( 255, 0, 0 ); // contour drawContours( drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point() ); // ellipse ellipse( drawing, minEllipse[i], color, 1, 8 ); // rotated rectangle Point2f rect_points[4]; minRect[i].points( rect_points ); for( int j = 0; j < 4; j++ ) line( drawing, rect_points[j], rect_points[(j+1)%4], color, 1, 8 ); // Окружность circle( drawing, center[i], cvRound(radius[i]), color, 1, 8, 0 ); // Центр окружности line(drawing,cv::Point(center[i].x,0),cv::Point(center[i].x,drawing.rows),Scalar(0,0,255)); line(drawing,cv::Point(0,center[i].y),cv::Point(drawing.cols,center[i].y),Scalar(0,0,255)); } // засемплим точки с окружности. float step=0.01; float x=center[0].x; float y=center[0].y; float r=radius[0]*0.9; Mat unrolled=Mat::zeros(100,CV_PI*2.0/step,CV_8UC1); int i=0; for(float ang=0;ang<CV_PI*2;ang+=step) { float xs=x+r*cos(ang); float ys=y+r*sin(ang); uchar c=thr.at<uchar>(ys,xs); drawing.at<Vec3b>(ys,xs)=Vec3b(0,0,255); if(c>128) { line(unrolled,cv::Point(i,0),cv::Point(i,unrolled.rows),Scalar::all(255)); } ++i; } imshow("thr",thr); imshow("unrolled",unrolled); imshow("result",drawing); waitKey(0); destroyAllWindows(); return 0; } Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
nonblizz 2 Жалоба Опубликовано July 29, 2015 (изменено) Защитил диплом, забыл приложить финальный код, может кому пригодится. Большое спасибо Smorodov и всем отписавшимся.Считаются все необходимые параметры(коэффициент смещения колеса, делительная окружность, модуль колеса). При необходимости реализации масштаба, нужно домножить все параметры на коэффициент масштаба. #include "opencv2/opencv.hpp" #include <iostream> #include <vector> using namespace cv; using namespace std; int main(int ac, char** av) { vector<vector<Point> > contours; vector<Vec4i> hierarchy; vector<RotatedRect> minEllipse; vector<RotatedRect> minRect; string fname; float alpha = 20; if (ac > 1) { fname = av[1]; alpha = ::atof(av[2]); } else { cout << "Need filename!" << endl; cin.get(); return 0; } Mat frame = imread(fname); Mat drawing = frame.clone(); frame = 266 - frame; namedWindow("result"); cvtColor(frame, frame, cv::COLOR_BGR2GRAY); threshold(frame, frame, 1, 255, THRESH_BINARY); Mat thr = frame.clone(); /// Find contours findContours(frame, contours, hierarchy, cv::RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); vector<Point2f>center(contours.size()); vector<float>radius(contours.size()); vector<vector<Point> > contours_poly(contours.size()); for (int i = 0; i < contours.size(); i++) { approxPolyDP(Mat(contours[i]), contours_poly[i], 5, true); minEnclosingCircle((Mat)contours_poly[i], center[i], radius[i]); minRect.push_back(minAreaRect(Mat(contours[i]))); if (contours[i].size() > 5) { minEllipse.push_back(fitEllipse(Mat(contours[i]))); } } RNG rng(12345); for (int i = 0; i< contours.size(); i++) { Scalar color = Scalar(255, 0, 0); // contour drawContours(drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point()); // ellipse ellipse(drawing, minEllipse[i], color, 0, 8); // rotated rectangle Point2f rect_points[4]; minRect[i].points(rect_points); for (int j = 0; j < 4; j++) line(drawing, rect_points[j], rect_points[(j + 1) % 4], color, 1, 8); // окружность circle(drawing, center[i], cvRound(radius[i]), color, 1, 8, 0); // ÷ентр окружности line(drawing, cv::Point(center[i].x, 0), cv::Point(center[i].x, drawing.rows), Scalar(0, 0, 255)); line(drawing, cv::Point(0, center[i].y), cv::Point(drawing.cols, center[i].y), Scalar(0, 0, 255)); } // засемплим точки с окружности. float step = 0.001; float x = center[0].x; float y = center[0].y; float r = radius[0] * 0.9; Mat unrolled = Mat::zeros(100, CV_PI*2.0 / step, CV_8UC1); int i = 0; int tooth_thick = 0; vector<int> teeth; bool tooth = false; float normals[2][2]; for (float ang = 0; ang<CV_PI * 2; ang += step) { float xs = x + r*cos(ang); float ys = y + r*sin(ang); uchar c = thr.at<uchar>(ys, xs); drawing.at<Vec3b>(ys, xs) = Vec3b(0, 0, 255); if (c>128) { if (teeth.size() == 1 && tooth == false) { normals[0][0] = xs; normals[0][1] = ys; } tooth = true; tooth_thick++; line(unrolled, cv::Point(i, 0), cv::Point(i, unrolled.rows), Scalar::all(255),5); } else { if (tooth) { teeth.push_back(tooth_thick); tooth_thick = 0; //count++; if (teeth.size() == 1) { normals[1][0] = xs; normals[1][1] = ys; } } tooth = false; } ++i; } //do normals to surface of teeth //line(drawing, Point(normals[0][0], normals[0][1]), Point(800 * cos(alpha * 180 / CV_PI), normals[0][1] * sin(alpha * 180 / CV_PI)), Scalar(0, 255, 0), 1); //line(drawing, Point(normals[1][0], normals[1][1]), Point(800 * cos(alpha * 180 / CV_PI), normals[1][1] * sin(alpha * 180 / CV_PI)), Scalar(0, 255, 0), 1); float m = minRect[0].size.width/(teeth.size()+2);//модуль float d = m*teeth.size();//delitelnaya okrujnost float Db = d*::cos(alpha); float sb = (CV_PI*m) / 2; //tolshina po delitelnoy okrujnosti alpha = alpha * CV_PI / 180;//for invol (convert deg to radian) float X = ((normals[0][1] - normals[1][1]) - (Db*(sb / d + (tan(alpha)-alpha)))) / (2 * m*sin(alpha)); stringstream str; str << "d= " + to_string(minRect[0].size.width) << " "; str << "m= " + to_string(m) << " "; str << "x= " + to_string(X) << " "; //imshow("thr", thr); //imshow("unrolled", unrolled); Mat pic = cv::Mat::zeros(250, 250, CV_8UC3); putText(pic,str.str(), cv::Point(5, 10), CV_FONT_NORMAL, 0.3, Scalar::all(255), 1, 7, false); imshow("Результаты", pic); imshow("result", drawing); waitKey(0); destroyAllWindows(); return 0; } Изменено July 29, 2015 пользователем nonblizz 2 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Smorodov 579 Жалоба Опубликовано July 29, 2015 Наши поздравления . 1 Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах