Перейти к содержимому
Compvision.ru
nonblizz

Определить количество зубьев шестерни

Recommended Posts

Доброго времени суток всем! Пишу диплом, где мне необходимо посчитать количество зубьев шестерни на изображении, а также диаметр самой шестеренки. Есть 

Я выделил контуры изображения вот так (код на 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);

Что делать дальше, я не особо понимаю. Прошу вашего совета и помощи. 

 

Примеры изображений

post-7182-0-31976500-1421824877_thumb.jp

post-7182-0-32415200-1421824880_thumb.jp

post-7182-0-37233300-1421824883_thumb.jp

post-7182-0-13078400-1421824886_thumb.jp

post-7182-0-27874500-1421824889_thumb.jp

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я бы попробовал сгладить, бинаризировать, найти внешнюю окружность через преобразование Хафа, немного ее уменьшить (примерно на половину высоты зуба), засемплить точки с этой окружности и посчитать отрезки.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я бы сделал так, найти окружность преобразованием хафа, расширить окружность (т.к. зубцы скорее всего не попадут), получить блоб шестеренки(т.е. бинарную картинку где фон черный шестеренка полностью закрашенная белая),потом развернуть окружность в полоску относительно центра окружности(logpolar), и потом у вас будет такой "забор" у которого можно будет найти кол-во пиков простым суммированием пикселей по вертикали.

 

На самом деле задача очень похожа на то как получают iris code.

SameSuccess80.png

http://stackoverflow.com/questions/23127769/linearpolar-logpolar-transformation-distoring-an-iris-in-opencv

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Спасибо за ваши советы. Сделал бинаризацию, выделил границы через Кенни, затем сделал поиск кругов Хафа.

Код:

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. (Почему-то вообще ни одного круга так и не нашел)

dba693019e2c00a2210ae98a3235785c.png

 

2. (не влезла полностью картинка) 

3672214ef581aa486dd374fdfacbeb9a.png

 

3.

3136aac21900db886c434521ed1ccc3f.png

 

4. 

1bbe16f106b65dbc28385dbcbf57cb93.png

 

И соответственно ряд вопросов: как автоматически подбирать трешхолды (для кенни, сглаживания и бинаризации)? как расширить окружность (вернее, как узнать, насколько расширять окружность)? 

 

Заранее извиняюсь, если вопросы глупые, я новичок в openCV.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Главное чтобы внешний контур нормально определился.

Чтобы найти окружность можно действовать как здесь: http://docs.opencv.org/doc/tutorials/imgproc/shapedescriptors/bounding_rotated_ellipses/bounding_rotated_ellipses.html

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Да, если шестерёнки большие, окружность не найдется(можно попробовать так же блюр и\или уменьшение размеров изображения, но это малодейственно думаю).

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Метод Отсу даёт такую картину:

psH3llIDDc8.jpgd5766378394bb6c6e36332c754dc85c8.png

У меня так и не получилось убрать эту тень, а также убрать "засветы"

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Да еще надо сказать, что проблема картинок в плохой подсветке, т.е. наличие теней(неравномерная засветка), но по идее с этим можно справиться локальной бинаризацией.

 

Если пойти от бинаризации:

 

Глобальная Otsu:

habrastorage.org/files/fd7/ebb/4f5/fd7ebb4f53a24be3b42c3b8a30241afb.PNG
habrastorage.org/files/3e3/4f4/b9f/3e34f4b9ffbd44c4aaeccc100447fda0.PNG
habrastorage.org/files/cd3/e28/6d2/cd3e286d2c4d489e9596b31c2f9289cc.PNG
habrastorage.org/files/ea1/5f6/d8d/ea15f6d8d0144cafbe4add9cddc5b9b9.PNG
habrastorage.org/files/984/e50/ef6/984e50ef60f64f1fabde583e354709fd.PNG

 

Разные локальные бинаризации, видно что лучше выделяет зубцы, но все равно не идеально.

habrastorage.org/files/4e1/005/724/4e1005724d3b42c39130b55fccd44f62.PNG
habrastorage.org/files/dab/48f/2aa/dab48f2aa4db43b0859d358e04e353cf.PNG
habrastorage.org/files/59b/ed6/e1f/59bed6e1faf6434fba0e4a60ac7217e3.PNG
habrastorage.org/files/a6a/d0d/731/a6ad0d731f904af29d9ea5617d9b74ca.PNG
habrastorage.org/files/28e/22b/1c7/28e22b1c7add41bbbd7090fe72acd771.PNG

 

 

Еще нашел в HALCON, правда он закрытый, но демку можно погонять:

measure-large.gif

matching-large.gif

http://compumodules.com/image-processing/high-speed-camera/Machine-Vision-Software/HALCON-software-application-examples.html

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я точно знаю, что зубчатое колесо можно аналитически построить по нескольким параметрам.

Следовательно имеем модель колеса, управляемую несколькими параметрами.

 

Теперь, если определим функцию качества совпадения (например градиент изображения, расположенный в окрестности границы расчетного контура колеса), то можно провести оптимизацию, и получить очень даже хорошее качество совпадения + параметры геометрии зубчатого колеса.

 

Эта оптимизация должна проводиться с хорошим начальным приближением. (хотя бы расположение и радиус внешней окружности).

 

Задаем параметры -> строим -> оцениваем качество совпадения меняем параметры, и все по новой.

Вот такой брутфорс получается.

 

Также возможны вариации с контурами, моделями активной формы и т.д. .

 

Есть вариант с разложением в частотный спектр (для определения шага зубьев), но для этого надо знать окружность вершин зубьев.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я точно знаю, что зубчатое колесо можно аналитически построить по нескольким параметрам.

Следовательно имеем модель колеса, управляемую несколькими параметрами.

 

Теперь, если определим функцию качества совпадения (например градиент изображения, расположенный в окрестности границы расчетного контура колеса), то можно провести оптимизацию, и получить очень даже хорошее качество совпадения + параметры геометрии зубчатого колеса.

 

Эта оптимизация должна проводиться с хорошим начальным приближением. (хотя бы расположение и радиус внешней окружности).

 

Задаем параметры -> строим -> оцениваем качество совпадения меняем параметры, и все по новой.

Вот такой брутфорс получается.

 

Также возможны вариации с контурами, моделями активной формы и т.д. .

 

Есть вариант с разложением в частотный спектр (для определения шага зубьев), но для этого надо знать окружность вершин зубьев.

Проблема в том, что на одном и том же радиусе может быть разное количество зубьев (от 17 до 40), это подбирается исключительно шагом зубьев(его можно было бы вычислить, если бы как-то выделить центр шестерни и радиус высоты головки зуба, чтобы там четко выделялось хотя бы 2 стоящих рядом зуба(затем 360/угол между 2 зубьями и есть количество). Все упирается в эту окружность высоты головки зуба, которую надо как-то выделить (как - я придумать категорически не могу, единственный вариант который приходит в голову (в идеальных условиях), это взять крайний верхний, затем правый, затем левый пиксели (они придутся (по идее) на край одного из зубьев, затем построить окружность по 3м точкам. Нормальная ли это идея?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Да, по трем точкам можно, ведь они все лежат на описанной окружности.

Ну для простого случая что-то такое (правда в моей версии какой то глюк с minEnclosingCircle, выдает окружность немного больше).

 

Количество полос на развертке соответствует количеству зубьев.

 

 

post-1-0-16259700-1424884830_thumb.png

#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;
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Защитил диплом, забыл приложить финальный код, может кому пригодится. Большое спасибо 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;
}

 

Изменено пользователем nonblizz
  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Наши поздравления :).

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×