Read water without sending

This commit is contained in:
Simon Milvert 2021-01-09 22:06:57 +01:00
parent 7aa69e63ac
commit 738161b0da
2 changed files with 225 additions and 239 deletions

View File

@ -1,239 +0,0 @@
import math
import sys
import cv2
import os
import numpy as np
class WaterMeter(object):
def __init__(self, url, img=None, debug=False):
self.url = url
self.img = img
self.debug = debug
def read(self):
if self.img:
img = cv2.imread(self.img)
return img
def loop(self):
img = self.read()
needle = self.find_red_needle(img)
#needle = self.get_contours(needle, img)
dials = self.find_circle(img)
for dial in dials:
ulx = int(dial[0])
uly = int(dial[1])
radius = int(dial[2]) * 3
cut = img[uly - radius: uly + radius,
ulx - radius: ulx + radius]
needle_cut = needle[uly - radius: uly + radius,
ulx - radius: ulx + radius]
self.findAngle(cut, needle_cut, (radius, radius), radius * 2)
if self.debug:
cv2.imshow('image', img)
cv2.waitKey(0)
@staticmethod
def line(p1, p2):
A = (p1[1] - p2[1])
B = (p2[0] - p1[0])
C = (p1[0] * p2[1] - p2[0] * p1[1])
return A, B, -C
@staticmethod
def intersection(L1, L2):
D = L1[0] * L2[1] - L1[1] * L2[0]
Dx = L1[2] * L2[1] - L1[1] * L2[2]
Dy = L1[0] * L2[2] - L1[2] * L2[0]
if D != 0:
x = Dx / D
y = Dy / D
return x, y
else:
return False
def find_circle(self, img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gradient = cv2.HOUGH_GRADIENT
# Find suitable number by running detect_circle.py
circles = cv2.HoughCircles(img, gradient, 1.2, 40,
param1=50, param2=50, minRadius=50,
maxRadius=100)
if circles is not None:
circles = np.round(circles[0, :]).astype("int")
dials = []
for (x, y, r) in circles:
dials.append([x, y, r])
if self.debug:
# draw the circle
cv2.circle(img, (x, y), r, (0, 255, 0), 4)
# draw a rectangle in the middle
cv2.rectangle(img, (x - 5, y - 5), (x + 5, y + 5),
(0, 128, 255),
-1)
if self.debug:
cv2.imshow('circle', img)
cv2.waitKey(0)
return dials
def find_red_needle(self, img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Find suitable hsv number by running detect_hsv.py
lower_red_hue = self.create_hue_mask(hsv, [0, 100, 100], [10, 255, 255])
higher_red_hue = self.create_hue_mask(hsv, [170, 100, 100], [179, 255, 255])
mask = cv2.bitwise_or(lower_red_hue, higher_red_hue)
needle = cv2.GaussianBlur(mask, (5, 5), 0)
return needle
def get_contours(self, img, org):
ret, thresh = cv2.threshold(img, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
if self.debug:
for contour in contours:
peri = cv2.arcLength(contour, True)
approx = cv2.approxPolyDP(contour, 0.04 * peri, True)
cv2.drawContours(org, [approx], -1, (0, 0, 255), 3)
#cv2.drawContours(org, contours, -1, (0, 255, 0), 3)
cv2.imshow('needle', org)
cv2.waitKey(0)
return contours
@staticmethod
def draw_line(lines, img):
for r, theta in lines[0]:
# Stores the value of cos(theta) in a
a = np.cos(theta)
# Stores the value of sin(theta) in b
b = np.sin(theta)
# x0 stores the value rcos(theta)
x0 = a * r
# y0 stores the value rsin(theta)
y0 = b * r
# x1 stores the rounded off value of (rcos(theta)-1000sin(theta))
x1 = int(x0 + 1000 * (-b))
# y1 stores the rounded off value of (rsin(theta)+1000cos(theta))
y1 = int(y0 + 1000 * (a))
# x2 stores the rounded off value of (rcos(theta)+1000sin(theta))
x2 = int(x0 - 1000 * (-b))
# y2 stores the rounded off value of (rsin(theta)-1000cos(theta))
y2 = int(y0 - 1000 * (a))
# cv2.line draws a line in img from the point(x1,y1) to (x2,y2).
# (0,0,255) denotes the colour of the line to be
# drawn. In this case, it is red.
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
return img
def findAngle(self, img, redimg, center, width):
### lines
edges = cv2.Canny(redimg, 100, 200, apertureSize=3)
lines = cv2.HoughLinesP(image=edges,
rho=1,
theta=np.pi / 90,
threshold=15,
minLineLength=width / 4,
maxLineGap=50)
tip = None
maxlen = 0
if lines is None:
print("No lines found")
cv2.imshow('error', img)
cv2.waitKey()
cv2.destroyAllWindows()
else:
pl = None
img = self.draw_line(lines, img)
cv2.imshow('error', img)
cv2.waitKey()
cv2.destroyAllWindows()
# print "%d lines" % len(lines)
for x in range(0, len(lines)):
for x1, y1, x2, y2 in lines[x]:
l = self.line([x1, y1], [x2, y2]);
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
# print "line: %d,%d %d,%d" % (x1, y1, x2, y2)
# see if it intersects any other line
for y in range(0, len(lines)):
for xx1, yy1, xx2, yy2 in lines[y]:
l2 = self.line([xx1, yy1], [xx2, yy2]);
if l2 is l:
continue
r = self.intersection(l, l2)
if r and r[0] > 0 and r[1] > 0:
dist = math.sqrt((r[0] - center[0]) ** 2 + (
r[1] - center[1]) ** 2)
# print "intersection %d,%d at distance %d" % ( r[0], r[1], dist)
# cv2.circle(img,(r[0],r[1]),2,(0,0,255),2)
if dist > maxlen and dist < width / 2:
tip = r
maxlen = dist
# print "chosen intersection: %d,%d at distance %d" % ( tip[0], tip[1], maxlen)
#cv2.line(img, (tip[0], tip[1]), (center[0], center[1]), (255, 0, 255),
# 2)
xlen = tip[0] - center[0]
ylen = center[1] - tip[1]
rad = math.atan2(ylen, xlen)
deg = math.degrees(rad)
# print "angle deg:", deg
# print "angle rad:", rad
if deg < 0:
percent = (90 + abs(deg)) / 360
elif deg < 90:
percent = (90 - deg) / 360
else:
percent = (450 - deg) / 360
# print "percent", math.trunc(percent * 100)
string = "%d%%" % math.trunc(percent * 100)
cv2.putText(img, string, (center[0] - width / 5, center[1] - width / 3),
cv2.FONT_HERSHEY_SIMPLEX, 0.7,
(255, 255, 255), 2)
return math.trunc(percent * 100)
@staticmethod
def create_hue_mask(image, lower_color, upper_color):
lower = np.array(lower_color, np.uint8)
upper = np.array(upper_color, np.uint8)
# Create a mask from the colors
mask = cv2.inRange(image, lower, upper)
return mask
if __name__ == '__main__':
water_meter = WaterMeter('', img='capture_1.jpg', debug=True)
water_meter.loop()

225
water_meter/water_meter.py Executable file
View File

@ -0,0 +1,225 @@
import math
import sys
import time
import cv2
import os
import numpy as np
import imutils
from scipy.spatial import distance as dist
class WaterMeter(object):
def __init__(self, url, img=None, debug=False):
self.url = url
self.img = img
self.debug = debug
self.cap = None
self._quit = False
def _init_camera(self):
self.cap = cv2.VideoCapture()
self.cap.open(self.url)
if self.cap is None:
print("Can't access camera")
return None
if not self.cap.isOpened():
print("Can't open camera")
return None
return self.cap
def read(self):
img = cv2.imread(self.img)
self.get_degree(img)
def loop(self):
bad_frame = 0
while not self._quit:
if self._quit:
break
if bad_frame > 100:
if self.cap is not None:
self.cap.release()
self.cap = None
bad_frames = 0
# initializing connection to camera
if self.cap is None:
self._init_camera()
ret, current_frame = self.cap.read()
# the connection broke, or the stream came to an end
if (not ret) or (current_frame is None):
# ToDo Logging error frame
bad_frame += 1
continue
else:
bad_frame = 0
degree, img = self.get_degree(current_frame)
if degree < 0:
percent = (90 + abs(degree)) / 360
elif degree < 90 and degree != -1:
percent = (90 - degree) / 360
else:
percent = (450 - degree) / 360
print(f"Degree is {degree} and percent is {percent * 100}")
cv2.imshow("Degree", current_frame)
cv2.waitKey(1)
def stop(self):
self._quit = True
if self.cap is not None:
self.cap.release()
def get_degree(self, frame):
img = frame
dials = self.find_circle(img)
# Init value out of range
deg = -1
dial_one_litre = []
if not dials:
return -1, img
for dial in dials:
# Using only one circle (1 litre) left bottom
if not dial_one_litre:
dial_one_litre = dial
if dial[0] < dial_one_litre[0]:
dial_one_litre = dial
ulx = int(dial_one_litre[0])
uly = int(dial_one_litre[1])
radius = int(dial_one_litre[2])
# Only using image in dial
cut = img[uly - radius: uly + radius, ulx - radius: ulx + radius]
# Find needle in dial
needle = self.find_red_needle(cut)
extreme_points = self.get_contours(needle, cut)
if not extreme_points:
return -1, img
deg, point = self.find_angle(extreme_points, cut)
cv2.line(img,
((ulx - radius) + point['x'], uly - radius + point['y']),
(ulx, uly), (0, 255, 255), 2)
if self.debug:
cv2.imshow('image', img)
cv2.waitKey(0)
return deg, img
def find_circle(self, img):
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gradient = cv2.HOUGH_GRADIENT
# Find suitable number by running detect_circle.py
circles = cv2.HoughCircles(img, gradient, dp=1.26, minDist=42,
param1=52, param2=43, minRadius=67,
maxRadius=99)
if circles is not None:
circles = np.round(circles[0, :]).astype("int")
dials = []
for (x, y, r) in circles:
dials.append([x, y, r])
if self.debug:
# draw the circle
cv2.circle(img, (x, y), r, (0, 255, 0), 4)
# draw a rectangle in the middle
cv2.rectangle(img, (x - 5, y - 5), (x + 5, y + 5),
(0, 128, 255),
-1)
if self.debug:
cv2.imshow('circle', img)
cv2.waitKey(0)
return dials
return None
def find_red_needle(self, img):
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# Find suitable hsv number by running detect_hsv.py
lower_red_hue = self.create_hue_mask(hsv, [0, 100, 100], [10, 255, 255])
higher_red_hue = self.create_hue_mask(hsv, [170, 80, 110], [179, 255, 255])
mask = cv2.bitwise_or(lower_red_hue, higher_red_hue)
needle = cv2.GaussianBlur(mask, (5, 5), 0)
if self.debug:
cv2.imshow('circle', needle)
cv2.waitKey(0)
return needle
def get_contours(self, img, org):
ret, thresh = cv2.threshold(img, 127, 255, 0)
contours = cv2.findContours(thresh, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(contours)
try:
c = max(cnts, key=cv2.contourArea)
except ValueError:
print("Not finding any needle")
timestr = time.strftime("%Y%m%d-%H%M%S")
cv2.imwrite('error_neddle' + timestr + '.png', img)
return None
# determine the most extreme points along the contour
ext_left = tuple(c[c[:, :, 0].argmin()][0])
ext_right = tuple(c[c[:, :, 0].argmax()][0])
ext_top = tuple(c[c[:, :, 1].argmin()][0])
ext_bot = tuple(c[c[:, :, 1].argmax()][0])
extreme_points = (ext_left, ext_right, ext_top, ext_bot)
if self.debug:
cv2.drawContours(org, [c], -1, (0, 255, 255), 2)
cv2.circle(org, ext_left, 8, (0, 0, 255), -1)
cv2.circle(org, ext_right, 8, (0, 255, 0), -1)
cv2.circle(org, ext_top, 8, (255, 0, 0), -1)
cv2.circle(org, ext_bot, 8, (255, 255, 0), -1)
# show the output image
cv2.imshow("Image", org)
cv2.waitKey(0)
return extreme_points
@staticmethod
def create_hue_mask(image, lower_color, upper_color):
lower = np.array(lower_color, np.uint8)
upper = np.array(upper_color, np.uint8)
# Create a mask from the colors
mask = cv2.inRange(image, lower, upper)
return mask
def find_angle(self, extreme_points, cut):
length_from_centre = {}
height, width, _ = cut.shape
for (x, y) in extreme_points:
D = dist.euclidean((x, y), (int(height/2), int(width/2)))
if not length_from_centre:
length_from_centre = {'x': x, 'y': y, 'distance': D}
elif D > length_from_centre['distance']:
length_from_centre = {'x': x, 'y': y, 'distance': D}
if self.debug:
cv2.line(cut, (length_from_centre['x'], length_from_centre['y']),
(int(height / 2), int(width / 2)), (0, 0, 255), 2)
cv2.imshow('length', cut)
cv2.waitKey(0)
rad = math.atan2(length_from_centre['y'], length_from_centre['x'])
deg = math.degrees(rad)
return deg, length_from_centre
if __name__ == '__main__':
water_meter = WaterMeter('http://10.0.0.22:81', img='capture_4.png', debug=False)
water_meter.loop()
#water_meqter.read()