Я редко нахожу Hough полезным для приложений реального мира, поэтому я предпочел бы идти по пути шумоподавления, сегментации и подбора эллипса.
Для шумоподавления выбирают нелокальные средние (NLM).Для сегментации - просто глядя на изображение - я придумал модель гауссовой смеси с тремя классами: один для фона и два для объекта (рассеянный и зеркальный компонент).Здесь модель смешения по существу моделирует форму гистограммы изображения грайского уровня тремя гауссовыми функциями (как показано в смесь-гистограмма Википедии gif ).Заинтересованный читатель перенаправляется на статью в Википедии .
Подгонка эллипса в конце - это просто элементарный OpenCV-инструмент.
В C ++, но аналог OpenCV-Python
#include "opencv2/ml.hpp"
#include "opencv2/photo.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
void gaussianMixture(const cv::Mat &src, cv::Mat &dst, int nClasses )
{
if ( src.type()!=CV_8UC1 )
CV_Error(CV_StsError,"src is not 8-bit grayscale");
// reshape
cv::Mat samples( src.rows * src.cols, 1, CV_32FC1 );
src.convertTo( cv::Mat( src.size(), CV_32FC1, samples.data ), CV_32F );
cv::Mat labels;
cv::Ptr<cv::ml::EM> em = cv::ml::EM::create();
em->setClustersNumber( nClasses );
em->setTermCriteria( cv::TermCriteria(CV_TERMCRIT_ITER, 4, 0.0 ) );
em->trainEM( samples );
if ( dst.type()!=CV_8UC1 || dst.size()!=src.size() )
dst = cv::Mat( src.size(),CV_8UC1 );
for(int y=0;y<src.rows;++y)
{
for(int x=0;x<src.cols;++x)
{
dst.at<unsigned char>(y,x) = em->predict( src.at<unsigned char>(y,x) );
}
}
}
void automate()
{
cv::Mat input = cv::imread( /* input image in color */,cv::IMREAD_COLOR);
cv::Mat inputDenoised;
cv::fastNlMeansDenoising( input, inputDenoised, 8.0, 5, 17 );
cv::Mat gray;
cv::cvtColor(inputDenoised,gray,cv::COLOR_BGR2GRAY );
gaussianMixture(gray,gray,3 );
typedef std::vector< std::vector< cv::Point > > VecOfVec;
VecOfVec contours;
cv::Mat objectPixels = gray>0;
cv::findContours( objectPixels, contours, cv::RETR_LIST, cv::CHAIN_APPROX_NONE );
cv::Mat inputcopy; // for drawing of ellipses
input.copyTo( inputcopy );
for ( size_t i=0;i<contours.size();++i )
{
if ( contours[i].size() < 5 )
continue;
cv::drawContours( input, VecOfVec{contours[i]}, -1, cv::Scalar(0,0,255), 2 );
cv::RotatedRect rect = cv::fitEllipse( contours[i] );
cv::ellipse( inputcopy, rect, cv::Scalar(0,0,255), 2 );
}
}
Я должен был очистить очень маленькие контуры (в секундах верхнего ряда) (больше, чем минимум 5 точек) перед рисованием эллипсов.
* править * добавленоПредиктор Python без шумов и контуров поиска.После изучения модели время прогнозирования составляет около 1,1 секунды
img = cv.imread('D:/tmp/8b3Lm.jpg', cv.IMREAD_GRAYSCALE )
class Predictor :
def train( self, img ):
self.em = cv.ml.EM_create()
self.em.setClustersNumber( 3 )
self.em.setTermCriteria( ( cv.TERM_CRITERIA_COUNT,4,0 ) )
samples = np.reshape( img, (img.shape[0]*img.shape[1], -1) ).astype('float')
self.em.trainEM( samples )
def predict( self, img ):
samples = np.reshape( img, (img.shape[0]*img.shape[1], -1) ).astype('float')
labels = np.zeros( samples.shape, 'uint8' )
for i in range ( samples.shape[0] ):
retval, probs = self.em.predict2( samples[i] )
labels[i] = retval[1] * (255/3) # make it [0,255] for imshow
return np.reshape( labels, img.shape )
predictor = Predictor()
predictor.train( img )
t = time.perf_counter()
predictor.train( img )
t = time.perf_counter() - t
print ( "train %s s" %t )
t = time.perf_counter()
labels = predictor.predict( img )
t = time.perf_counter() - t
print ( "predict %s s" %t )
cv.imshow( "prediction", labels )
cv.waitKey( 0 )
![Mixture of Gaussians](https://i.stack.imgur.com/OXn51.png)