Знаходження мінімальної граничної міри заданого значення пікселя в растрі?


9

Мені цікаво, чи є спосіб знайти мінімальну обмежувальну міру для растра з певним значенням. Я вирізав растр із глобального зображення, і масштаб встановлений як глобальний масштаб з великою кількістю області NoData. Я хотів би видалити область NoData з цього растру і зберегти лише область, що містить пікселі певного значення. Як я можу це зробити?

Ось мій приклад: я хотів би витягнути значення = 1 (Блакитна область) і використовувати масштаб синьої області, а не весь світ для подальшої обробки.

Зразок зображення


Чи можете ви опублікувати зразок?
Аарон

"Я хотів би видалити нульові рядки та стовпці для цього растру." Що саме це означає? Я не розумію, що таке бажаний кінцевий результат.
blah238

Визначаючи "мінімальний обмежуючий ступінь", чи шукаєте ви прямокутний розмір або полігональний слід, що представляє площу зображення з даними?
blah238

1
@Tomek, ОП прагне знайти міру, не потрібно створювати її вручну.
blah238

1
Якщо буквально щось є чесною грою, то для цього програмного забезпечення є вбудовані команди; Наприклад, див. reference.wolfram.com/mathematica/ref/ImageCrop.html
whuber

Відповіді:


6

Якщо я правильно зрозумів це питання, то це здається, що ви хочете знати мінімальне обмежувальне поле значень, які не є нульовими. Потім ви можете переглянути значення властивостей, які повинні дати вам мінімальну рамку обмеження.


1
Все сказане про це, мабуть, найкращий підхід, враховуючи межі робочого процесу обробки растрової обробки ArcGIS.
blah238

Я робив це саме. Чи є автоматичний спосіб? Я думаю, що алгоритм растру до полігону має крок до отримання мінімального обмежувального поля растра.
Відвідано

ви після рішення пітона?
данго

8

Трюк полягає в обчисленні меж даних, які мають значення. Мабуть, найшвидший, найприродніший і найзагальніший спосіб їх отримання - це зональні підсумки: використовуючи всі клітини, що не належать NoData, для зони зональний хв і макс сіток, що містять координати X і Y, забезпечать повну міру.

ESRI продовжує змінювати способи здійснення цих розрахунків; наприклад, вбудовані вирази для координатних сіток були скинуті за допомогою ArcGIS 8 і, здається, не повернулися. Просто для розваги, ось набір швидких, простих розрахунків, які дозволять зробити роботу незважаючи ні на що.

  1. Перетворіть сітку в одну зону , прирівнявши її до себе, як у

    "Моя сітка" == "Моя сітка"

  2. Створіть сітку індексів стовпців, накопичивши поточну постійну сітку зі значенням 1. (Індекси почнуться з 0.) При бажанні помножте це на келійний розмір і додайте x-координату початку, щоб отримати сітку координат x » X "(показано нижче).

  3. Аналогічно створіть сітку індексу рядків ( а потім сітку координат y "Y"), накопичивши потоком постійну сітку зі значенням 64.

  4. Скористайтеся зонною сіткою з кроку (1) для обчислення зональних мінімумів та максимумів "X" та "Y" : тепер у вас є бажана міра.

Фінальне зображення

(Розмір, як показано у двох таблицях зональної статистики, зображено прямокутним контуром на цьому малюнку. Сітка "І" - це зональна сітка, отримана на етапі (1).)

Щоб піти далі, вам потрібно буде витягнути ці чотири числа зі своїх вихідних таблиць і використовувати їх для обмеження обсягу аналізу. Копіювання оригінальної сітки з обмеженим обсягом аналізу завершує завдання.


8

Ось версія методу @whubers для ArcGIS 10.1+ як панелі інструментів python (.pyt).

import arcpy

class Toolbox(object):
    def __init__(self):
        """Define the toolbox (the name of the toolbox is the name of the
        .pyt file)."""
        self.label = "Raster Toolbox"
        self.alias = ""

        # List of tool classes associated with this toolbox
        self.tools = [ClipNoData]


