Ask me anything
Ithy Logo
Sign in to save

Разблокировка скрытых значений: Билинейная интерполяция в C# для вашего 2D-массива данных

Комплексное руководство по точному определению значений из вашего набора данных с использованием C# и методов, аналогичных билинейной интерполяции.

csharp-bilinear-interpolation-2d-array-y9jwylox

Для поиска значения в вашем двумерном массиве entPr по двум известным параметрам (например, значениям из первой и второй строк) с использованием линейной интерполяции, мы применим подход, который по сути является формой билинейной интерполяции, адаптированной для структуры ваших данных. Ваши данные представляют собой набор точек (X, Y, Z), а не строго регулярную сетку. Мы будем выполнять последовательные линейные интерполяции: сначала вдоль оси Y для двух "опорных" значений X, а затем вдоль оси X для полученных результатов.

Ключевые моменты

  • Структура данных: Ваш массив entPr содержит координаты X в первой строке, Y во второй и соответствующие значения Z в третьей. Каждая колонка представляет отдельную точку данных.
  • Двухэтапная интерполяция: Сначала мы интерполируем значения Z вдоль оси Y для двух ближайших известных X-значений, окружающих ваш целевой X. Затем, используя эти два промежуточных Z-значения, мы интерполируем окончательное Z-значение вдоль оси X.
  • Обработка границ: Если целевые значения X или Y выходят за пределы известного диапазона в данных, результат будет соответствовать значению на ближайшей границе (экстраполяция путем привязки к краю).

Понимание процесса интерполяции

Интерполяция помогает нам оценить неизвестные значения, которые лежат между известными точками данных. Поскольку у вас два входных параметра (X и Y) для определения третьего (Z), мы используем метод, аналогичный билинейной интерполяции.

Шаги алгоритма:

  1. Преобразование данных: Исходный массив double[,] преобразуется в список объектов DataPoint (содержащих X, Y, Z) для удобства обработки.
  2. Определение опорных X-значений: Находятся два уникальных значения X из вашего набора данных (xLow, xHigh), которые "окружают" или совпадают с вашим targetX. Если targetX выходит за пределы диапазона, он "привязывается" к ближайшему крайнему значению X.
  3. Интерполяция на "плоскости" xLow:
    • Выбираются все точки данных, у которых координата X равна xLow.
    • Вдоль оси Y для этих точек находится интерполированное значение Z_at_xLow_targetY при вашем targetY. Это стандартная 1D линейная интерполяция по Y. Если targetY выходит за пределы Y-значений для данного xLow, результат привязывается к краю.
  4. Интерполяция на "плоскости" xHigh: Аналогично шагу 3, вычисляется Z_at_xHigh_targetY для xHigh. (Этот шаг пропускается, если xLow и xHigh совпадают).
  5. Финальная интерполяция по X: Используя два полученных значения (Z_at_xLow_targetY и Z_at_xHigh_targetY) и соответствующие им xLow и xHigh, выполняется финальная 1D линейная интерполяция по X для нахождения итогового значения Z при targetX.

Формула линейной интерполяции (1D)

Основой всего процесса является формула линейной интерполяции. Если у нас есть две точки \((C_1, V_1)\) и \((C_2, V_2)\), то для значения \(targetC\) между \(C_1\) и \(C_2\) соответствующее значение \(V\) вычисляется как:

\[ V = V_1 + (targetC - C_1) \times \frac{V_2 - V_1}{C_2 - C_1} \]

Где \(C\) представляет координатную ось (X или Y), а \(V\) — значение для интерполяции (Z или промежуточное Z).

Визуальное сравнение методов интерполяции, включая билинейную

Визуальное сравнение различных методов интерполяции. Билинейная интерполяция (b) является расширением линейной на две размерности.


Реализация на C#

Ниже представлен полный код на C# для выполнения интерполяции по описанному методу. Код включает структуру `DataPoint` для хранения точек, основную функцию `InterpolateValue` и вспомогательные методы.


