Skip to content

Commit 31637ce

Browse files
authored
Merge pull request #269 from aaronwmorris/dev
use masks instead of extracting ROI for sqm, star, and line detection
2 parents 322a873 + 2d18664 commit 31637ce

9 files changed

Lines changed: 281 additions & 134 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ All configuration is read from /etc/indi-allsky/config.json . You can find conf
245245
| DETECT_STARS | true | (bool) Enable star detection |
246246
| DETECT_STARS_THOLD | 0.6 | (float) Star detection threshold |
247247
| DETECT_METEORS | false | (bool) Enable meteor detection |
248+
| DETECT_MASK | | (str) Image file to use for detection mask |
248249
| DETECT_DRAW | false | (bool) Draw detected objects on original image |
249250
| SQM_ROI | [] | (array) Region of interest for SQM and Star detection |
250251
| LOCATION_LATITUDE | | (float) Your latitude for astrometric calculations |

config.json_template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"DETECT_STARS_THOLD" : 0.6,
5151
"comment_DETECT_METEORS" : "Enable Meteor detection",
5252
"DETECT_METEORS" : false,
53+
"DETECT_MASK" : "",
5354
"comment_DETECT_DRAW" : "Enable drawing detections on original image",
5455
"DETECT_DRAW" : false,
5556
"comment_SQM_ROI" : "Region of Interest for SQM and Star detection",

indi_allsky/detectLines.py

Lines changed: 72 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -21,50 +21,41 @@ class IndiAllskyDetectLines(object):
2121
min_line_length = 40 # minimum number of pixels making up a line
2222
max_line_gap = 20 # maximum gap in pixels between connectable line segments
2323

24-
def __init__(self, config, bin_v):
24+
mask_blur_kernel_size = 75
25+
26+
27+
def __init__(self, config, bin_v, mask=None):
2528
self.config = config
2629
self.bin_v = bin_v
2730

28-
self.x_offset = 0
29-
self.y_offset = 0
30-
31+
self._sqm_mask = mask
32+
self._sqm_gradient_mask = None
3133

32-
def detectLines(self, img):
33-
image_height, image_width = img.shape[:2]
3434

35-
sqm_roi = self.config.get('SQM_ROI', [])
35+
def detectLines(self, original_img):
36+
if isinstance(self._sqm_mask, type(None)):
37+
# This only needs to be done once if a mask is not provided
38+
self._generateSqmMask(original_img)
3639

37-
try:
38-
x1 = int(sqm_roi[0] / self.bin_v.value)
39-
y1 = int(sqm_roi[1] / self.bin_v.value)
40-
x2 = int(sqm_roi[2] / self.bin_v.value)
41-
y2 = int(sqm_roi[3] / self.bin_v.value)
42-
except IndexError:
43-
logger.warning('Using central ROI for line detection')
44-
x1 = int((image_width / 2) - (image_width / 3))
45-
y1 = int((image_height / 2) - (image_height / 3))
46-
x2 = int((image_width / 2) + (image_width / 3))
47-
y2 = int((image_height / 2) + (image_height / 3))
40+
if isinstance(self._sqm_gradient_mask, type(None)):
41+
# This only needs to be done once
42+
self._generateSqmGradientMask(original_img)
4843

4944

50-
self.x_offset = x1
51-
self.y_offset = y1
45+
# apply the gradient to the image
46+
masked_img = (original_img * self._sqm_gradient_mask).astype(numpy.uint8)
5247

53-
roi_img = img[
54-
y1:y2,
55-
x1:x2,
56-
]
5748

58-
if len(img.shape) == 2:
59-
img_gray = roi_img
49+
if len(original_img.shape) == 2:
50+
img_gray = masked_img
6051
else:
61-
img_gray = cv2.cvtColor(roi_img, cv2.COLOR_BGR2GRAY)
52+
img_gray = cv2.cvtColor(masked_img, cv2.COLOR_BGR2GRAY)
6253

6354

6455

6556
lines_start = time.time()
6657

67-
blur_gray = cv2.GaussianBlur(img_gray, (self.blur_kernel_size, self.blur_kernel_size), 0)
58+
blur_gray = cv2.GaussianBlur(img_gray, (self.blur_kernel_size, self.blur_kernel_size), cv2.BORDER_DEFAULT)
6859

6960