class ClipNoData(object):
    def __init__(self):
        """Clip raster extent to the data that have values"""
        self.label = "Clip NoData"
        self.description = "Clip raster extent to the data that have values. "
        self.description += "Method by Bill Huber - https://gis.stackexchange.com/a/55150/2856"

        self.canRunInBackground = False

    def getParameterInfo(self):
        """Define parameter definitions"""
        params = []

        # First parameter
        params+=[arcpy.Parameter(
            displayName="Input Raster",
            name="in_raster",
            datatype='GPRasterLayer',
            parameterType="Required",
            direction="Input")
        ]

        # Second parameter
        params+=[arcpy.Parameter(
            displayName="Output Raster",
            name="out_raster",
            datatype="DERasterDataset",
            parameterType="Required",
            direction="Output")
        ]

        return params

    def isLicensed(self):
        """Set whether tool is licensed to execute."""
        return arcpy.CheckExtension('spatial')==u'Available'

    def execute(self, parameters, messages):
        """See https://gis.stackexchange.com/a/55150/2856
           ##Code comments paraphrased from @whubers GIS StackExchange answer
        """
        try:
            #Setup
            arcpy.CheckOutExtension('spatial')
            from arcpy.sa import *
            in_raster = parameters[0].valueAsText
            out_raster = parameters[1].valueAsText

            dsc=arcpy.Describe(in_raster)
            xmin=dsc.extent.XMin
            ymin=dsc.extent.YMin
            mx=dsc.meanCellWidth
            my=dsc.meanCellHeight
            arcpy.env.extent=in_raster
            arcpy.env.cellSize=in_raster
            arcpy.AddMessage(out_raster)

            ## 1. Convert the grid into a single zone by equating it with itself
            arcpy.AddMessage(r'1. Convert the grid into a single zone by equating it with itself...')
            zones = Raster(in_raster) == Raster(in_raster)

            ## 2. Create a column index grid by flow-accumulating a constant grid
            ##    with value 1. (The indexes will start with 0.) Multiply this by
            ##    the cellsize and add the x-coordinate of the origin to obtain
            ##    an x-coordinate grid.
            arcpy.AddMessage(r'Create a constant grid...')
            const = CreateConstantRaster(1)

            arcpy.AddMessage(r'2. Create an x-coordinate grid...')
            xmap = (FlowAccumulation(const)) * mx + xmin

            ## 3. Similarly, create a y-coordinate grid by flow-accumulating a
            ##    constant grid with value 64.
            arcpy.AddMessage(r'3. Create a y-coordinate grid...')
            ymap = (FlowAccumulation(const * 64)) * my + ymin

            ## 4. Use the zone grid from step (1) to compute the zonal min and
            ##    max of "X" and "Y"
            arcpy.AddMessage(r'4. Use the zone grid from step (1) to compute the zonal min and max of "X" and "Y"...')

            xminmax=ZonalStatisticsAsTable(zones, "value", xmap,r"IN_MEMORY\xrange", "NODATA", "MIN_MAX")
            xmin,xmax=arcpy.da.SearchCursor(r"IN_MEMORY\xrange", ["MIN","MAX"]).next()

            yminmax=ZonalStatisticsAsTable(zones, "value", ymap,r"IN_MEMORY\yrange", "NODATA", "MIN_MAX")
            ymin,ymax=arcpy.da.SearchCursor(r"IN_MEMORY\yrange", ["MIN","MAX"]).next()

            arcpy.Delete_management(r"IN_MEMORY\xrange")
            arcpy.Delete_management(r"IN_MEMORY\yrange")

            # Write out the reduced raster
            arcpy.env.extent = arcpy.Extent(xmin,ymin,xmax+mx,ymax+my)
            output = Raster(in_raster) * 1
            output.save(out_raster)

        except:raise
        finally:arcpy.CheckInExtension('spatial')

Дуже приємний Люк. Автономний, запущений, використовує in_memory та добре коментується під час завантаження. Мені довелося вимкнути обробку фону ( Geoprocessing> options> ... ), щоб працювати.
matt wilkie

1
Я оновив сценарій і встановив canRunInBackground = Неправдиво. Зауважу, що варто змінити середовище робочої області / нуля скриптів на локальну папку (не FGDB), як я виявив, коли залишив їх за замовчуванням (тобто <профіль користувача мережі> \ Default.gdb), сценарій зайняв 9 хв !!! запускатись по клітинній сітці 250х250. Перехід на локальний FGDB знадобився 9 сек, а в локальну папку
1-2

хороший момент про локальні папки та дякую за швидке виправлення фону (набагато краще, ніж написання інструкцій для всіх, кому я передаю це). Можливо, варто кинути це на bitbucket / github / gcode / тощо.
matt wilkie

