使用 DIPlib 进行垫圈测量
Washer measurement with DIPlib
我正在使用 opencv 测量垫圈尺寸以进行分类。但是 OpenCV 不够精确,这就是我想将我的代码从 OpenCV 迁移到 DIPlib 的原因。使用以下代码,我正在衡量以下标准:
外径、孔径、偏心、毛刺
如何使用 DIPlib 找到这些标准?
这是示例图片:
这是衡量上述标准的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)
}
此问题与有关。
在开始处理图像之前,您应该尝试做两件事来改进您的设置:
背景太亮了。那些像素是饱和的。当 CCD 具有饱和像素时,附近的像素会产生比它们应有的值更高的值。这种效果称为 blooming。它会使您的对象看起来比实际小。要么降低光强度,要么缩短曝光时间,要么关闭光圈,直到背景像素刚好低于最大值。
看起来我可以看到物体的一侧(图片顶部的中间灰色区域)。除非物体实际上在那里有一个锥形边缘,否则这很可能是因为物体没有在视野中居中。使用更长的焦点可能会减轻一些这种情况。结果是我们将不知道要测量哪条边,对象是否包含灰色区域?
一旦我们开始测量,我们就可以通过将轮廓描绘为多边形并进行多边形测量来复制您在 OpenCV 中使用 DIPlib 进行的一些处理。这不一定会产生比 OpenCV 更好的结果,除了周长测量(OpenCV 总是高估)。您可以在现有代码中根据面积而不是周长计算直径以获得更精确的结果。
另外minRect
测量不精确,因为它受到个别像素的影响,一些噪声会引入偏差。相反,将椭圆拟合到多边形,并在 elliptic
测量中使用椭圆的直径。
同样,burrdistance
测量给出了质心到轮廓中最近像素的距离,这很容易受到噪声的影响,因此有偏差。 burrpercentage
取决于该值,因此也可能有偏差。我不确定这些测量值应该提供什么,所以不会建议替代方案。但是考虑 ellipse variance 度量来量化轮廓的粗糙度(它量化到最佳拟合椭圆的距离的方差)。
如果多边形测量不够精确,您可以在图像中添加灰度信息以获得更精确的测量。这是这样做的 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
如果您不想使用灰度值测量,您可以像上面那样做,但使用等效的二进制测量:"Size"、"Center" 和 "DimensionsEllipsoid"。 "Size" 考虑了像素大小,因此无需进行我们需要对 "Mass" 进行的乘法运算。在这种情况下,您不需要将灰度图像传递给 measurementTool.Measure
,也不应将 dip::Dilation
应用于蒙版(因为您将自己测量蒙版)。
我正在使用 opencv 测量垫圈尺寸以进行分类。但是 OpenCV 不够精确,这就是我想将我的代码从 OpenCV 迁移到 DIPlib 的原因。使用以下代码,我正在衡量以下标准:
外径、孔径、偏心、毛刺
如何使用 DIPlib 找到这些标准?
这是示例图片:
这是衡量上述标准的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)
}
此问题与
在开始处理图像之前,您应该尝试做两件事来改进您的设置:
背景太亮了。那些像素是饱和的。当 CCD 具有饱和像素时,附近的像素会产生比它们应有的值更高的值。这种效果称为 blooming。它会使您的对象看起来比实际小。要么降低光强度,要么缩短曝光时间,要么关闭光圈,直到背景像素刚好低于最大值。
看起来我可以看到物体的一侧(图片顶部的中间灰色区域)。除非物体实际上在那里有一个锥形边缘,否则这很可能是因为物体没有在视野中居中。使用更长的焦点可能会减轻一些这种情况。结果是我们将不知道要测量哪条边,对象是否包含灰色区域?
一旦我们开始测量,我们就可以通过将轮廓描绘为多边形并进行多边形测量来复制您在 OpenCV 中使用 DIPlib 进行的一些处理。这不一定会产生比 OpenCV 更好的结果,除了周长测量(OpenCV 总是高估)。您可以在现有代码中根据面积而不是周长计算直径以获得更精确的结果。
另外minRect
测量不精确,因为它受到个别像素的影响,一些噪声会引入偏差。相反,将椭圆拟合到多边形,并在 elliptic
测量中使用椭圆的直径。
同样,burrdistance
测量给出了质心到轮廓中最近像素的距离,这很容易受到噪声的影响,因此有偏差。 burrpercentage
取决于该值,因此也可能有偏差。我不确定这些测量值应该提供什么,所以不会建议替代方案。但是考虑 ellipse variance 度量来量化轮廓的粗糙度(它量化到最佳拟合椭圆的距离的方差)。
如果多边形测量不够精确,您可以在图像中添加灰度信息以获得更精确的测量。这是这样做的 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
如果您不想使用灰度值测量,您可以像上面那样做,但使用等效的二进制测量:"Size"、"Center" 和 "DimensionsEllipsoid"。 "Size" 考虑了像素大小,因此无需进行我们需要对 "Mass" 进行的乘法运算。在这种情况下,您不需要将灰度图像传递给 measurementTool.Measure
,也不应将 dip::Dilation
应用于蒙版(因为您将自己测量蒙版)。