MauiKit Image Tools

preprocessimage.cpp
1/*****************************************************************************
2 * preprocessimage.cpp
3 *
4 * Created: 5/28/2020 2020 by mguludag
5 *
6 * Copyright 2020 mguludag. All rights reserved.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be
17 * included in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *****************************************************************************/
27#include "preprocessimage.hpp"
28#include <opencv2/photo.hpp>
29#include "opencv2/opencv.hpp"
30
31// Global Constants
32const int bgr_max = 255; // max value of a bgr pixel
33const int bgr_half = 128; // middle value of a bgr pixel
34std::vector<cv::Point> PreprocessImage::poi = {};
35
36PreprocessImage::PreprocessImage()
37{
38 CV_DNN_REGISTER_LAYER_CLASS(Crop, MyCropLayer);
39}
40
41cv::Mat PreprocessImage::deskewRotate(cv::Mat &image)
42{
43 if (!image.empty()) {
44 cv::Mat orig = image.clone();
45 cv::Mat warped;
46 fourPointTransform(orig, warped, poi);
47 cvtColor(warped, warped, cv::COLOR_RGB2BGRA);
48 return warped;
49 } else {
50 return image;
51 }
52}
53
54void PreprocessImage::adaptThreshold(cv::Mat &image,
55 bool isMorphOp,
56 uint8_t morphKernel,
57 uint8_t blurValue)
58{
59 if (!image.empty()) {
60 cvtColor(image, image, cv::COLOR_BGRA2GRAY);
61
62 adaptiveThreshold(image, image, 255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 9, 14);
63
64 if (isMorphOp) {
65 cv::dilate(image,
66 image,
67 cv::getStructuringElement(cv::MORPH_RECT,
68 cv::Size(morphKernel, morphKernel)));
69 cv::erode(image,
70 image,
71 cv::getStructuringElement(cv::MORPH_RECT, cv::Size(morphKernel, morphKernel)));
72 }
73
74 GaussianBlur(image, image, cv::Size(blurValue, blurValue), 0);
75 cvtColor(image, image, cv::COLOR_GRAY2BGRA);
76 }
77}
78
79void PreprocessImage::toGray(cv::Mat &image, uint8_t blurValue)
80{
81 if (!image.empty()) {
82 cvtColor(image, image, cv::COLOR_BGR2GRAY);
83 GaussianBlur(image, image, cv::Size(blurValue, blurValue), 0);
84 cvtColor(image, image, cv::COLOR_GRAY2BGR);
85 }
86}
87
88cv::Mat PreprocessImage::grayscale(cv::Mat matrix)
89{
90 cvtColor(matrix, matrix, cv::COLOR_BGR2GRAY);
91 return matrix;
92}
93
94bool PreprocessImage::compareContourAreas(std::vector<cv::Point> &contour1,
95 std::vector<cv::Point> &contour2)
96{
97 double i = fabs(contourArea(cv::Mat(contour1)));
98 double j = fabs(contourArea(cv::Mat(contour2)));
99 return (i > j);
100}
101
102bool PreprocessImage::compareXCords(cv::Point &p1, cv::Point &p2)
103{
104 return (p1.x < p2.x);
105}
106
107bool PreprocessImage::compareYCords(cv::Point &p1, cv::Point &p2)
108{
109 return (p1.y < p2.y);
110}
111
112bool PreprocessImage::compareDistance(std::pair<cv::Point, cv::Point> &p1,
113 std::pair<cv::Point, cv::Point> &p2)
114{
115 return (cv::norm(p1.first - p1.second) < cv::norm(p2.first - p2.second));
116}
117
118double PreprocessImage::_distance(cv::Point &p1, cv::Point &p2)
119{
120 return sqrt(((p1.x - p2.x) * (p1.x - p2.x)) + ((p1.y - p2.y) * (p1.y - p2.y)));
121}
122
123void PreprocessImage::resizeToHeight(cv::Mat &src, cv::Mat &dst, int height)
124{
125 cv::Size2d s = cv::Size2d(src.cols * (height / double(src.rows)), height);
126 cv::resize(src, dst, s, cv::INTER_AREA);
127}
128
129void PreprocessImage::orderPoints(std::vector<cv::Point> &inpts, std::vector<cv::Point> &ordered)
130{
131 sort(inpts.begin(), inpts.end(), &compareXCords);
132 std::vector<cv::Point> lm(inpts.begin(), inpts.begin() + 2);
133 std::vector<cv::Point> rm(inpts.end() - 2, inpts.end());
134
135 sort(lm.begin(), lm.end(), &compareYCords);
136 cv::Point tl(lm[0]);
137 cv::Point bl(lm[1]);
138 std::vector<std::pair<cv::Point, cv::Point>> tmp;
139 for (size_t i = 0; i < rm.size(); i++) {
140 tmp.push_back(std::make_pair(tl, rm[i]));
141 }
142
143 sort(tmp.begin(), tmp.end(), &compareDistance);
144 cv::Point tr(tmp[0].second);
145 cv::Point br(tmp[1].second);
146
147 ordered.push_back(tl);
148 ordered.push_back(tr);
149 ordered.push_back(br);
150 ordered.push_back(bl);
151}
152
153void PreprocessImage::fourPointTransform(cv::Mat &src, cv::Mat &dst, std::vector<cv::Point> &pts)
154{
155 std::vector<cv::Point> ordered_pts;
156 orderPoints(pts, ordered_pts);
157
158 std::vector<cv::Point2f> src_;
159 std::vector<cv::Point2f> dst_;
160
161 double wa = _distance(ordered_pts[2], ordered_pts[3]);
162 double wb = _distance(ordered_pts[1], ordered_pts[0]);
163 double mw = fmax(wa, wb);
164
165 double ha = _distance(ordered_pts[1], ordered_pts[2]);
166 double hb = _distance(ordered_pts[0], ordered_pts[3]);
167 double mh = fmax(ha, hb);
168
169 src_ = {
170 cv::Point(ordered_pts[0].x, ordered_pts[0].y),
171 cv::Point(ordered_pts[1].x, ordered_pts[1].y),
172 cv::Point(ordered_pts[2].x, ordered_pts[2].y),
173 cv::Point(ordered_pts[3].x, ordered_pts[3].y),
174 };
175 dst_ = {cv::Point(0, 0),
176 cv::Point(static_cast<int>(mw) - 1, 0),
177 cv::Point(static_cast<int>(mw) - 1, static_cast<int>(mh) - 1),
178 cv::Point(0, static_cast<int>(mh) - 1)};
179 cv::Mat m = getPerspectiveTransform(src_, dst_);
180 cv::warpPerspective(src, dst, m, cv::Size(static_cast<int>(mw), static_cast<int>(mh)));
181}
182
183std::vector<cv::Point2f> PreprocessImage::getPoints(cv::Mat &src)
184{
185 cv::Mat gray, edged;
186 std::vector<cv::Point2f> src_;
187 std::vector<cv::Point2f> dst_;
188 std::vector<std::vector<cv::Point>> contours;
189 std::vector<cv::Vec4i> hierarchy = {};
190 std::vector<std::vector<cv::Point>> approx;
191
192 if (!src.empty()) {
193 double ratio = src.rows / 500.0;
194 preProcess(src, edged);
195 cv::findContours(edged, contours, hierarchy, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
196 approx.resize(contours.size());
197 size_t i, j;
198 for (i = 0; i < contours.size(); i++) {
199 double peri = cv::arcLength(contours[i], true);
200 cv::approxPolyDP(contours[i], approx[i], 0.03 * peri, true);
201 }
202
203 sort(approx.begin(), approx.end(), compareContourAreas);
204
205 for (i = 0; i < approx.size(); i++) {
206 if (approx[i].size() == 4) {
207 break;
208 }
209 }
210
211 if (i < approx.size()) {
212 for (j = 0; j < approx[i].size(); j++) {
213 approx[i][j] *= ratio;
214 }
215
216 std::vector<cv::Point> ordered_pts;
217 orderPoints(approx[i], ordered_pts);
218
219 double wa = _distance(ordered_pts[2], ordered_pts[3]);
220 double wb = _distance(ordered_pts[1], ordered_pts[0]);
221 double mw = fmax(wa, wb);
222
223 double ha = _distance(ordered_pts[1], ordered_pts[2]);
224 double hb = _distance(ordered_pts[0], ordered_pts[3]);
225 double mh = fmax(ha, hb);
226
227 src_ = {
228 cv::Point(ordered_pts[0].x, ordered_pts[0].y),
229 cv::Point(ordered_pts[1].x, ordered_pts[1].y),
230 cv::Point(ordered_pts[2].x, ordered_pts[2].y),
231 cv::Point(ordered_pts[3].x, ordered_pts[3].y),
232 };
233 dst_ = {cv::Point(0, 0),
234 cv::Point(static_cast<int>(mw) - 1, 0),
235 cv::Point(static_cast<int>(mw) - 1, static_cast<int>(mh) - 1),
236 cv::Point(0, static_cast<int>(mh) - 1)};
237 }
238 }
239 return src_;
240}
241
242void PreprocessImage::setPoints(std::vector<cv::Point2f> pt)
243{
244 poi.clear();
245 for (auto i : pt) {
246 poi.push_back(i);
247 }
248}
249
250void PreprocessImage::preProcess(cv::Mat &src, cv::Mat &dst)
251{
252 cv::Mat imageGrayed;
253 cv::Mat imageOpen, imageClosed, imageBlurred;
254
255 cv::cvtColor(src, imageGrayed, cv::COLOR_BGRA2GRAY);
256
257 cv::adaptiveThreshold(imageGrayed,
258 imageGrayed,
259 255,
260 cv::ADAPTIVE_THRESH_GAUSSIAN_C,
261 cv::THRESH_BINARY,
262 25,
263 4);
264
265 cv::Mat structuringElmt = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(4, 4));
266 cv::morphologyEx(imageGrayed, imageOpen, cv::MORPH_OPEN, structuringElmt);
267 cv::morphologyEx(imageOpen, imageClosed, cv::MORPH_CLOSE, structuringElmt);
268
269 cv::medianBlur(imageClosed, imageBlurred, 9);
270
271 cv::Canny(imageBlurred, dst, 200, 250);
272}
273
274double PreprocessImage::computeSkew(cv::Mat src)
275{
276 cv::cvtColor(src, src, cv::COLOR_BGRA2GRAY);
277 cv::Size size = src.size();
278 cv::threshold(src, src, 180, 255, cv::THRESH_OTSU);
279 cv::bitwise_not(src, src);
280 std::vector<cv::Vec4i> lines;
281 cv::HoughLinesP(src, lines, 1, CV_PI / 180, 100, size.width / 2., 10);
282 cv::Mat disp_lines(size, CV_8UC1, cv::Scalar(0, 0, 0));
283 double angle = 0.;
284 unsigned nb_lines = static_cast<unsigned>(lines.size());
285 for (unsigned i = 0; i < nb_lines; ++i) {
286 cv::line(disp_lines,
287 cv::Point(lines[i][0], lines[i][1]),
288 cv::Point(lines[i][2], lines[i][3]),
289 cv::Scalar(255, 0, 0));
290 angle += atan2(static_cast<double>(lines[i][3]) - lines[i][1],
291 static_cast<double>(lines[i][2]) - lines[i][0]);
292 }
293 angle /= nb_lines;
294 return angle * 180 / CV_PI;
295}
296
297cv::Mat PreprocessImage::adjustBrightness(cv::Mat &in, int value, cv::Rect rect)
298{
299 if (!in.empty())
300 {
301 cv::Mat out;
302 const bool is_inside = (rect & cv::Rect(0, 0, in.cols, in.rows)) == rect;
303
304 if(is_inside && rect.area() > 0)
305 {
306 cv::Mat out2(in);
307 cv::Mat roi(in(rect));
308
309 roi.convertTo(out, -1, 1, value);
310 out.copyTo(out2(rect));
311 return out2;
312 }else
313 {
314 in.convertTo(out, -1, 1, value);
315 return out;
316 }
317
318 } else
319 return in;
320}
321
322cv::Mat PreprocessImage::adjustContrast(cv::Mat &in, int beta)
323{
324 if (!in.empty()) {
325 cv::Mat out;
326 // in.convertTo(out, -1, beta, 0); //easier way
327
328 // in.convertTo(out, CV_32S); // converts the matrix to type CV_32S to allow aritmetic
329 // int kappa = 259; // contrast parameter, the higher it is, the lower the effect of contrast
330 // double contrast_factor = (kappa * (beta + bgr_max)) / (bgr_max * (kappa - beta)); // calculates contrast factor
331 // qDebug() << "Constrast factor" <<contrast_factor;
332
333 // // contrast is calculated using the equation:
334 // // new_pixel = contrast_factor * (old_pixel - 128) + 128
335 // cv::Scalar scale(bgr_half, bgr_half, bgr_half); // contains cv scalar depth 3 with values of 128
336
337 // out -= scale; // old_pixel - 128
338 // out *= contrast_factor; // contrast * p
339 // out += scale; // p + 128
340 double factor = 1.0;
341 if(beta < 0)
342 {
343 factor = 1-double(abs(beta)/100.0);
344 qDebug() << "COnstarsr factor" << factor << abs(beta);
345
346 }else if(beta > 0)
347 {
348 factor = 4.0*(beta/100.0)+1;
349 }
350
351 in.convertTo(out, -1, factor, 1);
352 return out;
353 } else
354 return in;
355}
356
357cv::Mat PreprocessImage::hue(cv::Mat matrix, int h_shift)
358{
359 qDebug() << "Adjust HUE" << h_shift;
360 cv::Mat processed_mat; // initializes output matrix
361 cv::cvtColor(matrix, processed_mat, cv::COLOR_BGR2HSV); // converts input matrix to HSV type
362
363 short idx = 0; // index of hue in HSV format
364 // iterates through each pixel in mat to ahjust hue values
365 for (int y = 0; y < processed_mat.rows; y++) // iterate columns
366 {
367 for (int x = 0; x < processed_mat.cols; x++) // iterate rows
368 {
369 short h = processed_mat.at<cv::Vec3b>(y,x)[idx]; // get current hue value at pixel (y, x)
370 processed_mat.at<cv::Vec3b>(y,x)[idx] = (h + h_shift) % 180; // adjust hue
371 }
372 }
373
374 cv::cvtColor(processed_mat, processed_mat, cv::COLOR_HSV2BGR); // converts HSV back to BGR
375 return processed_mat;
376}
377// Gamma
378/**
379 * Returns an input BGR Mat with gamma adjustments
380 *
381 * @param matrix The input Mat
382 * @param gamma Gamma factor, -100 for low gamma (bright), +100 for high gamma (dark)
383 * @return Mat with the gamma adjustments
384 */
385cv::Mat PreprocessImage::gamma(cv::Mat matrix, double gamma)
386{
387 // Handles input to be within desired range
388 gamma *= 0.05;
389 if (gamma < 0)
390 gamma = -1 / (gamma - 1);
391 else
392 gamma += 1;
393
394 cv::Mat processed_mat = matrix.clone(); // initializes output matrix
395
396 short max_n = bgr_max + 1; // number of possible pixel values
397 cv::Mat lookUpTable(1, max_n, CV_8U); // lookup table mat
398 uchar* p = lookUpTable.ptr(); // pointers for each entry in lookuptable
399 for( int i = 0; i < max_n; ++i) // goes through each num in possible range to create lookup value of gamma
400 {
401 p[i] = cv::saturate_cast<uchar>(std::pow(i / (double)bgr_max, gamma) * (double)bgr_max); // gamma calculation
402 }
403
404 cv::LUT(processed_mat, lookUpTable, processed_mat); // uses lookup table to change
405
406 return processed_mat;
407}
408
409// Sharpness
410/**
411 * Returns a sharpened input BGR Mat
412 *
413 * @param matrix The input Mat
414 * @param beta Sharpness factor. Ranges from 0 to 100 (increasing in sharpness)
415 * @return Mat with the sharpness adjustments
416 */
417cv::Mat PreprocessImage::sharpness(cv::Mat matrix, double beta)
418{
419 // Truncates the input to be within range
420 if (beta < 0)
421 beta = 0;
422 else if (beta > 100)
423 beta = -10;
424 else
425 beta /= -10;
426
427 cv::Mat processed_mat = matrix.clone(); // initializes output matrix
428
429 double sigma = 3; // standard deviation for the gaussian blur
430 int size = 3; // kernel size
431
432 double alpha = 1 + -1 * beta; // weight of the original matrix (beta is weight of gaussian blur)
433 double gamma = 0; // constant added to the resulting matrix
434
435 cv::GaussianBlur(processed_mat, processed_mat, cv::Size(size, size), sigma, sigma); // creates a matrix adjusted with gaussian blur
436 cv::addWeighted(matrix, alpha, processed_mat, beta, gamma, processed_mat); // adds the orignal and blurred matrix with the weights alpha and beta respectively
437
438 return processed_mat;
439}
440
441cv::Mat PreprocessImage::manualThreshold(cv::Mat &image,
442 int threshValue,
443 uint8_t blurValue)
444{
445 if (!image.empty()) {
446 cv::Mat img = image.clone();
447 cvtColor(img, img, cv::COLOR_BGR2GRAY, 1);
448 cv::threshold(img, img, threshValue, 255, cv::THRESH_BINARY);
449 GaussianBlur(img, img, cv::Size(blurValue, blurValue), 0);
450 cvtColor(img, img, cv::COLOR_GRAY2BGR);
451 return img;
452 } else
453 return image;
454}
455
456
457//value from -255 to 255
458cv::Mat PreprocessImage::adjustSaturation(cv::Mat &in, int value)
459{
460 if (!in.empty())
461 {
462 cv::Mat out;
463
464 cv::cvtColor(in, out, cv::COLOR_BGR2HSV);
465
466 std::vector<cv::Mat> channels;
467 cv::split(out, channels); // splits HSV mat into an 3 mats with 1 channel each for H, S and V
468
469 short idx = 1; // index of saturation in HSV format
470 short rtype = -1; // use same type as input matrix
471 short alpha = 1; // sat_value *= alpha
472
473 channels[idx].convertTo(channels[idx], rtype, alpha, value); // sat_value += s_shift (clips sat_val to stay between 0 to 255)
474
475 cv::merge(channels, out); // merges channels HSV back together into output matrix
476 cv::cvtColor(out, out, cv::COLOR_HSV2BGR); // converts HSV back to BGR
477 // cv::cvtColor(out, out, cv::COLOR_BGR2BGRA); // converts HSV back to BGR
478
479 return out;
480 } else
481 return in;
482
483}
484
485void PreprocessImage::hedEdgeDetectDNN(cv::Mat &image,
486 std::string &prototxt,
487 std::string &caffemodel,
488 int size = 128)
489{
490 cv::dnn::Net net = cv::dnn::readNet(prototxt, caffemodel);
491
492 cv::Mat img;
493 cv::cvtColor(image, img, cv::COLOR_BGRA2BGR);
494 cv::Size reso(size, size);
495 cv::Mat theInput;
496 resize(img, theInput, reso);
497 cv::Mat blob = cv::dnn::blobFromImage(theInput,
498 1.0,
499 reso,
500 cv::Scalar(104.00698793, 116.66876762, 122.67891434),
501 false,
502 false);
503 net.setInput(blob);
504 cv::Mat out
505 = net.forward(); // outputBlobs contains all output blobs for each layer specified in outBlobNames.
506
507 std::vector<cv::Mat> vectorOfImagesFromBlob;
508 cv::dnn::imagesFromBlob(out, vectorOfImagesFromBlob);
509 cv::Mat tmpMat = vectorOfImagesFromBlob[0] * 255;
510 cv::Mat tmpMatUchar;
511 tmpMat.convertTo(tmpMatUchar, CV_8U);
512
513 // old code cv::Mat tmpMat = out.reshape(1, reso.height) ;
514 cv::resize(tmpMatUchar, image, img.size());
515}
516
517void PreprocessImage::CalcBlockMeanVariance(cv::Mat& Img, cv::Mat& Res,float blockSide) // blockSide - the parameter (set greater for larger font on image)
518{
519 using namespace std;
520 using namespace cv;
521
522 Mat I;
523 Img.convertTo(I,CV_32FC1);
524 Res=Mat::zeros(Img.rows/blockSide,Img.cols/blockSide,CV_32FC1);
525 Mat inpaintmask;
526 Mat patch;
527 Mat smallImg;
528 Scalar m,s;
529
530 for(int i=0;i<Img.rows-blockSide;i+=blockSide)
531 {
532 for (int j=0;j<Img.cols-blockSide;j+=blockSide)
533 {
534 patch=I(Range(i,i+blockSide+1),Range(j,j+blockSide+1));
535 cv::meanStdDev(patch,m,s);
536 if(s[0]>0.01) // Thresholding parameter (set smaller for lower contrast image)
537 {
538 Res.at<float>(i/blockSide,j/blockSide)=m[0];
539 }else
540 {
541 Res.at<float>(i/blockSide,j/blockSide)=0;
542 }
543 }
544 }
545
546 cv::resize(I,smallImg,Res.size());
547
548 cv::threshold(Res,inpaintmask,0.02,1.0,cv::THRESH_BINARY);
549
550 Mat inpainted;
551 smallImg.convertTo(smallImg,CV_8UC1,255);
552
553 inpaintmask.convertTo(inpaintmask,CV_8UC1);
554 cv::inpaint(smallImg, inpaintmask, inpainted, 5, cv::INPAINT_TELEA);
555
556 cv::resize(inpainted,Res,Img.size());
557 Res.convertTo(Res,CV_32FC1,1.0/255.0);
558
559}
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri May 2 2025 11:56:08 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.