+1 Дякую за цей внесок, Лука! Я вдячний, що ви заповнили (досить великий) пробіл, який залишився в моїй відповіді.
whuber

4

Я розробив рішення на основі gdal та numpy. Він розбиває растрову матрицю на рядки та стовпці та видаляє будь-який порожній рядок / стовпчик. У цій реалізації "порожній" - це щонайменше 1, і припадає лише на однополосні растри.

(Я розумію, як я пишу, що цей підхід до сканування підходить лише для зображень з "комірцями" з нодатами. Якщо ваші дані - острови в морях нулів, простір між островами також випаде, збивши все разом і повністю зіпсувавши геореференціювання .)

Ділові частини (потребує розробки, не працюватимуть так, як є):

    #read raster into a numpy array
    data = np.array(gdal.Open(src_raster).ReadAsArray())
    #scan for data
    non_empty_columns = np.where(data.max(axis=0)>0)[0]
    non_empty_rows = np.where(data.max(axis=1)>0)[0]
        # assumes data is any value greater than zero
    crop_box = (min(non_empty_rows), max(non_empty_rows),
        min(non_empty_columns), max(non_empty_columns))

    # retrieve source geo reference info
    georef = raster.GetGeoTransform()
    xmin, ymax = georef[0], georef[3]
    xcell, ycell = georef[1], georef[5]

    # Calculate cropped geo referencing
    new_xmin = xmin + (xcell * crop_box[0]) + xcell
    new_ymax = ymax + (ycell * crop_box[2]) - ycell
    cropped_transform = new_xmin, xcell, 0.0, new_ymax, 0.0, ycell

    # crop
    new_data = data[crop_box[0]:crop_box[1]+1, crop_box[2]:crop_box[3]+1]

    # write to disk
    band = out_raster.GetRasterBand(1)
    band.WriteArray(new_data)
    band.FlushCache()
    out_raster = None

Повний сценарій:

import os
import sys
import numpy as np
from osgeo import gdal

if len(sys.argv) < 2:
    print '\n{} [infile] [outfile]'.format(os.path.basename(sys.argv[0]))
    sys.exit(1)

src_raster = sys.argv[1]
out_raster = sys.argv[2]

def main(src_raster):
    raster = gdal.Open(src_raster)

    # Read georeferencing, oriented from top-left
    # ref:GDAL Tutorial, Getting Dataset Information
    georef = raster.GetGeoTransform()
    print '\nSource raster (geo units):'
    xmin, ymax = georef[0], georef[3]
    xcell, ycell = georef[1], georef[5]
    cols, rows = raster.RasterYSize, raster.RasterXSize
    print '  Origin (top left): {:10}, {:10}'.format(xmin, ymax)
    print '  Pixel size (x,-y): {:10}, {:10}'.format(xcell, ycell)
    print '  Columns, rows    : {:10}, {:10}'.format(cols, rows)

    # Transfer to numpy and scan for data
    # oriented from bottom-left
    data = np.array(raster.ReadAsArray())
    non_empty_columns = np.where(data.max(axis=0)>0)[0]
    non_empty_rows = np.where(data.max(axis=1)>0)[0]
    crop_box = (min(non_empty_rows), max(non_empty_rows),
        min(non_empty_columns), max(non_empty_columns))

    # Calculate cropped geo referencing
    new_xmin = xmin + (xcell * crop_box[0]) + xcell
    new_ymax = ymax + (ycell * crop_box[2]) - ycell
    cropped_transform = new_xmin, xcell, 0.0, new_ymax, 0.0, ycell

    # crop
    new_data = data[crop_box[0]:crop_box[1]+1, crop_box[2]:crop_box[3]+1]

    new_rows, new_cols = new_data.shape # note: inverted relative to geo units
    #print cropped_transform

    print '\nCrop box (pixel units):', crop_box
    print '  Stripped columns : {:10}'.format(cols - new_cols)
    print '  Stripped rows    : {:10}'.format(rows - new_rows)

    print '\nCropped raster (geo units):'
    print '  Origin (top left): {:10}, {:10}'.format(new_xmin, new_ymax)
    print '  Columns, rows    : {:10}, {:10}'.format(new_cols, new_rows)

    raster = None
    return new_data, cropped_transform