using System;
using System.Collections.Generic;
using System.Linq;

// Структура для удобного представления точек данных
public struct DataPoint
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

public static class InterpolationModule
{
    // Вспомогательная функция для одномерной линейной интерполяции
    // c1, c2 - координаты опорных точек (например, X1, X2 или Y1, Y2)
    // v1, v2 - значения в опорных точках (например, Z1, Z2)
    // targetC - координата, для которой ищется значение
    private static double LinearInterpolate(double c1, double v1, double c2, double v2, double targetC)
    {
        // Если координаты опорных точек совпадают или очень близки (избегаем деления на ноль)
        if (Math.Abs(c1 - c2) < 1e-9)
        {
            return v1; // Возвращаем значение одной из точек (или среднее, или выбрасываем ошибку)
        }
        // Классическая формула линейной интерполяции
        return v1 + (targetC - c1) * (v2 - v1) / (c2 - c1);
    }

    // Основная функция для выполнения интерполяции
    public static double InterpolateValue(double[,] dataMatrix, double targetX, double targetY)
    {
        // 1. Преобразование входного двумерного массива в список объектов DataPoint
        var points = new List<DataPoint>();
        int numCols = dataMatrix.GetLength(1);
        for (int i = 0; i < numCols; i++)
        {
            points.Add(new DataPoint { X = dataMatrix[0, i], Y = dataMatrix[1, i], Z = dataMatrix[2, i] });
        }

        if (!points.Any())
        {
            throw new ArgumentException("Входной массив данных пуст.");
        }

        // 2. Нахождение уникальных X-значений, которые "окружают" targetX или совпадают с ним
        var uniqueXValues = points.Select(p => p.X).Distinct().OrderBy(x => x).ToList();
        if (!uniqueXValues.Any())
        {
            // Эта ситуация маловероятна, если points не пуст, но для полноты проверка
            throw new ArgumentException("В данных отсутствуют значения X для интерполяции.");
        }

        double xLow, xHigh;
        // Обработка случаев, когда targetX выходит за пределы диапазона известных X-значений (привязка к краям)
        if (targetX <= uniqueXValues.First()) { xLow = xHigh = uniqueXValues.First(); }
        else if (targetX >= uniqueXValues.Last()) { xLow = xHigh = uniqueXValues.Last(); }
        else
        {
            // targetX находится внутри диапазона
            xLow = uniqueXValues.Last(x => x <= targetX);
            xHigh = uniqueXValues.First(x => x >= targetX);
        }

        // 3. Интерполяция значения Z для targetY на "X-плоскости", соответствующей xLow
        var pointsAtXLow = points.Where(p => Math.Abs(p.X - xLow) < 1e-9).OrderBy(p => p.Y).ToList();
        if (!pointsAtXLow.Any())
        {
             // Если для выбранного xLow нет точек (маловероятно, если xLow взят из uniqueXValues, полученных из points)
             throw new ArgumentException($"Нет данных для X = {xLow}. Невозможно выполнить интерполяцию на этой X-плоскости.");
        }
        double zAtXLowTargetY = InterpolateZOnYAxis(pointsAtXLow, targetY);

        // Если xLow и xHigh совпадают (т.е. targetX лежит на существующей X-координате или был привязан к краю),
        // то результат уже найден
        if (Math.Abs(xLow - xHigh) < 1e-9)
        {
            return zAtXLowTargetY;
        }

        // 4. Интерполяция значения Z для targetY на "X-плоскости", соответствующей xHigh
        var pointsAtXHigh = points.Where(p => Math.Abs(p.X - xHigh) < 1e-9).OrderBy(p => p.Y).ToList();
         if (!pointsAtXHigh.Any())
        {
             throw new ArgumentException($"Нет данных для X = {xHigh}. Невозможно выполнить интерполяцию на этой X-плоскости.");
        }
        double zAtXHighTargetY = InterpolateZOnYAxis(pointsAtXHigh, targetY);

        // 5. Финальная интерполяция по оси X, используя значения, полученные на шагах 3 и 4
        return LinearInterpolate(xLow, zAtXLowTargetY, xHigh, zAtXHighTargetY, targetX);
    }

