Kstars

focusblurriness.h
1/*
2 SPDX-FileCopyrightText: 2024 John Evans <john.e.evans.email@googlemail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#pragma once
8
9#include "focus.h"
10#include "fitsviewer/fitsdata.h"
11#include "auxiliary/imagemask.h"
12#include "auxiliary/robuststatistics.h"
13#include "../ekos.h"
14#include <ekos_focus_debug.h>
15#include "opencv2/opencv.hpp"
16#include "opencv2/imgcodecs.hpp"
17
18namespace Ekos
19{
20
21// This class uses "blurriness" measures to calculate focus. The following are supported:
22// Star Fields
23// FOCUS_STAR_STDDEV - This measure uses the variance / mean of pixels. JEE need to rename.
24//
25// Non-star fields: Lunar, Solar & Planetary
26// FOCUS_STAR_SOBEL - This measure uses the Sobel Edge Detection openCV algorithm. The measure is variance / mean.
27// FOCUS_STAR_LAPLASSIAN - This measure uses the Laplassian openCV algorithm. The measure is mean ^ 2.
28// FOCUS_STAR_CANNY - This measure uses the Canny openCV algorithm. The measure is mean.
29//
30// This class uses openCV to perform calculations on the images. Set the debug = true flag to have openCV display
31// intermediate images for debugging.
32
33using namespace cv;
34
35class FocusBlurriness
36{
37 private:
38 /**
39 * @brief Get the openCV image datatype for the passed in fits image type
40 * @param fits image type
41 * @return openCV image type
42 */
43 int getCVType(const int type);
44
45 /**
46 * @brief Get the Mosaic Mask pointer if a mosaic mask is being used
47 * @param mask pointer
48 * @param tile
49 * @return Mosaic Mask
50 */
51 ImageMosaicMask * getMosaicMask(const QSharedPointer<ImageMask> &mask, const int tile);
52
53 /**
54 * @brief Calculate Region of Interest for the passed in tile
55 * @param tile
56 * @param Mosaic Mask
57 * @return openCV ROI
58 */
59 cv::Rect calcROIfromTile(const int tile, ImageMosaicMask *mosaicMask);
60
61 /**
62 * @brief Apply Ring Mask to Image
63 * @param img is the image
64 * @oaram width of image
65 * @param height of image
66 * @param ringMask
67 */
68 void applyRingMaskToImage(cv::Mat &img, const int width, const int height, ImageRingMask *ringMask);
69
70 public:
71
72 FocusBlurriness(Mathematics::RobustStatistics::ScaleCalculation scaleCalc);
73 ~FocusBlurriness();
74
75 template <typename T>
76 void processBlurriness(const T &imageBuffer, const QSharedPointer<FITSData> &imageData, const bool denoise,
77 const QSharedPointer<ImageMask> &mask, const int &tile, const QRect &roi, const Focus::StarMeasure starMeasure,
78 double *blurriness, double *weight)
79 {
80 // Initialise outputs
81 *blurriness = INVALID_STAR_MEASURE;
82 *weight = 1.0;
83
84 // Check mask & tile inputs
85 ImageMosaicMask *mosaicMask = nullptr;
86 if (tile >= 0)
87 mosaicMask = getMosaicMask(mask, tile);
88
89 // openCV uses exceptions rather than error codes to communicate errors so enclose everything in try catch
90 try
91 {
92 // Dimensions of area to perform Blurriness on; whole sensor or just tile
93 auto stats = imageData->getStatistics();
94 int width = stats.width;
95 int height = stats.height;
96
97 // Get the openCV datatype
98 int cvType = getCVType(stats.dataType);
99 if (cvType < 0)
100 return;
101
102 // Setup the image matrix
103 size_t rowLen = width * stats.bytesPerPixel;
104 cv::Mat img = cv::Mat(height, width, cvType, (void *) imageBuffer, rowLen);
105 if (img.empty())
106 {
107 qCDebug(KSTARS_EKOS_FOCUS) << QString("%1 Unable to process image in openCV").arg(__FUNCTION__);
108 return;
109 }
110 if (debug)
111 cv::imshow("Original image", img);
112
113 // Convert colour images to greyscale
114 if (img.channels() != 1)
115 {
116 cv::cvtColor(img, img, cv::COLOR_BGR2GRAY);
117 if (debug)
118 cv::imshow("Greyscale image", img);
119 }
120
121 // Now see if there are restrictions on the image size such as an ROI or a mask
122 if (tile >= 0 || roi != QRect())
123 {
124 // We are dealing with a ROI within the passed in image
125 cv::Rect cvROI;
126 if (tile >= 0)
127 cvROI = calcROIfromTile(tile, mosaicMask);
128 else
129 cvROI = cv::Rect(roi.x(), roi.y(), roi.width(), roi.height());
130
131 // Apply the ROI to the image
132 img = img(cvROI);
133 if (img.empty())
134 {
135 qCDebug(KSTARS_EKOS_FOCUS) << QString("%1 Unable to process ROI image in openCV").arg(__FUNCTION__);
136 return;
137 }
138 if (debug)
139 cv::imshow("ROI", img);
140 }
141 else if (mask)
142 {
143 // Check for a ring mask and if set, apply to the image
144 ImageRingMask *ringMask = dynamic_cast<ImageRingMask *>(mask.get());
145 if (ringMask)
146 applyRingMaskToImage(img, width, height, ringMask);
147 }
148
149 cv::Mat img_8b, sobelx, sobely, result, abs_grad_x, abs_grad_y;
150 cv::Scalar mean, sigma;
151 double min;
152 // Denoise (blur) the image for better edge detection
153 if (denoise)
154 {
155 cv::GaussianBlur(img, img, Size(9, 9), 0, 0, BORDER_DEFAULT);
156 if (debug)
157 cv::imshow("Denoised image", img);
158 }
159
160 switch (starMeasure)
161 {
162 case Focus::FOCUS_STAR_STDDEV:
163 cv::meanStdDev(img, mean, sigma);
164 result = img.clone();
165 // JEE variance seems better than SD so may need to rename this method
166 *blurriness = sigma.val[0] * sigma.val[0] / mean.val[0];
167 break;
168 case Focus::FOCUS_STAR_SOBEL:
169 // Use a signed matrix otherwise half the negative gradients are lost
170 cv::Sobel(img, sobelx, CV_64F, 1, 0, 5);
171 cv::Sobel(img, sobely, CV_64F, 0, 1, 5);
172
173 if (m_maxX < 0.0)
174 cv::minMaxIdx(sobelx, &min, &m_maxX);
175 cv::convertScaleAbs(sobelx, abs_grad_x, 255 / m_maxX, 0);
176 if (m_maxY < 0.0)
177 cv::minMaxIdx(sobely, &min, &m_maxY);
178 cv::convertScaleAbs(sobely, abs_grad_y, 255 / m_maxY, 0);
179
180 cv::addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, result);
181 if (debug)
182 {
183 cv::imshow("Sobel X", sobelx);
184 cv::imshow("Sobel Y", sobely);
185 cv::imshow("abs_grad_x", abs_grad_x);
186 cv::imshow("abs_grad_y", abs_grad_y);
187 }
188 cv::meanStdDev(result, mean, sigma);
189 *blurriness = sigma.val[0] * sigma.val[0] / mean.val[0];
190 break;
191 case Focus::FOCUS_STAR_LAPLASSIAN:
192 cv::Laplacian(img, result, CV_64F, 3);
193 if (debug)
194 cv::imshow("Laplassian", result);
195 if (m_maxX < 0.0)
196 cv::minMaxIdx(result, &min, &m_maxX);
197 cv::convertScaleAbs(result, result, 255.0 / m_maxX, 0);
198 cv::meanStdDev(result, mean, sigma);
199 *blurriness = mean.val[0] * mean.val[0];
200 break;
201 case Focus::FOCUS_STAR_CANNY:
202 // Canny algorithm works on 8bit images
203 if (m_maxX < 0.0)
204 cv::minMaxIdx(img, &min, &m_maxX);
205 cv::convertScaleAbs(img, img, 255.0 / m_maxX, 0);
206 // The hysteresis variables min=100 and max=3*min seem to work OK
207 cv::Canny(img, result, 100, 300, 5, false);
208 if (debug)
209 {
210 cv::imshow("8bit image", img);
211 cv::imshow("Canny", result);
212 }
213 cv::meanStdDev(result, mean, sigma);
214 *blurriness = mean.val[0];
215 break;
216 default:
217 qCDebug(KSTARS_EKOS_FOCUS) << QString("%1 called with unknown star measure %2").arg(__FUNCTION__).arg(starMeasure);
218 break;
219 }
220 if (debug)
221 {
222 cv::imshow("result", result);
223 cv::equalizeHist(result, result);
224 cv::imshow("equalized result", result);
225 cv::waitKey();
226 cv::destroyAllWindows();
227 }
228 }
229 catch (const cv::Exception &ex)
230 {
231 qCDebug(KSTARS_EKOS_FOCUS) << QString("openCV exception %1 called from %2").arg(ex.what()).arg(__FUNCTION__);
232 *blurriness = INVALID_STAR_MEASURE;
233 *weight = 1.0;
234 }
235 }
236 static double constexpr INVALID_STAR_MEASURE = -1.0;
237
238 private:
239
240 Mathematics::RobustStatistics::ScaleCalculation m_ScaleCalc;
241 double m_maxX = -1.0;
242 double m_maxY = -1.0;
243 bool debug = false; // JEE Remove for production
244};
245}
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:83
int height() const const
int width() const const
int x() const const
int y() const const
T * get() const const
QString arg(Args &&... args) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:40 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.