表格线识别通用库文档
载入中...
搜索中...
未找到
projection.hpp
浏览该文件的文档.
1/*
2 * @Description: 投影类 头文件及其实现
3 * @Version:
4 * @Autor: dreamy-xay
5 * @date: 2023-12-04
6 * @LastEditors: dreamy-xay
7 * @LastEditTime: 2024-05-10
8 */
9
10#ifndef COMMON_PROJECTION_HPP
11#define COMMON_PROJECTION_HPP
12
13#include <iostream>
14#include <opencv2/core.hpp>
15
17#include "common/base/list.hpp"
18#include "common/base/rect.hpp"
19#include "common/enum.h"
21#include "common/macro.h"
22#include "common/type.h"
23
24namespace cm {
25
67 private:
68 size_t height; // 投影图像区域高度
69 size_t width; // 投影图像区域宽度
70 size_t rows, cols; // 投影缓存空间所对应的图像的行数和列数
71 size_t** hdir_prefix_sum; // 横向投影前缀和
72 size_t** vdir_prefix_sum; // 纵向投影前缀和
73
74 void Reset(size_t height, size_t width, bool is_copy_data);
75 void Free();
76
77 public:
78 Projection();
81 Projection(const cv::Mat& binary_image);
83
86
87 Projection& Reserve(size_t height, size_t width);
88 Projection& Resize(size_t height, size_t width);
90 Projection& Assign(const cv::Mat& binary_image);
93
95 cv::Mat Image(Direction direction, const Rect& rect) const;
98
99 size_t Row(size_t index, Interval col_range = Interval::All()) const;
100 size_t Col(size_t index, Interval row_range = Interval::All()) const;
101
102 size_t Height() const;
103 size_t Width() const;
104
105 Rect Boundary(double hdir_threshold = 0.005, double vdir_threshold = 0.005, int margin = 20) const;
106
111
112 template <typename INDEXED>
114 template <typename INDEXED>
116};
117
125inline Projection::Projection() : height(0), width(0), rows(0), cols(0), hdir_prefix_sum(nullptr), vdir_prefix_sum(nullptr) {}
126
137 // 先初始化所有信息
138 width = height = 0;
139 rows = cols = 0;
140 hdir_prefix_sum = vdir_prefix_sum = nullptr;
141
142 // 调用重新赋值函数进行拷贝构造
143 this->Assign(projection);
144}
145
156 // 更新当前类的所有信息
157 width = projection.width;
158 height = projection.height;
159 rows = projection.rows;
160 cols = projection.cols;
161 hdir_prefix_sum = projection.hdir_prefix_sum;
162 vdir_prefix_sum = projection.vdir_prefix_sum;
163
164 // projection 置前缀和数组指针置为空,防止二次释放空间
165 projection.hdir_prefix_sum = projection.vdir_prefix_sum = nullptr;
166}
167
179inline Projection::Projection(const cv::Mat& binary_image) {
180 // 先初始化所有信息
181 width = height = 0;
182 rows = cols = 0;
183 hdir_prefix_sum = vdir_prefix_sum = nullptr;
184
185 // 调用重新赋值函数进行拷贝构造
186 this->Assign(binary_image);
187}
188
197 this->Free(); // 释放全部空间
198}
199
210 // 调用重新赋值函数进行拷贝构造
211 this->Assign(projection);
212
213 return *this;
214}
215
231 // 先释放当前类原有空间
232 this->Free();
233
234 // 再更新当前类的所有信息
235 width = projection.width;
236 height = projection.height;
237 rows = projection.rows;
238 cols = projection.cols;
239 hdir_prefix_sum = projection.hdir_prefix_sum;
240 vdir_prefix_sum = projection.vdir_prefix_sum;
241
242 // projection 置前缀和数组指针置为空,防止二次释放空间
243 projection.hdir_prefix_sum = projection.vdir_prefix_sum = nullptr;
244
245 return *this;
246}
247
257inline void Projection::Free() {
258 // 释放横向投影前缀和空间
259 if (rows && hdir_prefix_sum) {
260 for (int i = 0; i < rows; ++i)
261 delete[] hdir_prefix_sum[i];
262
263 rows = 0;
264 delete[] hdir_prefix_sum;
265 hdir_prefix_sum = nullptr;
266 }
267
268 // 释放纵向投影前缀和空间
269 if (cols && vdir_prefix_sum) {
270 for (int i = 0; i < cols; ++i)
271 delete[] vdir_prefix_sum[i];
272
273 delete[] vdir_prefix_sum;
274 vdir_prefix_sum = nullptr;
275 cols = 0;
276 }
277}
278
292inline void Projection::Reset(size_t rows, size_t cols, bool is_copy_data) {
293 // 如果需要重置的空间大小小于当前已分配空间大小,则不重置空间
294 if (rows <= this->rows && cols <= this->cols)
295 return;
296
297 // 分配新的横向投影前缀和空间
298 size_t** new_hdir_prefix_and = new size_t*[rows]();
299 for (int i = 0; i < rows; ++i)
300 new_hdir_prefix_and[i] = new size_t[cols + 1]();
301
302 // 分配新的纵向投影前缀和空间
303 size_t** new_vdir_prefix_and = new size_t*[cols]();
304 for (int i = 0; i < cols; ++i)
305 new_vdir_prefix_and[i] = new size_t[rows + 1]();
306
307 // 根据条件选择性复制原来空间数据
308 if (is_copy_data) {
309 // 复制原来空间横向投影前缀和数据
310 for (int i = 0; i < this->rows; ++i)
311 memcpy(new_hdir_prefix_and[i], hdir_prefix_sum[i], (this->cols + 1) * sizeof(size_t)); // flawfinder: ignore
312
313 // 复制原来空间纵向投影前缀和数据
314 for (int i = 0; i < this->cols; ++i)
315 memcpy(new_vdir_prefix_and[i], vdir_prefix_sum[i], (this->rows + 1) * sizeof(size_t)); // flawfinder: ignore
316 }
317
318 this->Free(); // 释放原来空间数据
319
320 hdir_prefix_sum = new_hdir_prefix_and; // 更新新的横向投影前缀和空间
321 vdir_prefix_sum = new_vdir_prefix_and; // 更新新的纵向投影前缀和空间
322
323 this->rows = rows; // 更新分配空间对应图像行数大小
324 this->cols = cols; // 更新分配空间对应图像列数大小
325}
326
342inline Projection& Projection::Reserve(size_t height, size_t width) {
343 this->Reset(height, width, true); // 重置空间大小并复制原空间数据
344
345 return *this;
346}
347
365inline Projection& Projection::Resize(size_t height, size_t width) {
366 this->Reset(height, width, true); // 重置空间大小并复制原空间数据
367
368 this->height = height; // 更新投影所对应图像高度
369 this->width = width; // 更新投影所对应图像宽度
370
371 return *this;
372}
373
388 // 先清空当前类原有数据(但分配的空间不动)
389 this->Clear();
390
391 // 更新投影所对应图像高度和宽度
392 height = projection.height;
393 width = projection.width;
394
395 // 重置空间大小但不要复制原空间数据
396 this->Reset(projection.height, projection.width, false);
397
398 // 赋值更新横向投影前缀和数组
399 for (int i = 0; i < rows; ++i)
400 memcpy(hdir_prefix_sum[i], projection.hdir_prefix_sum[i], (cols + 1) * sizeof(size_t)); // flawfinder: ignore
401
402 // 赋值更新纵向投影前缀和数组
403 for (int i = 0; i < cols; ++i)
404 memcpy(vdir_prefix_sum[i], projection.vdir_prefix_sum[i], (rows + 1) * sizeof(size_t)); // flawfinder: ignore
405
406 return *this;
407}
408
425 // 只支持单通道图像的投影赋值构造
426 Cm_Assert(binary_image.channels() == 1, "only single-channel image (grayscale image) can be projected!");
427
428 // 先清空当前类原有数据(但分配的空间不动)
429 this->Clear();
430
431 // 更新投影所对应图像高度和宽度
432 height = binary_image.rows;
433 width = binary_image.cols;
434
435 // 重置空间大小但不要复制原空间数据
436 this->Reset(height, width, false);
437
438 // 计算横线投影前缀和
439 for (int i = 0; i < height; ++i) {
440 const uchar* row_ptr = binary_image.ptr<uchar>(i); // 二值图行指针(加速索引)
441
442 // 计算当前行的列前缀和
443 for (int j = 0; j < width; ++j)
444 hdir_prefix_sum[i][j + 1] = hdir_prefix_sum[i][j] + row_ptr[j];
445 }
446
447 // 计算纵向投影前缀和
448 const uchar* image_ptr = binary_image.ptr<uchar>(0); // 二值图图像数据首指针(加速索引)
449 for (int i = 0; i < width; ++i)
450 for (int j = 0; j < height; ++j) // 计算当前列的行前缀和
451 vdir_prefix_sum[i][j + 1] = vdir_prefix_sum[i][j] + image_ptr[i + j * width];
452
453 return *this;
454}
455
468 // 对于类内所有变量值进行交换
469 std::swap(height, projection.height);
470 std::swap(width, projection.width);
471 std::swap(rows, projection.rows);
472 std::swap(cols, projection.cols);
473 std::swap(hdir_prefix_sum, projection.hdir_prefix_sum);
474 std::swap(vdir_prefix_sum, projection.vdir_prefix_sum);
475
476 return *this;
477}
478
489 // 在保证存在图像投影的情况下清零数据
490 if (width && height) {
491 // 只清空已经使用了的空间
492 for (int i = 0; i < height; ++i)
493 memset(hdir_prefix_sum[i], 0, (width + 1) * sizeof(size_t));
494
495 for (int i = 0; i < width; ++i)
496 memset(vdir_prefix_sum[i], 0, (height + 1) * sizeof(size_t));
497 }
498
499 width = height = 0; // 投影所对应图像高度和宽度也清零
500
501 return *this;
502}
503
518 row_range = std::move(row_range.Intersect({0, int(height)})); // 保证行区间在图像的高度范围内
519 col_range = std::move(col_range.Intersect({0, int(width)})); // 保证列区间在图像的宽度范围内
520
521 // 投影图像
522 cv::Mat proj_image(row_range.Length(), col_range.Length(), CV_8UC1); // 单通道二值图
523 proj_image = 255; // 初始化为白色
524
525 if (direction == HDIR)
526 for (int i = 0; i < proj_image.rows; ++i) // 横向投影
527 proj_image(cv::Rect(0, i, this->Row(i + row_range.start, col_range), 1)) = 0; // 画黑线
528 else
529 for (int i = 0; i < proj_image.cols; ++i) // 纵向投影
530 proj_image(cv::Rect(i, 0, 1, this->Col(i + col_range.start, row_range))) = 0; // 画黑线
531
532 return proj_image;
533}
534
547inline cv::Mat Projection::Image(Direction direction, const Rect& rect) const {
548 // 默认调用获取指定行区间和列区间的投影图像图像(将矩形区域拆解成行和列区间)
549 return this->Image(direction, {rect.y, rect.y + rect.height + 1}, {rect.x, rect.x + rect.width + 1});
550}
551
566 row_range = std::move(row_range.Intersect({0, int(height)})); // 保证行区间在图像的高度范围内
567 col_range = std::move(col_range.Intersect({0, int(width)})); // 保证列区间在图像的宽度范围内
568
569 List<size_t> proj_value; // 新建一个列表存储投影值
570
571 if (direction == HDIR)
572 for (int i = row_range.start; i < row_range.end; ++i) // 横向投影,根据前缀和求区间和,除以255是为了获取白色像素数目(即投影值)
573 proj_value.emplace_back((hdir_prefix_sum[i][col_range.end] - hdir_prefix_sum[i][col_range.start]) / 255);
574 else
575 for (int i = col_range.start; i < col_range.end; ++i) // 纵向投影,根据前缀和求区间和,除以255是为了获取白色像素数目(即投影值)
576 proj_value.emplace_back((vdir_prefix_sum[i][row_range.end] - vdir_prefix_sum[i][row_range.start]) / 255);
577
578 return std::move(proj_value); // 返回投影值
579}
580
594 // 默认调用获取指定行区间和列区间的投影值(将矩形区域拆解成行和列区间)
595 return this->Value(direction, {rect.y, rect.y + rect.height + 1}, {rect.x, rect.x + rect.width + 1});
596}
597
610inline size_t Projection::Row(size_t index, Interval col_range) const {
611 // 断言行索引必须在图像的高度范围内
612 Cm_Assert(index < height, "the index is out of bounds!");
613
614 col_range = std::move(col_range.Intersect({0, int(width)})); // 保证列区间在图像的高度范围内
615
616 // 根据前缀和计算区间投影值
617 return (hdir_prefix_sum[index][col_range.end] - hdir_prefix_sum[index][col_range.start]) / 255;
618}
619
632inline size_t Projection::Col(size_t index, Interval row_range) const {
633 // 断言列索引必须在图像的高度范围内
634 Cm_Assert(index >= 0 && index < width, "the index is out of bounds!");
635
636 row_range = std::move(row_range.Intersect({0, int(height)})); // 保证行区间在图像的高度范围内
637
638 // 根据前缀和计算区间投影值
639 return (vdir_prefix_sum[index][row_range.end] - vdir_prefix_sum[index][row_range.start]) / 255;
640}
641
651inline size_t Projection::Height() const {
652 return height;
653}
654
664inline size_t Projection::Width() const {
665 return width;
666}
667
682 int upper_boundary = 0, lower_boundary = height; // 图像的上下边界
683 int left_boundary = 0, right_boundary = width; // 图像的左右边界
684
685 int min_h_proj_value = hdir_threshold * width; // 最小的横向投影值
686 int min_v_proj_value = vdir_threshold * height; // 最小的纵向投影值
687
688 for (int i = 0; i < width; ++i) // 查找图像的左边界
689 if ((vdir_prefix_sum[i][height] - vdir_prefix_sum[i][0]) / 255 >= min_v_proj_value) {
691 break;
692 }
693
694 for (int i = width - 1; i >= left_boundary; --i) // 查找图像的右边界
695 if ((vdir_prefix_sum[i][height] - vdir_prefix_sum[i][0]) / 255 >= min_v_proj_value) {
697 break;
698 }
699
700 for (int i = 0; i < height; ++i) // 查找图像的上边界
701 if ((hdir_prefix_sum[i][width] - hdir_prefix_sum[i][0]) / 255 >= min_h_proj_value) {
703 break;
704 }
705
706 for (int i = height - 1; i >= upper_boundary; --i) // 查找图像的下边界
707 if ((hdir_prefix_sum[i][width] - hdir_prefix_sum[i][0]) / 255 >= min_h_proj_value) {
709 break;
710 }
711
712 upper_boundary = std::max(0, upper_boundary - margin); // 根据 margin 向上扩充像素
713 lower_boundary = std::min(int(height) - 1, lower_boundary + margin); // 根据 margin 向下扩充像素
714 left_boundary = std::max(0, left_boundary - margin); // 根据 margin 向左扩充像素
715 right_boundary = std::min(int(width) - 1, right_boundary + margin); // 根据 margin 向右扩充像素
716
718}
719
737 row_range = std::move(row_range.Intersect({0, int(height)})); // 保证行区间在图像的高度范围内
738 col_range = std::move(col_range.Intersect({0, int(width)})); // 保证列区间在图像的宽度范围内
739
740 List<size_t> projection = std::move(this->Value(direction, row_range, col_range)); // 通过函数获取指定区域的投影值
741
742 // 拿到投影值的原始数据(加快索引速度)
743 const size_t* proj_value_ptr = projection.data();
744
745 // 投影区间
747
748 // 抽取波峰
750
751 // 是否转换绝对坐标
753 for (auto& interval : intervals)
754 interval.Translate(proj_range.start); // 区间转换绝对坐标
755
756 return std::move(intervals);
757}
758
774 // 默认调用根据指定的区域投影抽取波峰函数(将矩形区域拆解成行和列区间)
775 return this->ExtractPeak(direction, min_proj_value, min_peak_span, {rect.y, rect.y + rect.height + 1}, {rect.x, rect.x + rect.width + 1}, is_absolute_coord);
776}
777
795 row_range = std::move(row_range.Intersect({0, int(height)})); // 保证行区间在图像的高度范围内
796 col_range = std::move(col_range.Intersect({0, int(width)})); // 保证列区间在图像的宽度范围内
797
798 List<size_t> projection = std::move(this->Value(direction, row_range, col_range)); // 通过函数获取指定区域的投影值
799
800 // 拿到投影值的原始数据(加快索引速度)
801 const size_t* proj_value_ptr = projection.data();
802
803 // 投影区间
805
806 // 抽取波谷
808
809 // 是否转换绝对坐标
811 for (auto& interval : intervals)
812 interval.Translate(proj_range.start); // 区间转换绝对坐标
813
814 return std::move(intervals);
815}
816
833 // 默认调用根据指定的区域投影抽取波谷函数(将矩形区域拆解成行和列区间)
834 return this->ExtractTrough(direction, max_proj_value, min_trough_span, {rect.y, rect.y + rect.height + 1}, {rect.x, rect.x + rect.width + 1}, is_absolute_coord);
835}
836
853template <typename INDEXED>
855 Intervals peak_intervals; // 波峰区间列表
856
857 int peak_span = 0; // 波峰跨度
858
859 // 遍历给定区间内所有投影值
860 for (int i = interval.start; i < interval.end; ++i) {
861 if (projection[i] >= min_proj_value) // 如果满足波峰条件
862 ++peak_span; // 波峰跨度加1
863 else { // 如果不满足波峰条件
864 if (peak_span >= min_peak_span) // 检测波峰跨度是否满足条件
865 peak_intervals.emplace_back(i - peak_span, i); // 满足则增加波峰区间
866 peak_span = 0; // 波峰跨度重置为0
867 }
868 }
869 if (peak_span >= min_peak_span) // 检测最后一个波峰区间跨度是否满足条件
870 peak_intervals.emplace_back(interval.end - peak_span, interval.end); // 满足则增加波峰区间
871
872 return std::move(peak_intervals);
873}
874
890template <typename INDEXED>
892 Intervals trough_intervals; // 波谷区间列表
893
894 int trough_span = 0; // 波谷跨度
895
896 // 遍历给定区间内所有投影值
897 for (int i = interval.start; i < interval.end; ++i) {
898 if (projection[i] <= max_proj_value) // 如果满足波谷条件
899 ++trough_span; // 波谷跨度加1
900 else { // 如果不满足波谷条件
901 if (trough_span >= min_trough_span) // 检测波谷跨度是否满足条件
902 trough_intervals.emplace_back(i - trough_span, i); // 满足则增加波谷区间
903 trough_span = 0; // 波谷跨度重置为0
904 }
905 }
906 if (trough_span >= min_trough_span) // 检测最后一个波谷区间跨度是否满足条件
907 trough_intervals.emplace_back(interval.end - trough_span, interval.end); // 满足则增加波谷区间
908
909 return std::move(trough_intervals); // 返回波谷区间列表
910}
911
912} // namespace cm
913
914#endif
区间类
Definition interval.hpp:29
static Interval All()
获取表示全范围的区间
Definition interval.hpp:493
区间列表类
Definition intervals.hpp:29
点类
Definition point.hpp:52
T y
点的 y 坐标
Definition point.hpp:57
T x
点的 x 坐标
Definition point.hpp:55
Point< T > & Translate(T dx, T dy)
点的坐标平移操作
Definition point.hpp:427
~Projection()
投影类的析构函数
Projection & operator=(const Projection &projection)
投影类的拷贝赋值函数
Projection & Clear()
清空投影类所有数据
Intervals ExtractTrough(Direction direction, size_t max_proj_value, size_t min_trough_span, Interval row_range=Interval::All(), Interval col_range=Interval::All(), bool is_absolute_coord=true) const
根据指定的行区间和列区间投影抽取波谷
size_t Row(size_t index, Interval col_range=Interval::All()) const
获取横向投影中指定行的投影值
Projection()
投影类的默认构造函数
Projection & Assign(const Projection &projection)
重新赋值一份投影
size_t Height() const
获取投影图像区域高度
List< size_t > Value(Direction direction, Interval row_range=Interval::All(), Interval col_range=Interval::All()) const
获取指定行区间和列区间的投影值
Projection & Reserve(size_t height, size_t width)
重置投影图像所对应投影数据空间大小
Intervals ExtractPeak(Direction direction, size_t min_proj_value, size_t min_peak_span, Interval row_range=Interval::All(), Interval col_range=Interval::All(), bool is_absolute_coord=true) const
根据指定的行区间和列区间投影抽取波峰
size_t Col(size_t index, Interval row_range=Interval::All()) const
获取纵向投影中指定列的投影值
Projection & Resize(size_t height, size_t width)
重置投影图对象的大小
size_t Width() const
获取投影图像区域宽度
Rect Boundary(double hdir_threshold=0.005, double vdir_threshold=0.005, int margin=20) const
根据投影获取图像的边界
cv::Mat Image(Direction direction, Interval row_range=Interval::All(), Interval col_range=Interval::All()) const
获取指定行区间和列区间的投影图像图像
Projection & Swap(Projection &projection)
交换投影
矩形类
Definition rect.hpp:31
#define Cm_Assert(expr, message)
断言宏
Definition macro.h:109
Direction
方向枚举
Definition enum.h:114
@ HDIR
横向
Definition enum.h:116
unsigned int size_t
表示内存中对象的大小,经常用于表示数组长度、内存分配等涉及到大小的地方。
Definition type.h:20