7061
edges = cv2.Canny(blur_gray, self.canny_low_threshold, self.canny_high_threshold)
@@ -91,35 +82,74 @@ def detectLines(self, img):
9182

9283
logger.info('Detected %d lines', len(lines))
9384

94-
self._drawLines(img, lines, (x1, y1, x2, y2))
85+
self._drawLines(original_img, lines)
9586

9687
return lines
9788

9889

99-
def _drawLines(self, img, lines, box):
90+
def _generateSqmMask(self, img):
91+
logger.info('Generating mask based on SQM_ROI')
92+
93+
image_height, image_width = img.shape[:2]
94+
95+
# create a black background
96+
mask = numpy.zeros((image_height, image_width), dtype=numpy.uint8)
97+
98+
sqm_roi = self.config.get('SQM_ROI', [])
99+
100+
try:
101+
x1 = int(sqm_roi[0] / self.bin_v.value)
102+
y1 = int(sqm_roi[1] / self.bin_v.value)
103+
x2 = int(sqm_roi[2] / self.bin_v.value)
104+
y2 = int(sqm_roi[3] / self.bin_v.value)
105+
except IndexError:
106+
logger.warning('Using central ROI for blob calculations')
107+
x1 = int((image_width / 2) - (image_width / 3))
108+
y1 = int((image_height / 2) - (image_height / 3))
109+
x2 = int((image_width / 2) + (image_width / 3))
110+
y2 = int((image_height / 2) + (image_height / 3))
111+
112+
# The white area is what we keep
113+
cv2.rectangle(
114+
img=mask,
115+
pt1=(x1, y1),
116+
pt2=(x2, y2),
117+
color=(255), # mono
118+
thickness=cv2.FILLED,
119+
)
120+
121+
# mask needs to be blurred so that we do not detect it as an edge
122+
self._sqm_mask = mask
123+
124+
125+
def _generateSqmGradientMask(self, img):
126+
# blur the mask to prevent mask edges from being detected as lines
127+
blur_mask = cv2.blur(self._sqm_mask, (self.mask_blur_kernel_size, self.mask_blur_kernel_size), cv2.BORDER_DEFAULT)
128+
129+
if len(img.shape) == 2:
130+
# mono
131+
mask = blur_mask
132+
else:
133+
# color
134+
mask = cv2.cvtColor(blur_mask, cv2.COLOR_GRAY2BGR)
135+
136+
self._sqm_gradient_mask = mask / 255
137+
138+
139+
def _drawLines(self, img, lines):
100140
if not self.config.get('DETECT_DRAW'):
101141
return
102142

103143
color_bgr = list(self.config['TEXT_PROPERTIES']['FONT_COLOR'])
104144
color_bgr.reverse()
105145

106-
### box drawn in star detection
107-
#logger.info('Draw box around ROI')
108-
#cv2.rectangle(
109-
# img=img,
110-
# pt1=(box[0], box[1]),
111-
# pt2=(box[2], box[3]),
112-
# color=(128, 128, 128),
113-
# thickness=1,
114-
#)
115-
116146

117147
for line in lines:
118148
for x1, y1, x2, y2 in line:
119149
cv2.line(
120150
img,
121-
(x1 + self.x_offset, y1 + self.y_offset),
122-
(x2 + self.x_offset, y2 + self.y_offset),
151+
(x1, y1),
152+
(x2, y2),
123153
tuple(color_bgr),
124154
3,
125155
)

indi_allsky/flask/forms.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import time
66
from datetime import datetime
7+
import cv2
78

89
from flask_wtf import FlaskForm
910
from wtforms import IntegerField
@@ -440,6 +441,35 @@ def IMAGE_EXTRA_TEXT_validator(form, field):
440441
raise ValidationError(str(e))
441442

442443

