Определение веса самосвала в движении нетривиальная задача, в которой технические ограничения, тип подвески, принцип действия датчиков и качество телематической цепочки играют ключевую роль. Датчики нагрузки фиксируют показатели с высоким уровнем шума: дорожные неровности, динамика подвески и особенности конструкции кузова приводят к тому, что каждое измерение — лишь приближение к реальному весу. Для корректного расчёта требуется система, которая не только усредняет данные, но и адаптируется к качеству входных сигналов. Перед использованием алгоритмов обработки данных необходимо понимать конструкцию техники, доступные методы взвешивания и природу шума, возникающего в процессе измерений.
Три основных типа карьерных самосвалов в России
В российской добывающей отрасли применяются три доминирующих класса машин, каждый из которых имеет собственные особенности измерения веса:
Классические карьерные самосвалы с жёсткой рамой, такие как БелАЗ 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>делает код пригодным для документации в статье и удобным к пониманию.