    // Вспомогательная функция для интерполяции Z вдоль оси Y для заданного набора точек
    // (предполагается, что все точки в pointsOnXPlane имеют одинаковую X-координату)
    private static double InterpolateZOnYAxis(List<DataPoint> pointsOnXPlane, double targetY)
    {
        if (!pointsOnXPlane.Any())
        {
            // Эта проверка здесь для дополнительной надежности, хотя основная логика должна предотвращать пустой список
            throw new ArgumentException("Нет точек на указанной X-плоскости для выполнения Y-интерполяции.");
        }
        
        // pointsOnXPlane уже отсортирован по Y при его создании

        double y1, y2, z1, z2;

        // Обработка случаев, когда targetY выходит за пределы диапазона Y-значений для данной X-плоскости
        if (targetY <= pointsOnXPlane.First().Y)
        {
            // targetY меньше или равен минимальному Y, привязываем к значению Z первой точки
            return pointsOnXPlane.First().Z;
        }
        if (targetY >= pointsOnXPlane.Last().Y)
        {
            // targetY больше или равен максимальному Y, привязываем к значению Z последней точки
            return pointsOnXPlane.Last().Z;
        }

        // Нахождение точек, "окружающих" targetY
        var p1_candidate = pointsOnXPlane.LastOrDefault(p => p.Y <= targetY);
        var p2_candidate = pointsOnXPlane.FirstOrDefault(p => p.Y >= targetY);

        // Если targetY точно совпадает с Y одной из точек
        if (p1_candidate.Y == targetY) return p1_candidate.Z;
        if (p2_candidate.Y == targetY) return p2_candidate.Z; // Должно быть p2_candidate.X, но Y здесь primary

        // Убедимся, что p1 и p2 корректны
        DataPoint p1 = (p1_candidate.X == 0 && p1_candidate.Y == 0 && p1_candidate.Z == 0 && pointsOnXPlane.Any()) ? pointsOnXPlane.First() : p1_candidate; // Fallback if LastOrDefault returns default struct
        DataPoint p2 = (p2_candidate.X == 0 && p2_candidate.Y == 0 && p2_candidate.Z == 0 && pointsOnXPlane.Any()) ? pointsOnXPlane.Last() : p2_candidate;   // Fallback

        y1 = p1.Y; z1 = p1.Z;
        y2 = p2.Y; z2 = p2.Z;
        
        return LinearInterpolate(y1, z1, y2, z2, targetY);
    }

    // Пример использования
    public static void Main(string[] args)
    {
        double[,] entPr = new double[,] {
            {20,20,20,30,30,30,40,40,40,50,50,50 },
            {250.119521912,275.458167331,300.3187251,150.199203187,223.824701195,300.3187251,120.079681275,207.569721116,300.3187251,120.079681275,149.721115538,300.3187251 },
            {0.168128654971,0.380116959064,0.584795321637,0.285087719298,1.1769005848,2.10526315789,0.380116959064,1.78362573099,3.26023391813,1.12573099415,1.71783625731,4.78070175439 }
        };

        // Пример 1: Общий случай интерполяции
        double targetX1 = 25;
        double targetY1 = 260; 
        try
        {
            double interpolatedZ1 = InterpolateValue(entPr, targetX1, targetY1);
            Console.WriteLine($"Для X = {targetX1} и Y = {targetY1}, интерполированное значение Z = {interpolatedZ1}");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"Ошибка для ({targetX1},{targetY1}): {ex.Message}");
        }

