Измерение шайбы с помощью DIPlib - PullRequest
1 голос
/ 23 апреля 2020

Я использую opencv для измерения размеров шайб в целях сортировки. Но OpenCV недостаточно точен, поэтому я хочу перенести свой код из OpenCV в DIPlib. С помощью приведенного ниже кода я измеряю следующие критерии:

внешний диаметр, диаметр отверстия, эксцентриситет, заусенец

Как мне найти эти критерии с помощью DIPlib?

Это пример image:

enter image description here

Это код OpenCV, который измеряет вышеуказанные критерии:

blur(openCvImage, openCvImage, Size(3, 3));
threshold(openCvImage, thresh_output, parameter.thresh1, parameter.thresh1 * 3, THRESH_BINARY_INV);
findContours(thresh_output, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE);
cvtColor(openCvImage, openCvImage, COLOR_GRAY2RGB);

if (contours.size() == 2)
{
    vector<Moments> mu(contours.size());//contours
    vector<Point2f> mc(contours.size());//centroid
    vector<RotatedRect> minRect(contours.size());//min rectangle

    // draw contours and draw point centers of inner and outter circles and find inner and outer perimeter
    for (int i = 0; i < contours.size(); i++)
    {
        mu[i] = moments(contours[i], false);// get the moments
        mc[i] = Point2f(mu[i].m10 / mu[i].m00, mu[i].m01 / mu[i].m00);// get the centroid of figures.
        drawContours(openCvImage, contours, i, color, 2, 8, hierarchy, 0, Point());//draw contours
        circle(openCvImage, mc[i], 2, color, -1, 8, 0);//Draw point centroid of the circles
        minRect[i] = minAreaRect(contours[i]);//find min fitted rectangle to circles
        diameter[i] = arcLength(contours[i], 1) / (M_PI);//find diameter of the washer and washer hole(R=perimeter/pi)
        if (minRect[i].size.width < minRect[i].size.height) { swap(minRect[i].size.width, minRect[i].size.height); }//sort the values
        //a=shortest diameter b=longest diameter  sqrt(b2-a2)/b if b=a equation=0 if a goes to 0 equation=1 eliptic is between 0 an 1 (*100)
        eliptic[i] = ((sqrt(pow((minRect[i].size.width / 2), 2) - pow((minRect[i].size.height / 2), 2))) / (minRect[i].size.width / 2)) * 100;
    }
    burrdistance = pointPolygonTest(contours[0], mc[0], 1);//find the distance from centroid to burr
    eccentricity = norm(mc[0] - mc[1]);//find the distance between centroid of the circles
    circle(openCvImage, mc[0], burrdistance, (0, 255, 0), 1, 8, 0);//making circle from centroid to burr
    burrpercentage = ((diameter[0] / 2) - burrdistance) / (diameter[0] / 2) * 100;//(radius-burrdistance)/radius)
}

1 Ответ

1 голос
/ 23 апреля 2020

Этот вопрос относится к этому другому .

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

  1. Фон слишком яркий. Эти пиксели насыщены. Когда ПЗС имеет насыщенный пиксель, соседние пиксели выдают более высокие значения, чем должны. Этот эффект называется blooming . Это заставит ваш объект казаться меньше, чем он есть. Либо уменьшите интенсивность света, либо сократите время экспозиции, либо закройте диафрагму, пока пиксели фона не станут чуть ниже их максимального значения.

  2. Похоже, я вижу одну сторону объект (промежуточная серая область вверху рисунка). Если объект на самом деле не имеет конусообразного края, это, вероятно, связано с тем, что объект не центрирован в поле зрения. Использование более длинного фокуса может смягчить некоторые из них. В результате мы не будем знать, какое ребро измерять, включает ли объект серую область или нет?

Как только мы дойдем до измерения, мы можем повторить некоторые обработки, которую вы выполняете в OpenCV с помощью DIPlib, отслеживая контур как многоугольник и выполняя измерения многоугольника. Это не обязательно даст лучшие результаты, чем вы получаете с OpenCV, за исключением измерения периметра (которое OpenCV всегда переоценивает). В существующем коде вы могли бы рассчитать диаметр на основе площади, а не периметра для гораздо более точного результата.

Также измерение minRect является неточным, поскольку на него влияют отдельные пиксели, некоторые шум внесет уклон. Вместо этого поместите эллипс в многоугольник и используйте диаметры эллипса в своей мере elliptic.

Аналогично, измерение burrdistance дает расстояние от центра тяжести до ближайшего пикселя в контуре, который легко поддается шуму и, следовательно, смещен. burrpercentage зависит от этого значения и, следовательно, также возможно смещено. Я не уверен, что эти измерения должны обеспечить, поэтому не буду предлагать альтернативу. Но рассмотрите меру эллипса для количественной оценки шероховатости контура (она количественно определяет дисперсию расстояния до эллипса наилучшего соответствия).

