【发布时间】:2021-05-06 08:36:02
【问题描述】:
我有一些看起来像这样的二进制(黑白)网格图像:
现在,我想将此类图像转换为常规的 2D NumPy 数组,其中每个单元格必须对应于 0,如果源单元格是白色(或未着色),则必须对应于 1,如果单元格是黑色。也就是说,预期的输出是:
[[0,1,0,0,1],
[0,0,0,0,1],
[0,1,0,0,0],
[0,0,0,0,0],
[0,0,0,0,0],
[0,0,0,1,0],
[0,0,1,0,0]]
我查看了许多建议,包括 this one,但他们没有说明我必须如何将原始像素减少到常规网格。
我当前的代码:
import numpy as np
from PIL import Image
def from_img(imgfile, size, keep_ratio=True, reverse=False):
def resample(img_, size):
return img.resize(size, resample=Image.BILINEAR)
def makebw(img, threshold=200):
edges = (255 if reverse else 0, 0 if reverse else 255)
return img.convert('L').point(lambda x: edges[1] if x > threshold else edges[0], mode='1')
img = Image.open(imgfile)
if keep_ratio:
ratio = max(size) / max(img.size)
size = tuple(int(sz * ratio) for sz in img.size)
return np.array(makebw(resample(img, size)), dtype=int)
此代码可能适用于单元格之间不包含边框的图像,并且仅在手动指定行数和列数时才适用。但我确信必须有一种方法可以通过边缘检测/重采样技术自动执行此例程......
更新
虽然对于上图所示的均匀、规则的黑白网格有很好的解决方案(见下文建议),但对于像这样的具有多种非 BW 颜色的不均匀、嘈杂的图像,这项任务就更加困难了:
我现在正在研究一个 opencv 实现,它检测轮廓并尝试挑选出单元格大小来重建网格矩阵。我当前的代码:
import matplotlib.pyplot as plt
import numpy as np
import cv2
def find_contours(fpath, gray_thresh=150, extent_param=0.85, area_param=(0.0003, 0.3), ratio_param=(0.75, 1.33)):
"""
Finds contours (shapes) in an image (loading it from a file) and filters the contours
according to a number of parameters.
gray_thresh: grayscale threshold
extent_param: minimum extent of contour (see https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_properties/py_contour_properties.html#extent)
area_param: min and max ratio of contour area to image area
ratio_param: min and max ratio of contour (see https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contour_properties/py_contour_properties.html#aspect-ratio)
"""
image = cv2.imread(fpath)
# grayscale image
imgray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(imgray, gray_thresh, 255, 0)
# get all contours (see https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_contours/py_contours_begin/py_contours_begin.html)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# get min and max contour area in pixels (from given ratios)
if area_param:
area = imgray.shape[0] * imgray.shape[1]
min_area = float(area) * area_param[0]
max_area = float(area) * area_param[1]
# filtered contours
contours2 = []
# contour sizes
sizes = []
# contour coords
pos = []
# iterate by found contours
for c in contours:
# get contour area
c_area = cv2.contourArea(c)
# get bounding rect
rect = cv2.boundingRect(c)
# get extent (ratio of contour area to bounding rect area)
extent = float(c_area) / (rect[2] * rect[3])
# get aspect ratio of bounding rect
ratio = float(rect[2]) / rect[3]
# perform filtering (leave rect-shaped contours or filter by extent)
if (len(c) == 4 or not extent_param or extent >= extent_param) and \
(not area_param or (c_area >= min_area and c_area <= max_area)) and \
(not ratio_param or (ratio >= ratio_param[0] and ratio <= ratio_param[1])):
# add filtered contour to list, as well as its size and pos
contours2.append(c)
sizes.append(rect[-2:])
pos.append(rect[:2])
# get most frequent block size (w, h), first and last block
size_mode = max(set(sizes), key=sizes.count)
first_pos = min(pos)
last_pos = max(pos)
# return original image, grayscale image, most frequent contour size, first and last contour coords
return image, imgray, contours2, size_mode, first_pos, last_pos
def get_mean_colors_of_contours(img, imgray, contours):
"""
Returns the mean colors of given contours and one common mean.
"""
l_means = []
for c in contours:
mask = np.zeros(imgray.shape, np.uint8)
cv2.drawContours(mask, [c], 0, 255, -1)
l_means.append(cv2.mean(img, mask=mask)[0])
return np.mean(l_means), l_means
def get_color(x):
if x == 'r':
return (255, 0, 0)
elif x == 'g':
return (0, 255, 0)
elif x == 'b':
return (0, 0, 255)
return x
def text_in_contours(img, contours, values, val_format=None, text_color='b', text_scale=1.0):
"""
Prints stuff inside given contours.
img: original image (array)
contours: identified contours
values: stuff to print (iterable of same length as contours)
val_format: optional callback function to format a single value before printing
text_color: color of output text (default = blue)
text_scale: initial font scale (font will be auto adjusted)
"""
text_color = get_color(text_color)
if not text_color: return
for c, val in zip(contours, values):
rect = cv2.boundingRect(c)
center = (rect[0] + rect[2] // 2, rect[1] + rect[3] // 2)
txt = val_format(val) if val_format else str(val)
if not txt: continue
font = cv2.FONT_HERSHEY_DUPLEX
fontScale = min(rect[2:]) * text_scale / 100
lineType = 1
text_size, _ = cv2.getTextSize(txt, font, fontScale, lineType)
text_origin = (center[0] - text_size[0] // 2, center[1] + text_size[1] // 2)
cv2.putText(img, txt, text_origin, font, fontScale, text_color, lineType, cv2.LINE_AA)
return img
def draw_contours(fpath, contour_color='r', contour_width=1, **kwargs):
"""
Finds contours in image and draws their outlines.
fpath: path to image file
contour_color: color used to outline contours (r,g,b, tuple or None)
contour_width: outline width
kwargs: args passed to find_contours()
"""
if not contour_color: return
contour_color = get_color(contour_color)
img, imgray, contours, size_mode, first_pos, last_pos = find_contours(fpath, **kwargs)
cv2.drawContours(img, contours, -1, contour_color, contour_width)
return img, imgray, contours, size_mode, first_pos, last_pos
def show_image(img, fig_height_inches=8):
"""
Shows an image in iPython notebook.
"""
height, width = img.shape[:2]
aspect = width / height
fig = plt.figure(figsize=(fig_height_inches * aspect, fig_height_inches))
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
ax.imshow(img, interpolation='nearest', aspect='equal')
plt.show()
现在这可以帮助我在大多数情况下识别白细胞,例如
img, imgray, contours, size_mode, first_pos, last_pos = draw_contours('sss4.jpg')
mean_col, cols = get_mean_colors_of_contours(img, imgray, contours)
print(f'mean color = {mean_col}')
on_contour = lambda val: str(int(val)) if (val / mean_col) >= 0.9 else None
img = text_in_contours(img, contours, cols, on_contour)
show_image(img, 15)
输出
mean color = 252.54154936140293
所以,我现在只需要一些方法来重建带有 1 和 0 的网格,在缺失的点(没有识别出白色单元格的地方)添加 1。
【问题讨论】:
标签: python numpy computer-vision python-imaging-library edge-detection