        // Пример 2: Точка совпадает с существующей в данных
        double targetX2 = 30;
        double targetY2 = 223.824701195; 
        try
        {
            double interpolatedZ2 = InterpolateValue(entPr, targetX2, targetY2);
            Console.WriteLine($"Для X = {targetX2} и Y = {targetY2}, интерполированное значение Z = {interpolatedZ2} (Ожидается: {entPr[2,4]})");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"Ошибка для ({targetX2},{targetY2}): {ex.Message}");
        }
        
        // Пример 3: Y вне диапазона для данного X (ниже)
        double targetX3 = 20;
        double targetY3 = 200; 
        try
        {
            double interpolatedZ3 = InterpolateValue(entPr, targetX3, targetY3);
            Console.WriteLine($"Для X = {targetX3} и Y = {targetY3} (Y ниже диапазона), интерполированное Z = {interpolatedZ3} (Ожидается привязка к Z для наименьшего Y при X=20: {entPr[2,0]})");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"Ошибка для ({targetX3},{targetY3}): {ex.Message}");
        }

        // Пример 4: Y вне диапазона для данного X (выше)
        double targetX4 = 40;
        double targetY4 = 350;
         try
        {
            double interpolatedZ4 = InterpolateValue(entPr, targetX4, targetY4);
            Console.WriteLine($"Для X = {targetX4} и Y = {targetY4} (Y выше диапазона), интерполированное Z = {interpolatedZ4} (Ожидается привязка к Z для наибольшего Y при X=40: {entPr[2,8]})");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine($"Ошибка для ({targetX4},{targetY4}): {ex.Message}");
        }
    }
}
    

Визуализация процесса интерполяции

Для лучшего понимания того, как происходит процесс поиска значения, рассмотрим следующую ментальную карту. Она иллюстрирует последовательность шагов, начиная от входных данных и заканчивая получением конечного результата.