def write_raster(template, array, transform, filename):
    '''Create a new raster from an array.

        template = raster dataset to copy projection info from
        array = numpy array of a raster
        transform = geo referencing (x,y origin and pixel dimensions)
        filename = path to output image (will be overwritten)
    '''
    template = gdal.Open(template)
    driver = template.GetDriver()
    rows,cols = array.shape
    out_raster = driver.Create(filename, cols, rows, gdal.GDT_Byte)
    out_raster.SetGeoTransform(transform)
    out_raster.SetProjection(template.GetProjection())
    band = out_raster.GetRasterBand(1)
    band.WriteArray(array)
    band.FlushCache()
    out_raster = None
    template = None

if __name__ == '__main__':
    cropped_raster, cropped_transform = main(src_raster)
    write_raster(src_raster, cropped_raster, cropped_transform, out_raster)

Сценарій знаходиться в моєму коді на сховищі Github, якщо посилання йде на 404 полювання трохи; ці папки дозріли для деякої перебудови.


1
Це виграш для дійсно великих наборів даних. Я отримуюMemoryError Source raster (geo units): Origin (top left): 2519950.0001220703, 5520150.00012207 Pixel size (x,-y): 100.0, -100.0 Columns, rows : 42000, 43200 Traceback (most recent call last): File "D:/11202067_COACCH/local_checkout/crop_raster.py", line 72, in <module> cropped_raster, cropped_transform = main(src_raster) File "D:/11202067_COACCH/local_checkout/crop_raster.py", line 22, in main data = np.array(raster.ReadAsArray()) MemoryError
user32882

2

Незважаючи на всю свою аналітичну потужність, ArcGIS не вистачає базових растрових маніпуляцій, які можна знайти з традиційними настільними редакторами зображень, такими як GIMP . Очікується, що ви хочете використовувати ту саму ступінь аналізу для вихідного растру, що і для вхідного растру, якщо ви вручну не перекриєте налаштування середовища Output Extent . Оскільки це саме те, що ви шукаєте, щоб не знайти, а не встановити, ArcGIS спосіб робити речі стає на шляху.

Незважаючи на мої зусилля, і не вдаючись до програмування, я не міг знайти способу отримати бажану підмножину зображення (без перетворення растрових у векторні, що обчислювально марно).

Натомість я використовував GIMP для вибору синьої області за допомогою інструменту "Вибір за кольором", а потім перевернув виділення, натисніть Видалити, щоб видалити решту пікселів, перевернув виділення ще раз, обрізав зображення для вибору та, нарешті, експортував його назад на PNG GIMP зберегла його як 1-бітове зображення глибини. Результат нижче:

Вихідні дані

Звичайно, оскільки у вашому зразковому зображенні бракує просторового еталонного компонента, а GIMP просторово не усвідомлює, вихідне зображення є настільки ж корисним, як і ваш зразок введення. Вам потрібно буде зробити геореференцію, щоб вона була корисною в просторовому аналізі.


На насправді, ця операція використовується , щоб бути простим у попередніх версіях Spatial Analyst: зональний максимум і мінімум з двох координатних сіток (X і Y), з допомогою функції , як зони, дають ступінь точності. (Ну, можливо, ви захочете розширити його на половину клітинки у всіх чотирьох напрямках.) У ArcGIS 10 вам потрібно проявити творчість (або використовувати Python), щоб скласти координатну сітку. Незалежно від цього, все це можна зробити повністю в межах SA, використовуючи лише сітки та без ручного втручання.
whuber

@whuber Я бачив ваше рішення десь в іншому місці, але все ще не знаю, як я можу реалізувати ваш метод. Не могли б ви показати мені детальніше про це?
Відвідано

@Seen Швидкий пошук цього сайту знайде обліковий запис методу на сайті gis.stackexchange.com/a/13467 .
качан

1

Ось одна з можливостей використання ГІС SAGA: http://permalink.gmane.org/gmane.comp.gis.gdal.devel/33021

У GAG SAGA є модуль "Обрізати дані" (у бібліотеці модулів інструментів Grid), який виконує завдання.

Але для цього потрібно буде імпортувати ваш Geotif за допомогою модуля імпорту GDAL, обробити його в SAGA і, нарешті, експортувати його як Geotif знову за допомогою модуля експорту GDAL.

Іншою можливістю використання лише інструментів GP ArcGIS було б побудувати TIN з вашого растру за допомогою Raster to TIN , обчислити його кордон за допомогою домену TIN та обрізати растр за межею (або його конверт, використовуючи конверт функцій на полігон ).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.