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

KDE's Doxygen guidelines are available online.