Если измерения многоугольника недостаточно точны, вы Можно добавить информацию серой шкалы в изображение, чтобы получить более точное измерение. Это код DIPlib, который делает это:

#include "diplib.h"
#include "diplib/simple_file_io.h"
#include "diplib/mapping.h"
#include "diplib/binary.h"
#include "diplib/morphology.h"
#include "diplib/measurement.h"

int main() {
   double pixelSize = 0.001; // millimeters per pixel. This is just an example. You need to calibrate your image.
   dip::Image input = dip::ImageRead( "/Users/cris/tmp/washer.jpg" );
   input.SetPixelSize( pixelSize * dip::Units::Millimeter() );
   double low = 120;
   double high = 170; // adjust these values according to illumination
   input = dip::ErfClip( input, low, high, "both" ); // This removes noise and edge variability.
   input = ( input - low ) / ( high - low ); // normalize

   // Create masks images that separate hole from object, so we can measure them independently:
   dip::Image hole = input > 0.5;
   hole = dip::BinaryAreaOpening( dip::EdgeObjectsRemove( hole ), 1000 );
   dip::Dilation( hole, hole, { 10 } ); // Add a margin so we include the full edge
   dip::Image washer = ( input <= 0.5 ) | hole;
   dip::Dilation( washer, washer, { 10 } ); // Add a margin so we include the full edge

   // Measure hole
   dip::MeasurementTool measurementTool;
   dip::Image holeLabel = dip::Convert( hole, dip::DT_UINT8 ); // instead of labeling, all regions have object ID = 1
   auto holeMsr = measurementTool.Measure( holeLabel, input, { "Mass", "Gravity", "GreyDimensionsEllipsoid" } );
   double holeArea = holeMsr[ 1 ][ "Mass" ][ 0 ] * pixelSize * pixelSize;
   double holeDiameter = 2 * std::sqrt( holeArea / dip::pi );
   double holeCentroidX = holeMsr[ 1 ][ "Gravity" ][ 0 ];
   double holeCentroidY = holeMsr[ 1 ][ "Gravity" ][ 1 ];
   double holeMajorAxis = holeMsr[ 1 ][ "GreyDimensionsEllipsoid" ][ 0 ];
   double holeMinorAxis = holeMsr[ 1 ][ "GreyDimensionsEllipsoid" ][ 1 ];

   // Measure washer
   input = 1.0 - input;
   input.At( hole ) = 1.0;
   washer.Convert( dip::DT_UINT8 ); // instead of labeling, all regions have object ID = 1
   auto washerMsr = measurementTool.Measure( washer, input, { "Mass", "Gravity", "GreyDimensionsEllipsoid" } );
   double washerArea = washerMsr[ 1 ][ "Mass" ][ 0 ] * pixelSize * pixelSize;
   double washerDiameter = 2 * std::sqrt( washerArea / dip::pi );
   double washerCentroidX = washerMsr[ 1 ][ "Gravity" ][ 0 ];
   double washerCentroidY = washerMsr[ 1 ][ "Gravity" ][ 1 ];
   double washerMajorAxis = washerMsr[ 1 ][ "GreyDimensionsEllipsoid" ][ 0 ];
   double washerMinorAxis = washerMsr[ 1 ][ "GreyDimensionsEllipsoid" ][ 1 ];

   // Output measurements
   std::cout << "washer area = " << washerArea << " mm², diameter = " << washerDiameter
             << " mm, major diameter = " << washerMajorAxis << " mm, minor diameter = " << washerMinorAxis
             << " mm, centroid = (" << washerCentroidX << ", " << washerCentroidY << ") mm\n";
   std::cout << "hole area = " << holeArea << " mm², diameter = " << holeDiameter
             << " mm, major diameter = " << holeMajorAxis << " mm, minor diameter = " << holeMinorAxis
             << " mm, centroid = (" << holeCentroidX << ", " << holeCentroidY << ") mm\n";
}

Обратите внимание, что на точность (смещение) приведенного выше кода влияет область серого края. Диаметр измеряется на основе площади, а главный и вспомогательный диаметры эллипса измеряются на основе подгонки эллипса к форме.

Это вывод:

washer area = 0.568496 mm², diameter = 0.850783 mm, major diameter = 0.853937 mm, minor diameter = 0.84772 mm, centroid = (0.737456, 0.474875) mm
hole area = 0.0417281 mm², diameter = 0.230499 mm, major diameter = 0.230843 mm, minor diameter = 0.230167 mm, centroid = (0.73646, 0.470806) mm

Если вы надеваете Если вы не хотите использовать измерения серого, вы можете сделать то же самое, что и выше, но использовать эквивалентные двоичные меры: "Размер", "Центр" и "DimensionsEllipsoid". «Размер» учитывает размер пикселя, поэтому нет необходимости выполнять умножение, которое нам нужно было сделать с «Масса». В этом случае вам не нужно передавать полутоновые изображения в measurementTool.Measure, и вам не следует применять dip::Dilation к маскам (так как вы будете измерять сами маски).

...