stackercpp.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #include "stackercpp.h"
  2. Stacker::Stacker(bool verbose, struct Imagedata imagedata)
  3. {
  4. Stacker::verbose = verbose;
  5. Stacker::export_width = 0;
  6. Stacker::export_height = 0;
  7. Stacker::layers = 0;
  8. Stacker::trimratio = 0;
  9. Stacker::imagedata = imagedata;
  10. Stacker::ldb = lf_db_new();
  11. lf_db_load(Stacker::ldb);
  12. cv::setNumThreads(0);
  13. Stacker::stopwatch = clock();
  14. /*
  15. Stacker::imagedata.dist_a = 0.025898;
  16. Stacker::imagedata.dist_b = -0.092305;
  17. Stacker::imagedata.dist_c = 0.105513;
  18. Stacker::imagedata.vignette_k1 = -2.4268;
  19. Stacker::imagedata.vignette_k2 = 3.0906;
  20. Stacker::imagedata.vignette_k3 = -1.4546;
  21. */
  22. }
  23. void
  24. Stacker::add_frame(unsigned char *data, int width, int height)
  25. {
  26. stopwatch_start();
  27. Mat warp_matrix = Mat::eye(3, 3, CV_32F);
  28. Mat mat = Mat(height, width, CV_8UC3, data);
  29. Mat grayscale = Mat(height, width, CV_8UC1);
  30. cv::cvtColor(mat, grayscale, cv::COLOR_BGR2GRAY);
  31. stopwatch_mark("grayscale input");
  32. stopwatch_start();
  33. Scalar mean, stddev;
  34. meanStdDev(grayscale, mean, stddev);
  35. printf("mean: %f, dev: %f\n", mean[0], stddev[0]);
  36. if (mean[0] < 10) {
  37. return;
  38. }
  39. stopwatch_mark("filter");
  40. int number_of_iterations = 5;
  41. double termination_eps = 1e-10;
  42. TermCriteria criteria(TermCriteria::COUNT + TermCriteria::EPS, number_of_iterations, termination_eps);
  43. if (layers == 0) {
  44. // First image in the stack is used as the reference to align the next frames to
  45. Stacker::reference = grayscale;
  46. // Create black image to accumulate the average
  47. Stacker::stacked = Mat(height, width, CV_64FC3);
  48. Stacker::stacked.setTo(Scalar(0, 0, 0, 0));
  49. // Add first frame to the stack
  50. Stacker::stacked += mat;
  51. Stacker::layers += 1;
  52. } else {
  53. // All frames after the initial one are stacked
  54. Mat warped = Mat(Stacker::stacked.rows, Stacker::stacked.cols, CV_32FC1);
  55. stopwatch_start();
  56. cv::findTransformECC(grayscale, Stacker::reference, warp_matrix, MOTION_HOMOGRAPHY, criteria);
  57. stopwatch_mark("find alignment");
  58. stopwatch_start();
  59. warpPerspective(mat, warped, warp_matrix, warped.size(), INTER_LINEAR);
  60. stopwatch_mark("warp image");
  61. // Check how much the image should be cropped to hide the warped edges
  62. float current_trimratio = cv::videostab::estimateOptimalTrimRatio(warp_matrix, mat.size());
  63. Stacker::trimratio = std::max(Stacker::trimratio, current_trimratio);
  64. // Add the warped image to the stack
  65. Stacker::stacked += warped;
  66. Stacker::layers += 1;
  67. }
  68. }
  69. Mat
  70. Stacker::postprocess_mat(Mat
  71. input)
  72. {
  73. stopwatch_start();
  74. int h_crop = (int) ((float) input.cols * Stacker::trimratio);
  75. int v_crop = (int) ((float) input.rows * Stacker::trimratio);
  76. Mat cropped;
  77. input(Rect(h_crop, v_crop, input.cols - h_crop - h_crop, input.rows - v_crop - v_crop)
  78. ).
  79. copyTo(input);
  80. stopwatch_mark("trim");
  81. stopwatch_start();
  82. Mat blur;
  83. GaussianBlur(input, blur, Size(0, 0),
  84. 1.8);
  85. std::vector<cv::Mat> rgb_planes(3);
  86. cv::split(blur, rgb_planes
  87. );
  88. double min_r, max_r, min_g, max_g, min_b, max_b;
  89. minMaxIdx(rgb_planes[0], &min_b, &max_b
  90. );
  91. minMaxIdx(rgb_planes[1], &min_g, &max_g
  92. );
  93. minMaxIdx(rgb_planes[2], &min_r, &max_r
  94. );
  95. input = input - Scalar(min_b + 5, min_g + 5, min_r + 5);
  96. double scale_r, scale_g, scale_b;
  97. scale_r = 255 / (max_r - min_r + 5);
  98. scale_g = (255 / (max_g - min_g + 5));
  99. scale_b = 255 / (max_b - min_b + 5);
  100. multiply(input, Scalar(scale_b, scale_g, scale_r), input
  101. );
  102. stopwatch_mark("levels");
  103. stopwatch_start();
  104. Mat sharpened;
  105. GaussianBlur(input, sharpened, Size(0, 0),
  106. 1.7);
  107. addWeighted(input,
  108. 2.5, sharpened, -1.5, 0, sharpened);
  109. stopwatch_mark("sharpen");
  110. /* Disabled CLAHE local contrast, it's a bit to overpronounced and doesn't
  111. * seem really necessary at this point
  112. stopwatch_start();
  113. Mat lab;
  114. cvtColor(sharpened, lab, COLOR_BGR2Lab);
  115. std::vector<cv::Mat> lab_planes(3);
  116. cv::split(lab, lab_planes);
  117. stopwatch_mark("to Lab");
  118. stopwatch_start();
  119. cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
  120. clahe->setClipLimit(1);
  121. clahe->setTilesGridSize(Size(8, 8));
  122. cv::Mat dst;
  123. clahe->apply(lab_planes[0], dst);
  124. dst.copyTo(lab_planes[0]);
  125. stopwatch_mark("clahe");
  126. stopwatch_start();
  127. Mat result;
  128. cv::merge(lab_planes, lab);
  129. cvtColor(lab, result, COLOR_Lab2BGR);
  130. stopwatch_mark("to RGB");
  131. */
  132. return
  133. sharpened;
  134. }
  135. char *
  136. Stacker::get_result()
  137. {
  138. // Complete the averaging and go back to an 8-bit image
  139. stopwatch_start();
  140. Stacker::stacked.convertTo(Stacker::stacked, CV_8U, 1. / Stacker::layers);
  141. stopwatch_mark("average");
  142. // Run the final-frame postprocessing
  143. Mat result = postprocess_mat(Stacker::stacked);
  144. Stacker::export_width = result.cols;
  145. Stacker::export_height = result.rows;
  146. // Convert mat to bytes
  147. size_t size = result.total() * result.elemSize();
  148. char *data = (char *) malloc(size);
  149. std::memcpy(data, result.data, size * sizeof(char));
  150. return data;
  151. }
  152. char *
  153. Stacker::postprocess(unsigned char *data, int width, int height)
  154. {
  155. // Run lensfun
  156. lfCamera *cam = nullptr;
  157. const lfCamera **cameras = ldb->FindCamerasExt(nullptr, imagedata.model);
  158. if (cameras) {
  159. cam = (lfCamera *) cameras[0];
  160. fprintf(stderr, "Using camera %s\n", cam->Model);
  161. } else {
  162. fprintf(stderr, "No camera found in LensFun database\n");
  163. }
  164. lfLens *lens = nullptr;
  165. const lfLens **lenses = ldb->FindLenses(cam, nullptr, imagedata.model);
  166. if (lenses) {
  167. lens = (lfLens *) lenses[0];
  168. fprintf(stderr, "Using lens %s\n", lens->Model);
  169. } else {
  170. fprintf(stderr, "No lens found in LensFun database\n");
  171. }
  172. float cropfactor = (float) imagedata.focal_length_35mm / imagedata.focal_length;
  173. if (!lens && !cam) {
  174. cam = new lfCamera;
  175. cam->SetModel(imagedata.model);
  176. lens = new lfLens;
  177. lens->Type = LF_RECTILINEAR;
  178. lens->GuessParameters();
  179. lens->AddMount("fixed");
  180. lens->SetModel(imagedata.model);
  181. lens->MinFocal = imagedata.focal_length;
  182. lens->MaxFocal = imagedata.focal_length;
  183. lens->MinAperture = imagedata.fnumber;
  184. lens->MaxAperture = imagedata.fnumber;
  185. lens->AspectRatio = 1.5;
  186. lens->CropFactor = cropfactor;
  187. if (imagedata.dist_a != 0.0f) {
  188. auto *distortion = new lfLensCalibDistortion();
  189. distortion->Model = lfDistortionModel::LF_DIST_MODEL_PTLENS;
  190. distortion->Focal = imagedata.focal_length;
  191. distortion->Terms[0] = imagedata.dist_a;
  192. distortion->Terms[1] = imagedata.dist_b;
  193. distortion->Terms[2] = imagedata.dist_c;
  194. lens->AddCalibDistortion(distortion);
  195. }
  196. if (imagedata.vignette_k1 != 0.0f) {
  197. auto *vignette = new lfLensCalibVignetting();
  198. vignette->Model = LF_VIGNETTING_MODEL_PA;
  199. vignette->Focal = imagedata.focal_length;
  200. vignette->Aperture = imagedata.fnumber;
  201. vignette->Distance = 10.0f;
  202. vignette->Terms[0] = imagedata.vignette_k1;
  203. vignette->Terms[1] = imagedata.vignette_k2;
  204. vignette->Terms[2] = imagedata.vignette_k3;
  205. lens->AddCalibVignetting(vignette);
  206. }
  207. if (!lens->Check()) {
  208. fprintf(stderr, "Lens check failed\n");
  209. }
  210. }
  211. auto *mod = new lfModifier(lens, cropfactor, width, height);
  212. mod->Initialize(lens, LF_PF_U8, imagedata.focal_length, imagedata.fnumber, 10.0f, 1.0f, lfLensType::LF_RECTILINEAR,
  213. LF_MODIFY_ALL, false);
  214. auto *pos = new float[width * height * 2];
  215. bool do_remap = mod->ApplyGeometryDistortion(0.0f, 0.0f, width, height, pos);
  216. // Convert bytes to mat
  217. Mat mat = Mat(height, width, CV_8UC3, data);
  218. stopwatch_start();
  219. mod->ApplyColorModification(mat.data, 0, 0, width, height, LF_CR_3(RED, GREEN, BLUE), width * 3);
  220. stopwatch_mark("vignette");
  221. if (do_remap) {
  222. stopwatch_start();
  223. Mat dst = Mat(mat.size(), mat.type());
  224. Mat map_x(mat.size(), CV_32FC1);
  225. Mat map_y(mat.size(), CV_32FC1);
  226. for (int x = 0; x < mat.rows; x++) {
  227. for (int y = 0; y < mat.cols; y++) {
  228. size_t offset = ((x * mat.cols) + y) * 2;
  229. map_x.at<float>(x, y) = pos[offset];
  230. map_y.at<float>(x, y) = pos[offset + 1];
  231. }
  232. }
  233. remap(mat, dst, map_x, map_y, INTER_LANCZOS4, BORDER_CONSTANT, Scalar(0, 0, 0));
  234. mat = dst;
  235. stopwatch_mark("distortion");
  236. }
  237. // Run the final-frame postprocessing
  238. Mat result = postprocess_mat(mat);
  239. Stacker::export_width = result.cols;
  240. Stacker::export_height = result.rows;
  241. // Convert mat to bytes
  242. size_t size = result.total() * result.elemSize();
  243. char *outdata = (char *) malloc(size);
  244. std::memcpy(outdata, result.data, size * sizeof(char));
  245. return outdata;
  246. }
  247. void
  248. Stacker::stopwatch_start()
  249. {
  250. Stacker::stopwatch = clock();
  251. }
  252. void
  253. Stacker::stopwatch_mark(const char *name)
  254. {
  255. if (Stacker::verbose) {
  256. printf("[%.1fms] %s\n", float(clock() - Stacker::stopwatch) / CLOCKS_PER_SEC * 1000, name);
  257. }
  258. }
  259. int
  260. Stacker::get_width()
  261. {
  262. return Stacker::export_width;
  263. }
  264. int
  265. Stacker::get_height()
  266. {
  267. return Stacker::export_height;
  268. }