444+
def DETECT_MASK_validator(form, field):
445+
if not field.data:
446+
return
447+
448+
folder_regex = r'^[a-zA-Z0-9_\.\-\/\ ]+$'
449+
if not re.search(folder_regex, field.data):
450+
raise ValidationError('Invalid file name')
451+
452+
453+
detect_mask_p = Path(field.data)
454+
455+
try:
456+
if not detect_mask_p.exists():
457+
raise ValidationError('File does not exist')
458+
459+
if not detect_mask_p.is_file():
460+
raise ValidationError('Not a file')
461+
462+
with io.open(str(detect_mask_p), 'r'):
463+
pass
464+
except PermissionError as e:
465+
raise ValidationError(str(e))
466+
467+
468+
mask_data = cv2.imread(str(detect_mask_p), cv2.IMREAD_GRAYSCALE)
469+
if isinstance(mask_data, type(None)):
470+
raise ValidationError('File is not a valid image')
471+
472+
443473
def IMAGE_SCALE_validator(form, field):
444474
if field.data < 1:
445475
raise ValidationError('Image Scaling must be 1 or greater')
@@ -827,6 +857,7 @@ class IndiAllskyConfigForm(FlaskForm):
827857
DETECT_STARS = BooleanField('Star Detection')
828858
DETECT_STARS_THOLD = FloatField('Star Detection Threshold', validators=[DataRequired(), DETECT_STARS_THOLD_validator])
829859
DETECT_METEORS = BooleanField('Meteor Detection')
860+
DETECT_MASK = StringField('Detection Mask', validators=[DETECT_MASK_validator])
830861
DETECT_DRAW = BooleanField('Mark Detections on Image')
831862
SQM_ROI_X1 = IntegerField('SQM ROI x1', validators=[SQM_ROI_validator])
832863
SQM_ROI_Y1 = IntegerField('SQM ROI y1', validators=[SQM_ROI_validator])

indi_allsky/flask/templates/config.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,19 @@
338338
<div class="col-sm-8"></div>
339339
</div>
340340

341+
<div class="form-group row">
342+
<div class="col-sm-2">
343+
{{ form_config.DETECT_MASK.label(class='col-form-label') }}
344+
</div>
345+
<div class="col-sm-5">
346+
{{ form_config.DETECT_MASK(class='form-control bg-secondary') }}
347+
<div id="DETECT_MASK-error" class="invalid-feedback text-danger" style="display: none;"></div>
348+
</div>
349+
<div class="col-sm-5">
350+
<div>Image mask file for detection area. PNG or JPEG</div>
351+
</div>
352+
</div>
353+
341354
<div class="form-group row">
342355
<div class="col-sm-2">
343356
{{ form_config.DETECT_DRAW.label }}
@@ -1408,6 +1421,7 @@
14081421
'SQM_ROI_X2',
14091422
'SQM_ROI_Y2',
14101423
'DETECT_STARS_THOLD',
1424+
'DETECT_MASK',
14111425
'LOCATION_LATITUDE',
14121426
'LOCATION_LONGITUDE',
14131427
'NIGHT_SUN_ALT_DEG',

indi_allsky/flask/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,7 @@ def get_context(self):
772772
'DETECT_STARS' : self.indi_allsky_config.get('DETECT_STARS', True),
773773
'DETECT_STARS_THOLD' : self.indi_allsky_config.get('DETECT_STARS_THOLD', 0.6),
774774
'DETECT_METEORS' : self.indi_allsky_config.get('DETECT_METEORS', False),
775+
'DETECT_MASK' : self.indi_allsky_config.get('DETECT_MASK', ''),
775776
'DETECT_DRAW' : self.indi_allsky_config.get('DETECT_DRAW', False),
776777
'LOCATION_LATITUDE' : self.indi_allsky_config.get('LOCATION_LATITUDE', 0.0),
777778
'LOCATION_LONGITUDE' : self.indi_allsky_config.get('LOCATION_LONGITUDE', 0.0),
@@ -1035,6 +1036,7 @@ def dispatch_request(self):
10351036
self.indi_allsky_config['DETECT_STARS'] = bool(request.json['DETECT_STARS'])
10361037
self.indi_allsky_config['DETECT_STARS_THOLD'] = float(request.json['DETECT_STARS_THOLD'])
10371038
self.indi_allsky_config['DETECT_METEORS'] = bool(request.json['DETECT_METEORS'])
1039+
self.indi_allsky_config['DETECT_MASK'] = str(request.json['DETECT_MASK'])
10381040
self.indi_allsky_config['DETECT_DRAW'] = bool(request.json['DETECT_DRAW'])
10391041
self.indi_allsky_config['LOCATION_LATITUDE'] = float(request.json['LOCATION_LATITUDE'])
10401042
self.indi_allsky_config['LOCATION_LONGITUDE'] = float(request.json['LOCATION_LONGITUDE'])

0 commit comments

Comments
 (0)