Определение веса самосвала в движении нетривиальная задача, в которой технические ограничения, тип подвески, принцип действия датчиков и качество телематической цепочки играют ключевую роль. Датчики нагрузки фиксируют показатели с высоким уровнем шума: дорожные неровности, динамика подвески и особенности конструкции кузова приводят к тому, что каждое измерение — лишь приближение к реальному весу. Для корректного расчёта требуется система, которая не только усредняет данные, но и адаптируется к качеству входных сигналов. Перед использованием алгоритмов обработки данных необходимо понимать конструкцию техники, доступные методы взвешивания и природу шума, возникающего в процессе измерений.
Три основных типа карьерных самосвалов в России
В российской добывающей отрасли применяются три доминирующих класса машин, каждый из которых имеет собственные особенности измерения веса:
Классические карьерные самосвалы с жёсткой рамой, такие как БелАЗ 7555–7513 и CAT 777–793, оснащаются гидропневматической подвеской с датчиками давления. Эти датчики служат для оценки веса полезной нагрузки через измерение давления в подвеске. Однако их данные сильно зависят от темпа движения и амплитуды раскачки кузова, что создает шумы и неточности при динамическом движении. В качестве инженерного дополнения к измерению веса для таких моделей важно применять алгоритмы фильтрации и компенсации динамических колебаний, а также дополнять данные от датчиков давления весовыми сенсорами, установленными на шасси, для повышения точности оценки нагрузки и снижения влияния факторов движения. Для БелАЗ и CAT, реализованы высокоточные системы взвешивания с погрешностью в диапазоне до ±0,1–1%. Однако, из-за сильных вибраций и динамических пиков в процессе работы, количество данных с аномальными колебаниями составляет примерно 20–35%. Благодаря строгой конструкции и стабильной гидравлической и электронной схемам, такие системы обеспечивают относительно меньшую дисперсию и более предсказуемую точность, особенно при использовании фильтров и компенсационных алгоритмов.
Самосвалы с шарнирно-сочлененной рамой, например Volvo A30–A60 и CAT 730–745, используют гидравлическую подвеску и шарнирный узел. Вес измеряется с помощью датчиков давления в гидроцилиндрах подвески, а в некоторых случаях применяются тензодатчики, установленные на раме. Основной проблемой при определении веса таких машин являются резкие пиковые нагрузки в районе сочленения и сильные выбросы сигналов при разгоне и торможении. Колебания сигнала достигают 30–45%, что заметно выше, чем у жестких рам, и обусловлено интенсивными вертикальными и боковыми колебаниями в сочленениях. В этих условиях уровень зашумленности составляет значительные 30–45%, что связано с сильными пиковыми нагрузками и резкими изменениями давления в гидроцилиндрах. Для повышения точности необходимо использовать расширенные алгоритмы фильтрации, такие как калмановские фильтры или адаптивное сглаживание, а также комбинировать данные с различных датчиков.
Дорожные самосвалы модельного ряда КамАЗ, Scania, MAN отличаются пневматической подвеской или рессорной подвеской. Датчики давления устанавливаются в пневмоподушках, имеются также тензодатчики на мостах. Эти модели демонстрируют наибольшие уровни шумов, до 50–70%. Это связано с высокой зависимостью от дорожных условий, неровностей, вибрации и динамики движения. В этих условиях погрешности увеличиваются из-за поисковых воздействий на датчики давления в пневмоподушках и мостовых тензодатчиков, что приводит к большему количеству ошибок в измерениях. Поэтому, для повышения точности измерения веса рекомендуется использовать дополнительные системы калибровки, адаптивных фильтров и учета профиля дорожного покрытия, что позволяет снизить влияние внешних факторов и обеспечить стабильность данных при разных условиях эксплуатации.
Таким образом, для всех трёх групп карьерных и дорожных самосвалов необходим комплексный подход к измерению веса, включающий аппаратную избыточность датчиков (интеграция нескольких типов датчиков гидравлических и тензодатчиков) и программное сглаживание и адаптацию данных в реальном времени с учётом динамики движения и условий эксплуатации.
Применяемые типы датчиков для трёх групп карьерных и дорожных самосвалов
Группа самосвалов |
Тип датчика |
Место установки |
Плюсы |
Минусы |
Корректность данных (ориентировочно) |
|---|---|---|---|---|---|
Классические карьерные, жёсткая рама |
Датчики подвески (гидравлические/гидропневматические) |
В подвеске (амортизаторах, цилиндрах подвески) |
Простота установки; достаточная точность в статике |
Сильная зависимость от темпа движения и вибраций; шум на 50–200% при 25–45 км/ч; влияние раскачки и неравномерности нагрузки |
60–80% |
Самосвалы с шарнирно-сочлененной рамой |
Датчики давления в гидроцилиндрах подвески; тензодатчики рамы |
На гидроцилиндрах подвески, на раме |
Высокая точность; меньше влияние подвески |
Дорогое обслуживание; чувствительность к температуре и деформациям рамы; требовательность к монтажу |
70–90% |
Дорожные самосвалы (пневмо или рессорная подвеска) |
Датчики давления в пневмоподушках; тензодатчики мостов |
В пневматических подушках и на мостах |
Широкое распространение; позволяет отслеживать нагрузку на оси |
Запаздывание реакции подушек; высокие нелинейности; сильный шум на кочках; зависимость от температуры и профиля дороги |
40–65% |
Гидравлические и гидропневматические датчики, применяемые у карьерных самосвалов, обеспечивают приемлемую точность в статике, но при динамичном движении подвергаются значительным шумам и искажениям из-за вибрации и колебаний транспорта. Для повышения точности крайне важны алгоритмы обработки сигналов с компенсацией динамики.
Тензодатчики рамы и мостов в сочленённых самосвалах обладают улучшенной чувствительностью и меньшей зависимостью от внешних воздействий подвески. Однако их высокая стоимость и сложность монтажа, а также необходимость тщательной калибровки при температурных изменениях усложняют их применение.
Для дорожных самосвалов основной вызов — шум и запаздывания в реакции пневмоподушек, что затрудняет точное определение нагрузки в условиях плохого дорожного покрытия. Тензодатчики мостов дополняют систему, повышая качество данных, но требуют регулярной проверки и настройки.
Комплексное использование гидравлических/гидропневматических датчиков с тензодатчиками и развитая система фильтрации и калибровки сигналов — ключ к повышению точности измерения веса в различных типах самосвалов. При проектировании и эксплуатации весовых систем необходимо тщательно учитывать особенности монтажа, условия работы и особенности обработки данных для минимизации погрешностей и повышения надежности измерений.
Методологический подход к тарировке и расчёту перевозимого веса самосвала
Общий методологический подход к тарировке и расчёту перевозимого веса на борту карьерных и дорожных самосвалов включает несколько взаимосвязанных этапов и операций, основанных на работе с сырыми данными с датчиков и их последующей обработке для получения корректного веса груза.
Целью методологии является обеспечение единого, предсказуемого и технически обоснованного процесса определения веса груза на борту самосвала, исходя из следующих принципов:
независимость от типа машины, датчиков и телематики;
воспроизводимость результатов;
корректная работа в динамических режимах;
устойчивость к шуму, выбросам и ошибкам оборудования;
универсальность для производственного учёта и аналитики.
Три уровня проблемы: физический, системный и аналитический
Для получения достоверного веса нужно работать со всеми звеньями цепочки:
Уровень |
Что происходит |
Типичные проблемы |
|---|---|---|
Физический уровень |
Работа подвески, датчиков, изменение нагрузок |
Неровности дороги, крены, динамическая нагрузка |
Системный уровень |
Аналог → цифра, передача данных, телематика |
Сглаживание, задержки, потеря пакетов, квантование |
Аналитический уровень |
Фильтрация и расчёт веса |
Шум, вибрации, ошибки разграничения стадий |
Методология должна охватывать все три уровня.
Тарировка: фундамент расчёта веса
Тарировка — это процесс определения зависимости «измеренный параметр → фактический вес». Для карьерных и дорожных самосвалов обычно используются следующие методы тарировки:
Базовая статическая тарировка
Проводится на стационарных весах:
-
Порожний самосвал:
фиксируется фактический вес;
проверяется стабильность датчиков.
-
Гружёный самосвал:
выполняется 3–7 прогонов с разным грузом;
строится калибровочная кривая.
-
Определяется математическая модель:
линейная (давление подвески → вес),
полиномиальная,
регрессионная модель.
Вероятность корректной калибровки: 90–95%.
Динамическая тарировка
Необходима, если самосвал работает только на неровных поверхностях.
Метод:
выполняются серии замеров при разных скоростях (5–30 км/ч);
вычисляется поправочный коэффициент динамической нагрузки;
формируются фильтры для подавления колебаний.
Погрешность снижается на 20–30%.
Регулярная ретарировка
Требуется каждые 3–6 месяцев:
деградация подвески,
падение давления в подушках,
изменение характеристик рамы,
старение датчиков.
Без ретарировки погрешность растёт на 10–25% ежегодно.
Методология расчёта перевозимого веса: общая схема
Методология включает 6 этапов. Она включает лучшие мировые практики для Caterpillar OWT, Komatsu VHMS, Volvo MATRIS.
Этап 1. Сбор данных
Данные получаются с трёх каналов:
Источник |
Что даёт |
Надёжность |
|---|---|---|
Датчики подвески / тензодатчики |
«Сырой» вес |
Средняя |
Бортовой компьютер / работомер |
Усреднённый вес |
Средняя–высокая |
Трекер |
Периодические значения |
Низкая–средняя |
Система должна использовать максимально возможную частоту и сырые значения при доступности.
Этап 2. Классификация стадии движения
Определяются ключевые режимы:
Стадия |
Как распознать |
Требования к алгоритму |
|---|---|---|
Порожний |
вес ≈ вес пустого самосвала ± 5–10% |
строгая валидация |
Погрузка |
рост веса, низкая скорость |
исключение из расчётов |
Движение с грузом |
вес > вес пустого самосвала + пороговый вес |
адаптивная фильтрация |
Разгрузка |
резкое падение веса |
исключение из расчётов(*) |
Неопределённо |
нет стабильности |
автоопределение стадии |
(*)Исключение измерений веса в стадии разгрузки необходимо по нескольким причинам, связанным с физической динамикой процесса и спецификой работы измерительных систем на борту самосвалов:
Почему важно исключать измерения при разгрузке
Резкие искажения сигнала
В момент разгрузки происходит резкое и быстрое падение веса в кузове — груз выгружается, что приводит к большим динамическим колебаниям и шума в сигнале датчиков. Из-за инертности и задержек в реакции измерительного оборудования данные становятся нестабильными и могут содержать сильные погрешности.Нестабильность распределения нагрузки
Разгрузка вызывает неравномерное распределение массы в кузове и перемещения груза, что приводит к временным пиковым нагрузкам и скачкам в датчиках давления и тензодатчиках рамы. Это создает некорректные и непредсказуемые показания, которые не отражают реальный вес перевозимого груза.Непредсказуемость времени процесса
Продолжительность разгрузки непостоянна и может варьироваться в зависимости от типа разгрузочного оборудования, условий площадки и навыков оператора. Поэтому сложно применять корректирующие алгоритмы фильтрации, а включение этих периодов в расчёт приведет к статистическому искажению итоговых данных.-
Задача расчёта — именно вес перевозимого груза
Цель системы взвешивания — определить полезную нагрузку, находящуюся на самосвале в процессе транспортировки. Данные стадии разгрузки не соответствуют этому условию и могут ухудшить качество итоговых результатов анализа.Исключать или значительно ограничивать использование данных измерения веса в момент разгрузки следует обязательно для повышения точности и надёжности весовой информации. Это отражено в методологии классификации стадий движения, где этап разгрузки выделяется как особый режим с исключением данных из основного расчёта перевозимого веса. Для корректной работы системы важна точная классификация стадии движения и соответствующая адаптивная логика обработки сигналов.
Таким образом, исключение данных разгрузки — это инженерное требование, направленное на повышение качества и достоверности контролируемой информации о весе.
Этап 3. Очистка и валидация данных
Удаляются:
физически невозможные значения (ниже нуля / выше макс. грузоподъёмности × 1.2);
одиночные всплески;
шумовые периоды при резких манёврах.
На практике отсеивается 20–40% всех точек.
Этап 4. Выбор метода расчёта веса
Сценарий |
Метод |
Причина |
|---|---|---|
Порожний вес |
Медиана |
Устойчивость к выбросам, данные сравнительно стабильны |
Мало данных (<10) |
Медиана |
Минимизация ошибки при малом объёме информации |
Высокий шум (Коэффициент вариации >15%) |
Усечённое среднее |
Удаляет 10–20% экстремальных значений для повышения стабильности данных |
Стабильные условия |
Среднее арифметическое |
Максимальная точность при низком уровне шумов |
Методы расчёта веса, используемые на борту самосвалов, выбираются с учётом характера данных, их объёма, уровня шума и наличия выбросов, чтобы обеспечить максимальную точность и надёжность результатов. Ниже объясняется почему используются конкретные методы, приведены соответствующие формулы и обоснования.
1. Медиана
Используется на этапах:
Порожний самосвал (почти постоянный вес)
Мало данных (<10 точек)
Общая минимизация ошибки при выбросах
Почему?
Медиана устойчива к выбросам и экстремальным значениям: она выбирает центральное значение в упорядоченном наборе данных, игнорируя влияние сильных искажений.
Формула медианы:
Пусть данные x1,x2,...,xn упорядочены по возрастанию. Тогда медиана M равна

