Извлечение объектов (размера сетки) из изображения

Я работаю с изображением

Фактическое изображение:

Я использовал такие методы, как Canny, Hough преобразование, чтобы обнаружить линию, и получил вот это

Выход:

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

Часть:

Можете ли вы просветить меня по этому поводу?

# Dilate the edge image to make the edges thicker
   dialted_edge_image = cv2.dilate(edge_canny_image,kernel,iterations = 2)  

  # Perform a Hough Transform to detect lines
  lines = probabilistic_hough_line(edge_canny_image, threshold=40, line_length=1, line_gap=2)

  # Create a separate image for line detection
  line_detected_image = np.dstack([edge_canny_image] * 3)  # Convert to RGB for colored lines
   for line in lines:
    p0, p1 = line
    cv2.line(line_detected_image, p0, p1, (255, 255, 255), 2)  `

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

Barış Aktaş 07.03.2024 12:25

«Кэнни и Хаф» были твоей первой ошибкой. просто используйте inRange, чтобы выбрать три уровня (темный, серый, светлый) на вашем изображении. затем, возможно, примените морфологические операции, чтобы удалить «пятна»/трещины. -- для измерений определите несколько линий, по которым будет производиться выборка изображения. тогда вы уменьшили проблему до 1D. в 1D-данных легко найти нарастающие/спадающие фронты.

Christoph Rackwitz 07.03.2024 13:13

линии выборки, которые я бы определил: вертикальные по левому краю, по центру, по правому краю, чтобы получить горизонтальные полосы; то же самое для верхнего края, центра и нижнего края вертикальных полос. относительно этого вы можете вычислить пересечения полос, а затем взять диагонали, чтобы выбрать указанное вами «скругление». -- выборка проста с помощью warpAffine и нескольких функций для построения матриц преобразования (рекомендуется 3x3, а не 2x3, для простого матричного mul) с учетом всего, что вам нужно: линия через две точки, линия через точку и угол, ...

Christoph Rackwitz 07.03.2024 20:12

@BarışAktaş Спасибо за ответ. У меня есть несколько изображений, форма и размер которых будут меняться. Задача — оценить одинаковое значение для всех изображений в папке. Интересно, какая-нибудь модель или метод поможет это сделать?

Sakura 08.03.2024 01:17

@ChristophRackwitz Спасибо за ответ. У меня есть несколько изображений, форма и размер которых будут меняться. Задача — оценить одинаковое значение для всех изображений в папке. Интересно, какая-нибудь модель или метод поможет это сделать?

Sakura 08.03.2024 01:19

@ChristophRackwitz Не могли бы вы привести мне пример того, как нарисовать линию? Как это поможет мне оценить размер каждой стороны?

Sakura 08.03.2024 03:40

Если ваши изображения всегда «выровнены по осям» (я имею в виду, что края ячеек сетки вертикальны и горизонтальны), вы можете установить пороговое значение изображения и суммировать его по строкам и столбцам, чтобы найти ячейку сетки. Примерно так stackoverflow.com/a/57746189/2836621

Mark Setchell 08.03.2024 11:43
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
7
232
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Чтобы иметь возможность обеспечить точность и стабильность будущих расчетов:

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

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

и это мой код:

import cv2

def estimateThickness(img):
    #Determine if it is a vertical or a horizantal edege
    height,width = img.shape
    if height<=width:
        img = cv2.rotate(img,cv2.ROTATE_90_CLOCKWISE)
    height,width = img.shape

    #Estimate the thickness of sides from various locations
    #and extract min max average thickness
    thicknesess = []
    for nh in range(height//10):
        first,last = None,None
        for nw in range(width):
            # print(nl,ns)
            #Find the first white pixel on the direction 
            if img[10*nh][nw] == 255 and first is None:
                first = nw
            #Find the last white pixel on the direction 
            if img[10*nh][width-nw-1] == 255 and last is None:
                last = width-nw-1
            
            if first is not None and last is not None:
                thicknesess.append(last-first)

    return max(thicknesess),min(thicknesess),sum(thicknesess)/len(thicknesess)


#Read the image
src_image = cv2.imread('img\grid.png')
gray = cv2.cvtColor(src_image,cv2.COLOR_BGR2GRAY)


#Extract the bright part in the image and filter the rest to measure thickness
bright_part = cv2.inRange(gray,110,255)
bright_part = cv2.morphologyEx(bright_part,cv2.MORPH_OPEN,cv2.getStructuringElement(cv2.MORPH_RECT,(3,3)))
bright_part = cv2.morphologyEx(bright_part,cv2.MORPH_CLOSE,cv2.getStructuringElement(cv2.MORPH_RECT,(15,15)))

#Crop top left bot and right edges from the filtered image
left_edge = bright_part[200:500,180:280]
right_edge = bright_part[200:500,750:850]
top_edge = bright_part[20:120,400:700]
bot_edge = bright_part[580:680,400:700]

#Use the defined function with cropped image
minL,maxL,avgL = estimateThickness(left_edge)
minR,maxR,avgR = estimateThickness(right_edge)
minT,maxT,avgT = estimateThickness(top_edge)
minB,maxB,avgB = estimateThickness(bot_edge)

print('L',minL,maxL,avgL)
print('R',minR,maxR,avgR)
print('T',minT,maxT,avgT)
print('B',minB,maxB,avgB)
    
cv2.imshow('L',left_edge)
cv2.imshow('R',right_edge)
cv2.imshow('T',top_edge)
cv2.imshow('B',bot_edge)

cv2.imshow('Bright Part',bright_part)
cv2.imshow('Source',src_image)
key = cv2.waitKey(0)

Что касается других ваших расчетов, если вы сможете объяснить их подробнее, я постараюсь вам помочь.

Я ценю вашу помощь. Я попробую вашу идею.

Sakura 12.03.2024 03:25

Что касается других расчетов, если вам нужна помощь, я могу попробовать.

Barış Aktaş 18.03.2024 07:00

Сегментация изображений

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

Ширина ячейки сетки

Вторая подзадача — определение ширины ячейки сетки. Используемый здесь подход заключается в том, чтобы найти площадь большого отрицательного пространства посередине, а затем вычислить квадратный корень, чтобы получить ширину в пикселях: 519,1 пикселей.

Ширина линии сетки

Третья подзадача — найти ширину линий сетки. Основная идея состоит в том, чтобы создать чистую маску всех линий сетки, скелетонизировать эту маску, а затем разделить количество ненулевых пикселей маски на количество пикселей скелета. Однако скелет далек от совершенства, так как концы каждой линии сетки прорисованы недостаточно хорошо. Вместо этого мы обрабатываем каждый прямо идентифицируемый сегмент линии сетки отдельно. Конкретные шаги, предпринятые здесь, таковы:

  1. Изолируйте и очистите маску сетки.
  2. Скелетируйте маску сетки.
  3. Преобразуйте ненулевые пиксели маски скелета в граф связности, в котором узлы соединены, если соответствующие ненулевые пиксели соседствуют друг с другом.
  4. Найдите все цепи в графе связности.
  5. Выделите сегмент сетки, соответствующий каждой цепочке.
  6. Подсчитайте количество ненулевых пикселей в сегменте и разделите на длину цепочки, чтобы определить среднюю ширину сегмента сетки: 50,5 пикселей.

Ширина шва

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

Код

import numpy as np
from matplotlib import pyplot as plt
import networkx as nx

from itertools import product
from skimage.io import imread
from skimage.filters import threshold_multiotsu
from skimage.morphology import (
    binary_opening,
    binary_closing,
    skeletonize,
)
from skimage.measure import (
    label,
    find_contours,
    centroid,
    profile_line,
)
from skimage.draw import polygon2mask


def to_graph(skeleton_image, connectivity=8):
    """Convert a skeleton image to a networkx graph object."""
    perpendicular_steps = set([(-1, 0), (1, 0), (0, -1), (0, 1)])
    diagonal_steps = set([(-1, 1), (-1, -1), (1, -1), (1, 1)])
    if connectivity == 8:
        allowed_steps = perpendicular_steps.union(diagonal_steps)
    elif connectivity == 4:
        allowed_steps = perpendicular_steps
    else:
        raise ValueError(f"The parameter connectivity is either 4 or 8 not {connectivity}.")

    nodes = list(zip(*np.where(skeleton_image)))
    edges = [((x, y), (x+dx, y+dy)) for (x, y), (dx, dy) in product(nodes, allowed_steps) if (x+dx, y+dy) in nodes]

    return nx.Graph(edges)


def get_orthogonal_unit_vector(v):
    """Determine the orthogonal unit vector to a given vector.

    Parameters
    ----------
    v : numpy.array
        The input vector.

    Returns
    -------
    w : numpy.array
        The output vector.

    Notes
    -----
    Adapted from https://stackoverflow.com/a/16890776/2912349

    """
    v = np.atleast_2d(v)
    if not np.all(np.isclose(v, 0)):
        v = v / np.linalg.norm(v, axis=-1)[:, None] # unit vector
        w = np.c_[-v[:,1], v[:,0]]                  # orthogonal vector
        w = w / np.linalg.norm(w, axis=-1)[:, None] # orthogonal unit vector
        return np.squeeze(w)
    else:
        raise ValueError("Cannot determine the orthogonal vector. Input vector has zero length.")


if __name__ == "__main__":

    img = imread("~/wdir/tmp/grid.jpg")
    bw = img.mean(axis=-1)

    # --------------------------------------------------------------------------------
    # image decomposition

    fig, axes = plt.subplots(1, 3, figsize=(10, 5))
    axes = axes.ravel()
    axes[0].imshow(bw, cmap = "gray")
    axes[1].hist(bw.ravel(), bins=100)*2
    thresholds = threshold_multiotsu(bw)
    for threshold in thresholds:
        axes[1].axvline(threshold, color = "k", linestyle = "--")
    axes[1].set_xlabel("Pixel intensity")
    axes[1].set_ylabel("Count")
    regions = np.digitize(bw, bins=thresholds)
    axes[2].imshow(regions, cmap = "bwr")
    axes[1].set_aspect("auto")
    fig.tight_layout()

    # --------------------------------------------------------------------------------
    # determine grid cell width by finding the area of the largest negative space (LNS)

    fig, axes = plt.subplots(1, 3, figsize=(10, 5))
    axes = axes.ravel()

    negative_space = regions < regions.max()
    cleaned = binary_opening(negative_space, np.ones((25, 25), dtype=bool))
    axes[0].imshow(cleaned, cmap = "gray")

    labelled = label(cleaned)
    largest_negative_space_label = np.argmax(np.bincount(labelled.ravel())[1:]) + 1
    largest_negative_space_mask = labelled == largest_negative_space_label
    axes[1].imshow(largest_negative_space_mask, cmap = "gray")

    lns_contour = sorted(find_contours(largest_negative_space_mask), key=lambda x: len(x))[-1]
    axes[2].imshow(bw, cmap = "gray")
    axes[2].plot(lns_contour[:, 1], lns_contour[:, 0], color = "blue")

    area = largest_negative_space_mask.sum()
    interior_width = np.sqrt(area)
    print(f"Grid cell interior width: {interior_width:.1f} pixels")

    lns_centroid = centroid(largest_negative_space_mask)
    row, col = lns_centroid
    x = [col - interior_width/2, col + interior_width/2]
    y = [row, row]
    axes[2].plot(x, y, color = "yellow")

    fig.tight_layout()

    # --------------------------------------------------------------------------------
    # determine grid line width

    fig, axes = plt.subplots(2, 2)
    axes = axes.ravel()
    axes[0].imshow(bw, cmap = "gray")

    mask_gridline = regions == regions.max()
    axes[1].imshow(mask_gridline, cmap = "gray")

    # morphological cleaning
    d = 20
    selem = np.ones((d, d), dtype=bool)
    clean_gridline = binary_opening(binary_closing(mask_gridline, selem), selem)

    # skeletonize
    skeleton = skeletonize(clean_gridline)

    axes[2].imshow(clean_gridline.astype(int) + skeleton.astype(int), cmap = "gray")
    axes[3].imshow(clean_gridline.astype(int) + skeleton.astype(int), cmap = "gray")

    # get a first estimate of the width
    area = clean_gridline.sum()
    length = skeleton.sum()
    estimate = area / length

    # decompose skeleton into chains and estimate area of each chain
    g = to_graph(skeleton)
    chains = list(nx.connected_components(nx.subgraph(g, [node for node, degree in g.degree() if degree == 2])))
    chain_widths = []
    chain_lengths = []
    for chain in chains:
        if len(chain) > 100: # exclude short chains
            # ensure nodes in chain are correctly ordered
            start, stop = [node for node, degree in nx.subgraph(g, chain).degree() if degree == 1]
            chain = nx.shortest_path(g, start, stop)
            chain = np.array(list(chain))

            # find a polygon enclosing the grid segment corresponding to the chain
            start = chain[0]
            stop = chain[-1]
            delta = stop - start
            v = get_orthogonal_unit_vector(np.atleast_2d(delta)) * (1.5 * estimate / 2)
            polygon = np.array([start - v, start + v, stop + v, stop - v], dtype=int)
            polygon_mask = polygon2mask(bw.shape, polygon)

            # determine segment area and average width
            area = clean_gridline[polygon_mask].sum()
            length = np.linalg.norm(delta)
            width = area / length
            chain_widths.append(width)
            chain_lengths.append(length)

            # plot individual estimates
            color = np.random.rand(3)
            axes[3].plot(chain[:, 1], chain[:, 0], color=color)
            axes[3].plot(np.r_[polygon[:, 1], polygon[0, 1]], np.r_[polygon[:, 0], polygon[0, 0]], color=color)
            r, c = chain[int(len(chain) / 2)]
            dr, dc = get_orthogonal_unit_vector(np.atleast_2d(delta)) * width / 2
            axes[3].plot([c - dc, c + dc], [r - dr, r + dr], color=color)

    # estimate mean width weighted by chain length
    gridline_width = np.sum(np.array(chain_widths) * np.array(chain_lengths) / np.sum(chain_lengths).astype(float))
    print(f"Grid line width: {gridline_width:.1f} pixels")

        # --------------------------------------------------------------------------------
    # determine seam widths

    seam = regions == 1

    fig, axes = plt.subplots(2, 2)
    axes = axes.ravel()
    axes[0].imshow(seam, cmap = "gray")

    # isolate seam within largest grid cell
    clean_seam = seam.copy()
    clean_seam[clean_gridline] = 0
    clean_seam[~largest_negative_space_mask] = 0
    axes[1].imshow(clean_seam, cmap = "gray")

    # walk around contour; determine seam widths
    seam_thickness = []
    src = lns_centroid
    for ii, idx in enumerate(np.linspace(0, len(lns_contour), 72)[:-1]): # i.e. every 5 degrees
        dst = lns_contour[int(idx)]
        color = np.random.rand(3)
        if (ii % 2) == 0: # i.e. every 10 degrees
            axes[1].plot([src[1], dst[1]], [src[0], dst[0]], color=color)
        y = profile_line(clean_seam, src, dst)
        seam_thickness.append(np.sum(y))

    axes[2].plot(y, color=color)
    axes[2].set_ylabel("Intensity")
    axes[2].set_xlabel("Line length")
    axes[2].set_title("Example profile line")
    axes[3].hist(seam_thickness)
    axes[3].set_ylabel("Count")
    axes[3].set_xlabel("Seam thickness [pixel]")
    fig.tight_layout()

    plt.show()

Вы действительно спасатель!! Я ценю вашу помощь. Я попробую вашу идею.

Sakura 12.03.2024 03:27

Интересно, существуют ли какие-либо альтернативные модели/методы для получения функций из этих типов изображений (количество изображений = 200), где размер и форма ячейки сетки различаются для каждого изображения? Помимо размера сетки, есть ли какие-либо другие особенности, которые мы могли бы извлечь из подобных изображений?

Sakura 13.03.2024 05:51

Такие вопросы выходят за рамки stackoverflow.

Paul Brodersen 13.03.2024 17:01

Хорошо. Я за неудобства. Еще раз спасибо за решение

Sakura 14.03.2024 00:46

Просто чтобы убедиться, что я правильно вас понял. Вам нужен совет по определению дополнительных объектов или вы просто хотите вычислить те же размеры расстояния/размера на других изображениях с другими формами сетки? Написанный мною код должен работать и для других фигур сетки.

Paul Brodersen 14.03.2024 11:14

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

Sakura 14.03.2024 14:47

Первая цель — расплывчатость для SO. По теме было бы: «Как извлечь x из изображения y. Я попробовал abc, но по определенным причинам это не сработало». (То есть так же, как сформулирован ваш пост).

Paul Brodersen 14.03.2024 16:16

В своем коде я называю слой, отмеченный синим и зеленым, «швом». Код должен работать для различных сеток, если распределение интенсивности пикселей хорошо разделяется по структуре (сетка/шов/негативное пространство) - как это показано на опубликованном вами изображении.

Paul Brodersen 14.03.2024 16:18

Другие вопросы по теме