Этот вопрос относится к этому другому .
Есть две вещи, которые вы должны попытаться сделать, чтобы улучшить свои настройки, прежде чем вы даже начнете обрабатывать изображения:
Фон слишком яркий. Эти пиксели насыщены. Когда ПЗС имеет насыщенный пиксель, соседние пиксели выдают более высокие значения, чем должны. Этот эффект называется blooming . Это заставит ваш объект казаться меньше, чем он есть. Либо уменьшите интенсивность света, либо сократите время экспозиции, либо закройте диафрагму, пока пиксели фона не станут чуть ниже их максимального значения.
Похоже, я вижу одну сторону объект (промежуточная серая область вверху рисунка). Если объект на самом деле не имеет конусообразного края, это, вероятно, связано с тем, что объект не центрирован в поле зрения. Использование более длинного фокуса может смягчить некоторые из них. В результате мы не будем знать, какое ребро измерять, включает ли объект серую область или нет?
Как только мы дойдем до измерения, мы можем повторить некоторые обработки, которую вы выполняете в 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
к маскам (так как вы будете измерять сами маски).