Медиана особенно эффективна, когда количество данных невелико или когда данные содержат шумы и выбросы.
2. Усечённое среднее (Trimmed Mean)
Применяется при высоком шуме (коэффициент вариации CV > 15%) и большом числе данных.
Почему?
Усечённое среднее устраняет экстремальные значения из начала и конца упорядоченного ряда данных, что снижает влияние выбросов, сохраняя при этом информацию о тенденции выборки.
Формула усечённого среднего:
Пусть от общего объема данных nnn отсечено по k на каждом из краёв (обычно 10-20% суммарно), тогда:

Это позволяет снизить эффект редких выбросов, улучшая стабильность вычислений в условиях помех и шумов.
3. Среднее арифметическое
Используется в условиях стабильных и малошумных данных, когда много наблюдений.
Почему?
Среднее арифметическое даёт максимальную точность при нормальном распределении данных и низком уровне шумов.
Формула среднего арифметического:

Здесь все значения учитываются одинаково, что даёт лучший способ оценки средней тенденции при стабильных данных.
4. Обоснование выбора методов по сценарию
Сценарий |
Метод |
Причина выбора |
|---|---|---|
Порожний вес |
Медиана |
Устойчивость к выбросам, данные сравнительно стабильны |
Мало данных (<10) |
Медиана |
Минимизация ошибки при малом объёме информации |
Высокий шум (>15%) |
Усечённое среднее |
Удаление экстремальных шумных значений для повышения стабильности |
Стабильные условия |
Среднее арифметическое |
Максимальная точность при низком уровне шумов |
Дополнительный параметр — Коэффициент вариации (CV)
Чтобы решать, когда используется усечённое среднее, применяется коэффициент вариации, показывающий относительную дисперсию:

Если CV > 15%, данные считаются шумными, и тогда предпочтительнее использовать усечённое среднее для минимизации влияния выбросов.
Таким образом, выбор метода расчёта веса ориентирован на баланс между устойчивостью к шумам и точностью, в зависимости от качества и объёма данных. Применение медианы и усечённого среднего повышает надёжность вычислений в динамичных и нестабильных условиях, а среднее арифметическое — при стабильных и чистых данных.
Это позволяет наилучшим образом использовать измерения веса на борту самосвала для оперативного и аналитического контроля.
Этап 5. Расчёт фактического веса груза
Груз определяется как:
Вес груза = Общий вес самосвала - Вес пустого самосвала
При этом:
если значение отрицательное → устанавливается 0;
при низком уровне достоверности результат < 0.6 запись расчета помечается как "требует проверки".
Примерные вероятности корректности расчёта:
Стабильный рельеф: 90–95%
Средний рельеф: 85–90%
Сложный профиль трассы: 75–85%
Этап 6. Оценка достоверности результата
В итоговую доверенность входят:
Фактор |
Вес |
|---|---|
Коэффициент вариации CV |
Высокий |
Количество точек данных |
Средний |
Наличие выбросов |
Высокий |
Отклонение от ожидаемого веса пустого самосвала |
Высокий |
Стабильность движений |
Средний |
Итоговый универсальный подход (кратко)
Тарировка (статическая + динамическая).
Определение стадии движения (порожний / загрузка / движение / разгрузка).
Очистка и сертификация данных (фильтрация выбросов, отсечение шумов).
Адаптивный выбор метода расчёта (медиана, среднее, усеченное среднее).
Расчёт веса груза через разницу с весом пустого самосвала.
Оценка достоверности и формирование комментариев расчета.
Регулярная ретарировка каждые 3–6 месяцев.
Методология обеспечивает прозрачность, устойчивость и инженерную предсказуемость, независимо от типа самосвала и качества оборудования.
Код C# как пример расчета
Для простоты восприятия "магические числа" оставлены в коде :)
/// <summary>
/// Методы расчёта веса для самосвала в зависимости от качества и состояния данных
/// </summary>
public enum CalculationMethod
{
/// <summary>Автоматический выбор метода на основе данных</summary>
Auto,
/// <summary>Усечённое среднее: исключение 10-20% экстремальных значений</summary>
TrimmedMean,
/// <summary>Медиана: устойчива к выбросам, используется при малом объёме данных</summary>
Median,
/// <summary>Среднее арифметическое: максимальная точность в стабильных условиях</summary>
Average
}
/// <summary>
/// Результат вычисления веса для заданной выборки данных с датчиков самосвала
/// </summary>
public class WeightCalculationResult
{
/// <summary>Масса перевозимого груза (разница между полной массой и массой пустого самосвала)</summary>
public double CargoWeight { get; set; }
/// <summary>Общий измеренный вес самосвала (полная масса с грузом)</summary>
public double TotalWeight { get; set; }
/// <summary>Коэффициент доверия к результату (от 0 до 1)</summary>
public double Confidence { get; set; }
/// <summary>Использованный метод расчёта веса</summary>
public CalculationMethod UsedMethod { get; set; }
/// <summary>Количество использованных точек данных</summary>
public int DataPointsCount { get; set; }
/// <summary>Стандартное отклонение значений выборки</summary>
public double StandardDeviation { get; set; }
/// <summary>Качественная оценка данных (ошибки, предупреждения)</summary>
public string QualityAssessment { get; set; } = string.Empty;
}
/// <summary>
/// Класс, реализующий методы расчёта веса с учётом стадии движения самосвала
/// </summary>
public static class WeightCalculator
{
private static readonly WeightCalculatorConfig DefaultConfig = new();
/// <summary>
/// Основной метод вычисления веса полезной нагрузки на основе данных с датчиков и стадии движения
/// </summary>
/// <param name="sensorRecords">Набор измерений веса (например, давление в подвеске, тензометр)</param>
/// <param name="stage">Текущая стадия движения самосвала (Порожний, Груженый, Неопределённая)</param>
/// <param name="emptyTruckWeight">Вес пустого самосвала (ключевой параметр для расчёта груза)</param>
/// <param name="config">Конфигурация параметров расчёта</param>
/// <returns>Результат расчёта (масса груза, общий вес, качество, доверие)</returns>
public static WeightCalculationResult CalculateCargoWeight(
IEnumerable<SensorRecord> sensorRecords,
DumpTruckStateEnum stage,
double emptyTruckWeight = 0,
WeightCalculatorConfig? config = null)
{
config ??= DefaultConfig;
// Валидация входных данных
bool shouldCheckEmptyWeight = false; // Можно расширить логику валидации
var validationResult = ValidateInput(sensorRecords, emptyTruckWeight, stage, shouldCheckEmptyWeight);
if (!validationResult.IsValid)
{
return new WeightCalculationResult
{
CargoWeight = 0,
TotalWeight = 0,
Confidence = 0,
UsedMethod = CalculationMethod.Auto,
DataPointsCount = 0,
StandardDeviation = 0,
QualityAssessment = validationResult.ErrorMessage
};
}
// Фильтруем физически невозможные значения и нулевые
var values = sensorRecords!
.Where(r => r != null)
.Select(r => r.Value)
.Where(v => IsPhysicallyPossible(v, emptyTruckWeight, stage, config))
.ToList();
// Выбор метода расчёта на основе стадии движения
return stage switch
{
DumpTruckStateEnum.ReturningEmpty => CalculateForEmptyStage(values, emptyTruckWeight, config),
DumpTruckStateEnum.HaulingLoaded => CalculateForLoadedStage(values, emptyTruckWeight, config),
_ => CalculateForUnknownStage(values, emptyTruckWeight, config)
};
}
/// <summary>
/// Расчёт для этапа с порожним самосвалом (стабильен, вес близок к emptyTruckWeight)
/// Используется медиана для устойчивости к выбросам
/// </summary>
private static WeightCalculationResult CalculateForEmptyStage(List<double> values, double emptyTruckWeight, WeightCalculatorConfig config)
{
var result = new WeightCalculationResult
{
DataPointsCount = values.Count,
StandardDeviation = CalculateStandardDeviation(values)
};
double median = CalculateMedian(values);
// Проверка отклонения от ожидаемого веса порожнего самосвала
double expectedWeight = emptyTruckWeight;
double weightDifference = Math.Abs(median - expectedWeight);
double maxAllowedDifference = config.MaxEmptyWeightDeviation * expectedWeight;
if (weightDifference > maxAllowedDifference)
{
result.Confidence = CalculateConfidence(values, 0.3, config); // Низкая достоверность
result.QualityAssessment = "Вес порожнего самосвала значительно отличается от ожидаемого";
}
else
{
result.Confidence = CalculateConfidence(values, 0.8, config); // Высокая достоверность
result.QualityAssessment = "Данные соответствуют ожиданиям для порожнего самосвала";
}
result.TotalWeight = median;
result.CargoWeight = Math.Max(0, median - emptyTruckWeight);
result.UsedMethod = CalculationMethod.Median;
if (double.IsNaN(result.CargoWeight) || double.IsInfinity(result.CargoWeight))
{
result.CargoWeight = 0;
result.Confidence = 0;
result.QualityAssessment += " | Расчет прерван из-за некорректных числовых значений";
}
return result;
}
/// <summary>
/// Расчёт для этапа с груженым самосвалом (шумные данные, выбор метода зависит от качества данных)
/// </summary>
private static WeightCalculationResult CalculateForLoadedStage(List<double> values, double emptyTruckWeight, WeightCalculatorConfig config)
{
var result = new WeightCalculationResult
{
DataPointsCount = values.Count,
StandardDeviation = CalculateStandardDeviation(values)
};
double std = result.StandardDeviation;
double mean = values.Average();
double cv = mean > config.MinMeanValueForCV ? std / mean : double.MaxValue; // Коэффициент вариации
if (values.Count < config.MinimumDataPointsForReliableCalculation)
{
// Мало данных - применяем медиану для минимизации ошибки
double median = CalculateMedian(values);
result.TotalWeight = median;
result.CargoWeight = Math.Max(0, median - emptyTruckWeight);
result.UsedMethod = CalculationMethod.Median;
result.Confidence = CalculateConfidence(values, 0.5, config);
result.QualityAssessment = "Мало данных для надежного расчета";
}
else if (cv > config.HighNoiseThreshold)
{
// Высокий шум - применяем усечённое среднее для удаления выбросов
double trimmedMean = CalculateTrimmedMean(values, config.LoadedTrimPercentage);
result.TotalWeight = trimmedMean;
result.CargoWeight = Math.Max(0, trimmedMean - emptyTruckWeight);
result.UsedMethod = CalculationMethod.TrimmedMean;
result.Confidence = CalculateConfidence(values, 0.6, config);
result.QualityAssessment = "Применена усиленная фильтрация из-за высокого шума";
}
else
{
// Низкий шум и достаточное количество данных - используем среднее арифметическое для максимальной точности
result.TotalWeight = mean;
result.CargoWeight = Math.Max(0, mean - emptyTruckWeight);
result.UsedMethod = CalculationMethod.Average;
result.Confidence = CalculateConfidence(values, 0.9, config);
result.QualityAssessment = "Данные хорошего качества";
}
if (result.CargoWeight < config.MinExpectedPayload)
{
result.QualityAssessment += " | Рассчитанный вес груза подозрительно мал";
result.Confidence *= 0.7;
}
if (double.IsNaN(result.CargoWeight) || double.IsInfinity(result.CargoWeight))
{
result.CargoWeight = 0;
result.Confidence = 0;
result.QualityAssessment += " | Расчет прерван из-за некорректных числовых значений";
}
return result;
}
/// <summary>
/// Расчёт для неопределённого состояния - автоматическое определение стадии и рекурсивный вызов метода
/// </summary>
private static WeightCalculationResult CalculateForUnknownStage(List<double> values, double emptyTruckWeight, WeightCalculatorConfig config)
{
double median = CalculateMedian(values);
double expectedEmptyWeight = emptyTruckWeight;
double tolerance = config.EmptyWeightTolerance * expectedEmptyWeight;
DumpTruckStateEnum detectedStage = Math.Abs(median - expectedEmptyWeight) <= tolerance
? DumpTruckStateEnum.ReturningEmpty
: DumpTruckStateEnum.HaulingLoaded;
var result = detectedStage == DumpTruckStateEnum.ReturningEmpty
? CalculateForEmptyStage(values, emptyTruckWeight, config)
: CalculateForLoadedStage(values, emptyTruckWeight, config);
result.QualityAssessment += $" | Стадия определена автоматически: {detectedStage}";
return result;
}
/// <summary>
/// Проверка входных данных на корректность (наличие, возвращаемость весов, логичность)
/// </summary>
private static ValidationResult ValidateInput(
IEnumerable<SensorRecord> sensorRecords,
double emptyTruckWeight,
DumpTruckStateEnum stage,
bool checkReturningEmpty)
{
if (sensorRecords == null)
return ValidationResult.Invalid("Отсутствуют данные датчиков");
var records = sensorRecords.ToList();
if (records.Count == 0)
return ValidationResult.Invalid("Нет записей данных");
if (records.All(r => r == null))
return ValidationResult.Invalid("Все записи данных являются null");
if (emptyTruckWeight < 0)
return ValidationResult.Invalid("Вес пустого самосвала не может быть отрицательным");
if (checkReturningEmpty)
{
if (emptyTruckWeight == 0 && stage == DumpTruckStateEnum.ReturningEmpty)
return ValidationResult.Invalid("Для стадии 'порожний' должен быть указан вес пустого самосвала");
}
return ValidationResult.Valid();
}
/// <summary>
/// Проверка физической корректности измеренного веса
/// </summary>
private static bool IsPhysicallyPossible(
double weight,
double emptyTruckWeight,
DumpTruckStateEnum stage,
WeightCalculatorConfig config)
{
if (weight <= 0) return false;
double maxPossibleWeight = emptyTruckWeight + config.MaxExpectedPayload * 1.2;
if (weight > maxPossibleWeight) return false;
if (stage == DumpTruckStateEnum.ReturningEmpty)
{
double difference = Math.Abs(weight - emptyTruckWeight);
return difference <= config.MaxEmptyWeightDeviation * emptyTruckWeight;
}
return true;
}
/// <summary>
/// Расчёт коэффициента доверия с учётом шумов и объёма данных (от 0 до 1)
/// </summary>
private static double CalculateConfidence(List<double> values, double baseConfidence, WeightCalculatorConfig config)
{
if (values.Count == 0) return 0;
double std = CalculateStandardDeviation(values);
double mean = values.Average();
double effectiveMean = Math.Max(mean, config.MinMeanValueForCV);
double cv = std / effectiveMean;
double cvFactor = Math.Max(0, 1 - cv);
double dataPointsFactor = Math.Min(1, values.Count / 20.0);
double confidence = baseConfidence * cvFactor * dataPointsFactor;
return Math.Max(0, Math.Min(1, confidence));
}
/// <summary>
/// Стандартное отклонение выборки
/// </summary>
private static double CalculateStandardDeviation(List<double> values)
{
if (values.Count < 2) return 0;
double mean = values.Average();
double sumSq = values.Sum(v => Math.Pow(v - mean, 2));
return Math.Sqrt(sumSq / (values.Count - 1));
}
/// <summary>
/// Медиана выборки (устойчивая к выбросам характеристика центральной тенденции)
/// </summary>
private static double CalculateMedian(List<double> values)
{
if (values.Count == 0) return 0;
var sorted = values.OrderBy(v => v).ToList();
int count = sorted.Count;
return count % 2 == 1
? sorted[count / 2]
: (sorted[(count - 1) / 2] + sorted[count / 2]) / 2.0;
}
/// <summary>
/// Усечённое среднее — среднее после удаления экстремальных значений с двух концов (например, по 5-10%)
/// </summary>
private static double CalculateTrimmedMean(List<double> values, double trimPercentage)
{
if (values == null) throw new ArgumentNullException(nameof(values));
if (values.Count == 0) return 0;
if (trimPercentage < 0 || trimPercentage >= 0.5)
throw new ArgumentOutOfRangeException(nameof(trimPercentage), "trimPercentage должен быть в диапазоне [0, 0.5)");
if (values.Count == 1) return values[0];
if (values.Count == 2) return values.Average();
if (values.Count == 3) return CalculateMedian(values);
var sorted = values.OrderBy(v => v).ToList();
int trimCount = CalculateOptimalTrimCount(values.Count, trimPercentage);
int remainingCount = values.Count - 2 * trimCount;
var trimmedValues = sorted
.Skip(trimCount)
.Take(remainingCount)
.ToList();
return CalculateRobustAverage(trimmedValues, values);
}
/// <summary>
/// Расчёт оптимального количества элементов для усечения
/// </summary>
private static int CalculateOptimalTrimCount(int totalCount, double trimPercentage)
{
int calculated = (int)(totalCount * trimPercentage);
int minRemaining = Math.Max(1, totalCount / 10);
int maxTrim = (totalCount - minRemaining) / 2;
return Math.Min(calculated, maxTrim);
}
/// <summary>
/// Устойчивый к числовым ошибкам расчет среднего
/// </summary>
private static double CalculateRobustAverage(List<double> values, List<double> fallbackValues)
{
if (values.Count == 0)
return CalculateMedian(fallbackValues);
try
{
double sum = 0;
int validCount = 0;
foreach (double value in values)
{
if (double.IsFinite(value))
{
sum += value;
validCount++;
}
}
if (validCount == 0) return CalculateMedian(fallbackValues);
double result = sum / validCount;
return double.IsFinite(result) ? result : CalculateMedian(fallbackValues);
}
catch (OverflowException)
{
return CalculateMedian(fallbackValues);
}
}
}
/// <summary>
/// Конфигурация параметров расчёта веса
/// </summary>
public class WeightCalculatorConfig
{
/// <summary>Процент данных для отсечения с каждого конца при расчёте усечённого среднего для порожнего самосвала (например, 10%)</summary>
public double EmptyTrimPercentage { get; set; } = 0.10;
/// <summary>Процент данных для отсечения с каждого конца при расчёте усечённого среднего для груженого самосвала (например, 5%)</summary>
public double LoadedTrimPercentage { get; set; } = 0.05;
/// <summary>Порог коэффициента вариации для определения высокого шума (например, 15%)</summary>
public double HighNoiseThreshold { get; set; } = 0.15;
/// <summary>Минимальное количество точек данных для надежного расчёта</summary>
public int MinimumDataPointsForReliableCalculation { get; set; } = 10;
/// <summary>Максимальное допустимое отклонение веса порожнего самосвала от эталонного (например, 10%)</summary>
public double MaxEmptyWeightDeviation { get; set; } = 0.10;
/// <summary>Допуск при определении стадии "порожний" по весу (например, 5%)</summary>
public double EmptyWeightTolerance { get; set; } = 0.05;
/// <summary>Максимальная ожидаемая грузоподъемность (например, 40 000 кг)</summary>
public double MaxExpectedPayload { get; set; } = 40000;
/// <summary>Минимально ожидаемый груз для валидности</summary>
public double MinExpectedPayload { get; set; } = 1000;
/// <summary>Минимальное значение среднего веса для расчета коэффициента вариации</summary>
public double MinMeanValueForCV { get; set; } = 1.0;
}
/// <summary>
/// Результат валидации входных данных
/// </summary>
public class ValidationResult
{
/// <summary>Признак корректности данных</summary>
public bool IsValid { get; set; }
/// <summary>Сообщение об ошибке, если данные некорректны</summary>
public string ErrorMessage { get; set; } = string.Empty;
public static ValidationResult Valid() => new() { IsValid = true };
public static ValidationResult Invalid(string message) => new() { IsValid = false, ErrorMessage = message };
}
/// <summary>
/// Запись данных с датчика веса на борту самосвала
/// </summary>
public class SensorRecord
{
/// <summary>Уникальный идентификатор записи</summary>
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>Идентификатор самосвала</summary>
public int TruckId { get; init; }
/// <summary>Название или метка самосвала</summary>
public string TruckName { get; init; } = string.Empty;
/// <summary>Тип датчика (например, датчик давления подвески, тензодатчик)</summary>
public SensorType Type { get; init; }
/// <summary>Измеренное значение веса в момент времени</summary>
public double Value { get; init; }
/// <summary>Временная метка измерения</summary>
public DateTime Timestamp { get; init; }
}
/// <summary>
/// Перечисление стадий движения самосвала
/// </summary>
public enum DumpTruckStateEnum
{
/// <summary>Порожний самосвал (без груза)</summary>
ReturningEmpty,
/// <summary>Груженый самосвал (с полезной нагрузкой)</summary>
HaulingLoaded,
/// <summary>Неопределённое состояние, требуется автоматическое определение</summary>
Unknown
}
/// <summary>
/// Тип датчика, для полноты модели
/// </summary>
public enum SensorType
{
/// <summary>Датчик давления гидропневматической подвески</summary>
HydraulicPressure,
/// <summary>Тензодатчик на раме или мостах</summary>
StrainGauge,
/// <summary>Датчик давления пневмоподвески</summary>
PneumaticPressure,
/// <summary>Прочие</summary>
Other
}
Пояснения к коду
Метод
CalculateCargoWeightавтоматически выбирает способ обработки данных с учетом этапа движения и качества сигналов.Для порожнего самосвала используется медиана, устойчивый к выбросам метод, так как вес почти постоянен.
Для груженого самосвала применяется автоматический выбор между медианой (мало данных), усечённым средним (высокий шум) и средним арифметическим (стабильные данные).
Используются статистические показатели: стандартное отклонение и коэффициент вариации для оценки шума.
В коде есть детальная валидация на физическую возможность и проверка качества данных.
Дополнительные параметры (например, пороги, проценты усечения) вынесены в конфигурационный класс.
Расчёт усечённого среднего удаляет крайние выбросы, снижая влияние помех.
Аннотирование
/// <summary>делает код пригодным для документации в статье и удобным к пониманию.
Комментарии (3)

CitizenOfDreams
20.11.2025 10:52Глупый вопрос - а зачем нужно постоянно измерять вес груза в движении, а не один раз сразу после погрузки или разгрузки, когда самосвал стоит на месте? Его же не на ходу загружают/разгружают?

HumanBearPig
20.11.2025 10:52По опыту работы в Ковдорском ГОКе и использования ПО ВИСТ V2:
В Caterpillar 777-793 вес определялся при погрузке и "фиксировался" при переключении на 3-ю передачу. В ходе рейса вес далее не менялся.
Система контроля загрузки БелАЗ 7513х - 753хх определяла вес в течение всего рейса. Был вес как при погрузке, так и при разгрузке, водитель видел на экране один вес, а грузила - другой (а диспетчер вообще мог видеть средний из этих двух значений), что приводило к непониманию сторонами друг друга; потом всё-таки пришли к одному значению.Опытный водитель на обеих типах машин знал как и скинуть и докинуть себе веса.
Определение веса зависит от продольного и поперечного уклона самосвала при погрузке и от центра масс груза относительно кузова. Измерение в динамике дополняет измерение в статике, повышает точность.
vfreeeman
Статья, достойная хабра...