Spaces:
Running
Running
Upload 2 files
Browse files- cv2_utils.py +174 -0
- inference_utils.py +132 -0
cv2_utils.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from scipy.spatial import distance as dist
|
| 3 |
+
from imutils import perspective
|
| 4 |
+
from imutils import contours
|
| 5 |
+
import numpy as np
|
| 6 |
+
import argparse
|
| 7 |
+
import imutils
|
| 8 |
+
import cv2
|
| 9 |
+
from functools import reduce
|
| 10 |
+
from scipy.interpolate import interp1d
|
| 11 |
+
import math
|
| 12 |
+
|
| 13 |
+
def midpoint(ptA, ptB):
|
| 14 |
+
return ((ptA[0] + ptB[0]) * 0.5, (ptA[1] + ptB[1]) * 0.5)
|
| 15 |
+
|
| 16 |
+
def extract_bboxes(fused):
|
| 17 |
+
"""Compute bounding boxes from masks.
|
| 18 |
+
mask: [height, width]..
|
| 19 |
+
Returns: bbox array [num_instances, (y1, x1, y2, x2)].
|
| 20 |
+
"""
|
| 21 |
+
mask = cv2.cvtColor(fused, cv2.COLOR_BGR2GRAY)
|
| 22 |
+
mask[mask < 40] = 0
|
| 23 |
+
mask[mask >= 40] = 1
|
| 24 |
+
mask = mask.reshape(256, 256, 1)
|
| 25 |
+
boxes = np.zeros([mask.shape[-1], 4], dtype=np.int32)
|
| 26 |
+
for i in range(mask.shape[-1]):
|
| 27 |
+
m = mask[:, :, i]
|
| 28 |
+
# Bounding box.
|
| 29 |
+
horizontal_indicies = np.where(np.any(m, axis=0))[0]
|
| 30 |
+
vertical_indicies = np.where(np.any(m, axis=1))[0]
|
| 31 |
+
if horizontal_indicies.shape[0]:
|
| 32 |
+
x1, x2 = horizontal_indicies[[0, -1]]
|
| 33 |
+
y1, y2 = vertical_indicies[[0, -1]]
|
| 34 |
+
# x2 and y2 should not be part of the box. Increment by 1.
|
| 35 |
+
x2 += 1
|
| 36 |
+
y2 += 1
|
| 37 |
+
else:
|
| 38 |
+
# No mask for this instance. Might happen due to
|
| 39 |
+
# resizing or cropping. Set bbox to zeros
|
| 40 |
+
x1, x2, y1, y2 = 0, 0, 0, 0
|
| 41 |
+
boxes[i] = np.array([y1, x1, y2, x2])
|
| 42 |
+
return boxes.astype(np.int32)
|
| 43 |
+
|
| 44 |
+
def getContours(npImage, overlay_img, realHeight, realWidth, unit, confidence, angle_th=30):
|
| 45 |
+
# load the image, convert it to grayscale, and blur it slightly
|
| 46 |
+
image = npImage.copy()#cv2.imread(imagePath)
|
| 47 |
+
# image size
|
| 48 |
+
imgHeight = image.shape[0]
|
| 49 |
+
imgWidth = image.shape[1]
|
| 50 |
+
|
| 51 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 52 |
+
gray = cv2.GaussianBlur(gray, (3, 3), 0)
|
| 53 |
+
|
| 54 |
+
# perform edge detection, then perform a dilation + erosion to
|
| 55 |
+
# close gaps in between object edges
|
| 56 |
+
edged = cv2.Canny(gray, 50, 80)
|
| 57 |
+
|
| 58 |
+
edged = cv2.dilate(edged, None, iterations=1)
|
| 59 |
+
edged = cv2.erode(edged, None, iterations=1)
|
| 60 |
+
# find contours in the edge map
|
| 61 |
+
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
|
| 62 |
+
cv2.CHAIN_APPROX_SIMPLE)
|
| 63 |
+
#cv2.drawContours(image=overlay_img, contours=cnts[0], contourIdx=-1, color=(0, 255, 0), thickness=2, lineType=cv2.LINE_AA)
|
| 64 |
+
cnts = imutils.grab_contours(cnts)
|
| 65 |
+
|
| 66 |
+
# sort the contours from left-to-right and initialize the
|
| 67 |
+
# 'pixels per metric' calibration variable
|
| 68 |
+
(cnts, _) = contours.sort_contours(cnts)
|
| 69 |
+
pixelsPerMetricHeight = realHeight/imgHeight
|
| 70 |
+
pixelsPerMetricWidth = realWidth/imgWidth
|
| 71 |
+
#draw bounding box
|
| 72 |
+
y1, x1, y2, x2 = extract_bboxes(npImage)[0]
|
| 73 |
+
cv2.rectangle(overlay_img,(x1,y1),(x2, y2),(0,255,0),2)
|
| 74 |
+
# loop over the contours individually
|
| 75 |
+
|
| 76 |
+
for c in cnts:
|
| 77 |
+
# if the contour is not sufficiently large, ignore it
|
| 78 |
+
if cv2.contourArea(c) < 100:
|
| 79 |
+
continue
|
| 80 |
+
# compute the rotated bounding box of the contour
|
| 81 |
+
orig = overlay_img.copy()
|
| 82 |
+
box = cv2.minAreaRect(c)
|
| 83 |
+
box = cv2.boxPoints(box)
|
| 84 |
+
box = np.array(box, dtype="int")
|
| 85 |
+
# order the points in the contour such that they appear
|
| 86 |
+
# in top-left, top-right, bottom-right, and bottom-left
|
| 87 |
+
# order, then draw the outline of the rotated bounding
|
| 88 |
+
# box
|
| 89 |
+
box = perspective.order_points(box)
|
| 90 |
+
|
| 91 |
+
# unpack the ordered bounding box, then compute the midpoint
|
| 92 |
+
# between the top-left and top-right coordinates, followed by
|
| 93 |
+
# the midpoint between bottom-left and bottom-right coordinates
|
| 94 |
+
(tl, tr, br, bl) = box
|
| 95 |
+
(tltrX, tltrY) = midpoint(tl, tr)
|
| 96 |
+
(blbrX, blbrY) = midpoint(bl, br)
|
| 97 |
+
# compute the midpoint between the top-left and top-right points,
|
| 98 |
+
# followed by the midpoint between the top-righ and bottom-right
|
| 99 |
+
(tlblX, tlblY) = midpoint(tl, bl)
|
| 100 |
+
(trbrX, trbrY) = midpoint(tr, br)
|
| 101 |
+
|
| 102 |
+
# draw lines between the midpoints
|
| 103 |
+
#cv2.line(orig, (int(tltrX), int(tltrY)), (int(blbrX), int(blbrY)),
|
| 104 |
+
#(255, 0, 0), 1)
|
| 105 |
+
cv2.line(orig, (int(tlblX), int(tlblY)), (int(trbrX), int(trbrY)),
|
| 106 |
+
(0, 0, 255), 1)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
top_p = min([(int(tlblX), int(tlblY)), (int(trbrX), int(trbrY))], key=lambda x : x[1])
|
| 110 |
+
bot_p = max([(int(tlblX), int(tlblY)), (int(trbrX), int(trbrY))], key=lambda x : x[1])
|
| 111 |
+
D_ad = ((top_p[1] - bot_p[1]) ** 2 + (top_p[0] - bot_p[0])**2) ** 0.5 + 1e-7
|
| 112 |
+
|
| 113 |
+
P1 = min(top_p, bot_p, key=lambda x:x[0])
|
| 114 |
+
P2 = max(top_p, bot_p, key=lambda x:x[0])
|
| 115 |
+
slope = (P1[1] - P2[1]) / (P2[0] - P1[0]) if (P2[0] - P1[0]) != 0 else 0
|
| 116 |
+
cat = ''
|
| 117 |
+
angle = 0
|
| 118 |
+
if slope > 0:
|
| 119 |
+
angle = np.arccos((top_p[0] - bot_p[0])/D_ad) * 180 / math.pi
|
| 120 |
+
cv2.putText(orig, "angle={:.1f}".format(angle),
|
| 121 |
+
(max(top_p[0]-100, 0), top_p[1] + 15), cv2.FONT_HERSHEY_DUPLEX,
|
| 122 |
+
0.45, (0, 0, 255), 1)
|
| 123 |
+
else:
|
| 124 |
+
angle = np.arccos((bot_p[0] - top_p[0])/D_ad) * 180 / math.pi
|
| 125 |
+
cv2.putText(orig, "angle={:.1f}".format(angle),
|
| 126 |
+
(top_p[0], top_p[1] + 15), cv2.FONT_HERSHEY_DUPLEX,
|
| 127 |
+
0.45, (0, 0, 255), 1)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# compute the Euclidean distance between the midpoints
|
| 131 |
+
dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
|
| 132 |
+
dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
|
| 133 |
+
length = cv2.arcLength(c, True) / 2. * pixelsPerMetricWidth
|
| 134 |
+
M = cv2.moments(c)
|
| 135 |
+
cX = int(M["m10"] / M["m00"])
|
| 136 |
+
cY = int(M["m01"] / M["m00"])
|
| 137 |
+
|
| 138 |
+
# width
|
| 139 |
+
mask = gray.copy()
|
| 140 |
+
mask[mask < 40] = 0
|
| 141 |
+
width = cv2.countNonZero(mask[cY][:])
|
| 142 |
+
right_most_x = np.max(np.nonzero(mask[cY][:]))
|
| 143 |
+
left_most_x = np.min(np.nonzero(mask[cY][:]))
|
| 144 |
+
cv2.line(orig, (int(left_most_x), int(cY)), (int(right_most_x), int(cY)),
|
| 145 |
+
(0, 0, 255), 1)
|
| 146 |
+
width *= pixelsPerMetricWidth
|
| 147 |
+
# compute the size of the object
|
| 148 |
+
dimA = dA * pixelsPerMetricHeight
|
| 149 |
+
dimB = dB * pixelsPerMetricWidth
|
| 150 |
+
if angle < angle_th:
|
| 151 |
+
cat +='H'
|
| 152 |
+
cv2.putText(orig, "L={:.1f}".format(length) + unit,
|
| 153 |
+
(int(tltrX), int(tltrY) + 40), cv2.FONT_HERSHEY_DUPLEX,
|
| 154 |
+
0.45, (0, 0, 255), 1)
|
| 155 |
+
cv2.putText(orig, "W={:.1f}".format(width) + unit,
|
| 156 |
+
(int(tltrX), int(tltrY) + 55), cv2.FONT_HERSHEY_DUPLEX,
|
| 157 |
+
0.45, (0, 0, 255), 1)
|
| 158 |
+
else:
|
| 159 |
+
cat += 'V'
|
| 160 |
+
cv2.putText(orig, "L={:.1f}".format(length) + unit,
|
| 161 |
+
(int(tltrX), int(tltrY)), cv2.FONT_HERSHEY_DUPLEX,
|
| 162 |
+
0.45, (0, 0, 255), 1)
|
| 163 |
+
cv2.putText(orig, "W={:.1f}".format(width) + unit,
|
| 164 |
+
(int(tltrX), int(tltrY) + 15), cv2.FONT_HERSHEY_DUPLEX,
|
| 165 |
+
0.45, (0, 0, 255), 1)
|
| 166 |
+
if slope > 0:
|
| 167 |
+
cat += 'L'
|
| 168 |
+
else:
|
| 169 |
+
cat += 'R'
|
| 170 |
+
cv2.putText(orig, "Crack {:.2f}%".format(confidence.item()*100) + " cat="+ cat, (x1, max(0, y1-5)), cv2.FONT_HERSHEY_SIMPLEX, 0.45, (36,255,12), 1)
|
| 171 |
+
|
| 172 |
+
return orig
|
| 173 |
+
|
| 174 |
+
|
inference_utils.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import cv2
|
| 4 |
+
import torch
|
| 5 |
+
import numpy as np
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
from .cv2_utils import getContours
|
| 9 |
+
import torchvision.transforms as transforms
|
| 10 |
+
from .models.deepcrack_model import DeepCrackModel
|
| 11 |
+
|
| 12 |
+
def tensor2im(input_image, imtype=np.uint8):
|
| 13 |
+
""""Converts a Tensor array into a numpy image array.
|
| 14 |
+
|
| 15 |
+
Parameters:
|
| 16 |
+
input_image (tensor) -- the input image tensor array
|
| 17 |
+
imtype (type) -- the desired type of the converted numpy array
|
| 18 |
+
"""
|
| 19 |
+
if not isinstance(input_image, np.ndarray):
|
| 20 |
+
if isinstance(input_image, torch.Tensor): # get the data from a variable
|
| 21 |
+
image_tensor = input_image.data
|
| 22 |
+
else:
|
| 23 |
+
return input_image
|
| 24 |
+
image_numpy = image_tensor[0].cpu().float().numpy() # convert it into a numpy array
|
| 25 |
+
if image_numpy.shape[0] == 1: # grayscale to RGB
|
| 26 |
+
image_numpy = np.tile(image_numpy, (3, 1, 1))
|
| 27 |
+
image_numpy = (np.transpose(image_numpy, (1, 2, 0)) + 1) / 2.0 * 255.0 # post-processing: tranpose and scaling
|
| 28 |
+
else: # if it is a numpy array, do nothing
|
| 29 |
+
image_numpy = input_image
|
| 30 |
+
return image_numpy.astype(imtype)
|
| 31 |
+
|
| 32 |
+
def bytes_to_array(b: bytes) -> np.ndarray:
|
| 33 |
+
np_bytes = BytesIO(b)
|
| 34 |
+
return np.load(np_bytes, allow_pickle=True)
|
| 35 |
+
|
| 36 |
+
def read_image(bytesImg, dim=(256, 256)): #Decode Bytes to array
|
| 37 |
+
img_transforms = transforms.Compose([transforms.ToTensor(),
|
| 38 |
+
transforms.Normalize((0.5, 0.5, 0.5),
|
| 39 |
+
(0.5, 0.5, 0.5))])
|
| 40 |
+
img = np.fromstring(bytesImg, np.uint8)
|
| 41 |
+
img = cv2.imdecode(img, cv2.IMREAD_COLOR)
|
| 42 |
+
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 43 |
+
|
| 44 |
+
# adjust the image size
|
| 45 |
+
w, h = dim
|
| 46 |
+
if w > 0 or h > 0:
|
| 47 |
+
img = cv2.resize(img, (w, h), interpolation=cv2.INTER_CUBIC)
|
| 48 |
+
|
| 49 |
+
# apply the transform to both A and B
|
| 50 |
+
img = img_transforms(Image.fromarray(img.copy()))
|
| 51 |
+
return img
|
| 52 |
+
|
| 53 |
+
def create_model(opt, cp_path='pretrained_net_G.pth'):
|
| 54 |
+
model = DeepCrackModel(opt) # create a model given opt.model and other options
|
| 55 |
+
checkpoint = torch.load(cp_path)
|
| 56 |
+
if hasattr(model.netG, 'module'):
|
| 57 |
+
model.netG.module.load_state_dict(checkpoint, strict=False)
|
| 58 |
+
else:
|
| 59 |
+
model.netG.load_state_dict(checkpoint, strict=False)
|
| 60 |
+
model.eval()
|
| 61 |
+
return model
|
| 62 |
+
|
| 63 |
+
def overlay(
|
| 64 |
+
image: np.ndarray,
|
| 65 |
+
mask: np.ndarray,
|
| 66 |
+
color = (255, 0, 0),
|
| 67 |
+
alpha: float = 0.5,
|
| 68 |
+
resize = (256, 256)
|
| 69 |
+
) -> np.ndarray:
|
| 70 |
+
"""Combines image and its segmentation mask into a single image.
|
| 71 |
+
|
| 72 |
+
Params:
|
| 73 |
+
image: Training image.
|
| 74 |
+
mask: Segmentation mask.
|
| 75 |
+
color: Color for segmentation mask rendering.
|
| 76 |
+
alpha: Segmentation mask's transparency.
|
| 77 |
+
resize: If provided, both image and its mask are resized before blending them together.
|
| 78 |
+
|
| 79 |
+
Returns:
|
| 80 |
+
image_combined: The combined image.
|
| 81 |
+
|
| 82 |
+
"""
|
| 83 |
+
color = np.asarray(color).reshape(1, 1, 3)
|
| 84 |
+
colored_mask = np.expand_dims(mask, 0).repeat(3, axis=2)
|
| 85 |
+
masked = np.ma.MaskedArray(image, mask=colored_mask, fill_value=color)
|
| 86 |
+
image_overlay = masked.filled()
|
| 87 |
+
|
| 88 |
+
if resize is not None:
|
| 89 |
+
image = cv2.resize(image, resize)
|
| 90 |
+
image_overlay = cv2.resize(image_overlay, resize)
|
| 91 |
+
|
| 92 |
+
image_combined = cv2.addWeighted(image, 1 - alpha, image_overlay, alpha, 0)
|
| 93 |
+
|
| 94 |
+
return image_combined
|
| 95 |
+
|
| 96 |
+
def inference(model, bytesImg, dim, unit):
|
| 97 |
+
#print(img_path)
|
| 98 |
+
|
| 99 |
+
image = read_image(bytesImg) #Read Array
|
| 100 |
+
# batchify
|
| 101 |
+
image = image.unsqueeze(0)
|
| 102 |
+
# hacky way to pass ground truth label
|
| 103 |
+
model.set_input({'image': image, 'label': torch.zeros_like(image), 'A_paths':''})
|
| 104 |
+
model.test() # run inference
|
| 105 |
+
visuals = model.get_current_visuals() # get image results
|
| 106 |
+
confidence = visuals['fused'].max()
|
| 107 |
+
|
| 108 |
+
# fused for final prediction
|
| 109 |
+
for key in visuals.keys():
|
| 110 |
+
visuals[key] = tensor2im(visuals[key])
|
| 111 |
+
|
| 112 |
+
h, w, _ = visuals['fused'].shape
|
| 113 |
+
fused = Image.fromarray(visuals['fused'])
|
| 114 |
+
fused = np.array(fused, dtype='uint8')
|
| 115 |
+
realHeight=dim[1]
|
| 116 |
+
realWidth=dim[0]
|
| 117 |
+
|
| 118 |
+
mask = cv2.cvtColor(fused, cv2.COLOR_BGR2GRAY)
|
| 119 |
+
mask[mask < 90] = 0
|
| 120 |
+
mask[mask >= 90] = 255
|
| 121 |
+
cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL,
|
| 122 |
+
cv2.CHAIN_APPROX_SIMPLE)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
overlay_img = overlay(tensor2im(image), mask, alpha=0)
|
| 126 |
+
cv2.drawContours(image=overlay_img, contours=cnts[0], contourIdx=-1, color=(0, 255, 0), thickness=1, lineType=cv2.LINE_AA)
|
| 127 |
+
contour_img = getContours(fused, overlay_img, realHeight, realWidth, unit, confidence)
|
| 128 |
+
|
| 129 |
+
return contour_img if contour_img is not None else overlay_img, visuals
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
|