mindmap root["Процесс 2D Интерполяции"] id1["Входные данные"] id1_1["Массив точек (X, Y, Z)"] id1_2["Целевые значения (targetX, targetY)"] id2["Этап 1: Выбор X-плоскостей"] id2_1["Найти уникальные X-координаты из данных"] id2_2["Определить xLow и xHigh, обрамляющие targetX
(или совпадающие с targetX/краями диапазона)"] id3["Этап 2: Интерполяция по Y на X-плоскости xLow"] id3_1["Выбрать все точки (xLow, Yi, Zi)"] id3_2["Отсортировать по Yi"] id3_3["Найти y_low_on_xLow и y_high_on_xLow, обрамляющие targetY"] id3_4["Вычислить Z_at_xLow_targetY
линейной интерполяцией по Y"] id4["Этап 3: Интерполяция по Y на X-плоскости xHigh (если xLow != xHigh)"] id4_1["Выбрать все точки (xHigh, Yj, Zj)"] id4_2["Отсортировать по Yj"] id4_3["Найти y_low_on_xHigh и y_high_on_xHigh, обрамляющие targetY"] id4_4["Вычислить Z_at_xHigh_targetY
линейной интерполяцией по Y"] id5["Этап 4: Финальная интерполяция по X"] id5_1["Использовать точки (xLow, Z_at_xLow_targetY) и (xHigh, Z_at_xHigh_targetY)"] id5_2["Вычислить итоговое Z
линейной интерполяцией по X при targetX"] id6["Результат"] id6_1["Интерполированное значение Z"]

Эта карта показывает, что процесс, хотя и называется билинейным в общем смысле, разбивается на последовательность более простых одномерных линейных интерполяций, что делает его применимым к вашему конкретному набору данных.


Пример расчета (табличное представление)

Давайте рассмотрим гипотетический пример расчета для `targetX = 25` и `targetY = 260` с использованием вашего массива `entPr`.

Шаг Действие Данные/Примерные значения Результат
1 Определение `xLow` и `xHigh` для `targetX = 25` Уникальные X: {20, 30, 40, 50} `xLow = 20`, `xHigh = 30`
2 Интерполяция по Y на плоскости `xLow = 20` для `targetY = 260` Точки при X=20: (20, 250.12, 0.168), (20, 275.46, 0.380), (20, 300.32, 0.585) `y1_xLow = 250.1195`, `z1_xLow = 0.1681`
`y2_xLow = 275.4582`, `z2_xLow = 0.3801`
`Z_at_xLow_targetY` = LinInterp(250.12, 0.168, 275.46, 0.380, 260) ≈ 0.256
3 Интерполяция по Y на плоскости `xHigh = 30` для `targetY = 260` Точки при X=30: (30, 150.20, 0.285), (30, 223.82, 1.177), (30, 300.32, 2.105) `y1_xHigh = 223.8247`, `z1_xHigh = 1.1769`
`y2_xHigh = 300.3187`, `z2_xHigh = 2.1053`
`Z_at_xHigh_targetY` = LinInterp(223.82, 1.177, 300.32, 2.105, 260) ≈ 1.564
4 Финальная интерполяция по X для `targetX = 25` Точки: (20, `Z_at_xLow_targetY` ≈ 0.256), (30, `Z_at_xHigh_targetY` ≈ 1.564) Итоговое Z = LinInterp(20, 0.256, 30, 1.564, 25) ≈ 0.910

Примечание: Приведенные в таблице расчеты являются приблизительными для иллюстрации. Точные значения будут вычислены программой.


Сравнение методов интерполяции

Существуют различные методы интерполяции, каждый со своими сильными и слабыми сторонами. Представленный здесь метод (последовательная линейная интерполяция) является практичным выбором для вашего типа данных. Диаграмма ниже сравнивает его концептуально с другими подходами по нескольким критериям.

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


Это видео объясняет концепцию билинейной интерполяции в контексте обработки изображений. Хотя ваш случай связан с числовыми данными, математические принципы интерполяции на двумерной плоскости схожи: оценка значения в точке на основе четырех окружающих известных значений.


Часто задаваемые вопросы (FAQ)

Что произойдет, если мои целевые значения X или Y выходят за пределы диапазона данных?

В представленной реализации, если `targetX` или `targetY` выходят за пределы диапазона значений, имеющихся в вашем массиве `entPr`, программа "привяжет" целевое значение к ближайшему крайнему значению из данных. Например, если наименьшее X в данных равно 20, а вы запрашиваете интерполяцию для X=15, расчет будет произведен так, как если бы вы запросили X=20. Это называется экстраполяцией путем привязки к краю (clamping). Аналогично для `targetY` на этапе интерполяции по оси Y.

Что если для опорного значения X (xLow или xHigh) нет достаточного количества Y-точек для интерполяции?

Код разработан так, чтобы использовать все доступные точки данных для выбранных `xLow` и `xHigh`. Если для конкретного `xLow` (или `xHigh`) существует менее двух уникальных Y-значений, линейная интерполяция по Y не сможет быть выполнена классическим образом. В текущей реализации, если `targetY` выходит за пределы диапазона Y-значений для этой X-плоскости (или если есть только одна точка), значение Z будет привязано к Z этой единственной точки или к ближайшей граничной точке. Если для `xLow` или `xHigh` вообще нет Y-точек (что маловероятно, если `xLow`/`xHigh` выбраны из `uniqueXValues`, полученных из исходных данных), программа выдаст ошибку, так как интерполяция невозможна.

Является ли этот метод самым точным для моего набора данных?

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

Почему бы просто не усреднить значения ближайших точек?

Усреднение значений нескольких ближайших точек — это один из видов интерполяции (например, метод k-ближайших соседей с усреднением или взвешенное усреднение). Однако линейная (и билинейная) интерполяция учитывает не только значения, но и относительные расстояния до известных точек, предполагая линейное изменение значения между ними. Это часто дает более структурированный и предсказуемый результат, особенно если данные имеют некоторую тенденцию. Простая линейная интерполяция использует две ближайшие точки на линии (или четыре в случае билинейной на плоскости), тогда как усреднение может использовать большее количество точек и различные схемы взвешивания.


Рекомендуемые запросы


Источники


Last updated May 7, 2025

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI