Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Satini_pvduc
/
ocrpdf
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Snippets
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Authored by
tien_nemo
2025-09-09 13:15:59 +0700
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Commit
fec2e1e888ea619ea8b4c3d84f89bd1d332d4fdd
fec2e1e8
1 parent
0a53c2c3
demo2
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
152 additions
and
39 deletions
app/Http/Controllers/OcrController.php
app/Services/OCR/extrac_table.py
app/Services/OCR/table_detector.py
resources/views/ocr/index.blade.php
app/Http/Controllers/OcrController.php
View file @
fec2e1e
...
...
@@ -22,6 +22,7 @@ class OcrController extends Controller
'customer_name_xy'
=>
'required|string'
,
]);
$dataDetail
=
$request
->
fields
??
[];
$tableColumns
=
$request
->
table_columns
??
[];
try
{
$masterTemplate
=
MstTemplate
::
updateOrCreate
(
[
'tpl_name'
=>
$request
->
template_name
],
...
...
@@ -49,6 +50,24 @@ class OcrController extends Controller
);
}
// Lưu mapping cột bảng (lưu col index vào field_xy với field_name đặc biệt)
if
(
!
empty
(
$tableColumns
)
&&
is_array
(
$tableColumns
))
{
foreach
(
$tableColumns
as
$name
=>
$colIdx
)
{
if
(
$colIdx
===
null
||
$colIdx
===
''
||
$colIdx
===
false
)
{
continue
;
}
DtTemplate
::
updateOrInsert
(
[
'tpl_id'
=>
$masterTemplate
->
id
,
'field_name'
=>
'__table_col__'
.
$name
,
],
[
'field_xy'
=>
(
string
)
$colIdx
,
]
);
}
}
return
response
()
->
json
([
'success'
=>
true
,
'message'
=>
'Lưu template thành công'
,
...
...
@@ -70,8 +89,8 @@ class OcrController extends Controller
$templateName
=
$request
->
get
(
'template_name'
,
''
);
// Giả sử file OCR JSON & ảnh nằm trong storage/app/public/image/
$jsonPath
=
public_path
(
"image/
3_1757295841
_with_table.json"
);
$imgPath
=
(
"image/
3_1757295841
.jpg"
);
$jsonPath
=
public_path
(
"image/
nemo_new_1757393338
_with_table.json"
);
$imgPath
=
(
"image/
nemo_new_1757393338
.jpg"
);
if
(
!
file_exists
(
$jsonPath
))
{
return
response
()
->
json
([
'error'
=>
'File OCR JSON không tìm thấy'
],
404
);
...
...
@@ -100,7 +119,13 @@ class OcrController extends Controller
// Lấy detail của template
$details
=
DtTemplate
::
where
(
'tpl_id'
,
$mst
->
id
)
->
get
();
$tableColumnMapping
=
[];
foreach
(
$details
as
$detail
)
{
if
(
strpos
(
$detail
->
field_name
,
'__table_col__'
)
===
0
)
{
$name
=
substr
(
$detail
->
field_name
,
strlen
(
'__table_col__'
));
$tableColumnMapping
[
$name
]
=
is_numeric
(
$detail
->
field_xy
)
?
intval
(
$detail
->
field_xy
)
:
null
;
continue
;
}
$coords
=
array_map
(
'intval'
,
explode
(
','
,
$detail
->
field_xy
));
// coords = [x1, y1, x2, y2]
...
...
@@ -130,6 +155,7 @@ class OcrController extends Controller
'pdfImageUrl'
=>
$imgPath
,
'dataMapping'
=>
$dataMapping
,
'is_template'
=>
$is_template
,
'tableColumnMapping'
=>
$tableColumnMapping
??
[],
//?? new \stdClass(),
'fieldOptions'
=>
[
[
'value'
=>
'template_name'
,
'label'
=>
'Tên Mẫu PDF'
],
[
'value'
=>
'customer_name'
,
'label'
=>
'Tên khách hàng'
],
...
...
app/Services/OCR/extrac_table.py
View file @
fec2e1e
...
...
@@ -11,14 +11,14 @@ from PIL import Image, ImageEnhance
# ==== Config ====
BASE_DIR
=
os
.
path
.
abspath
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
".."
,
".."
,
".."
))
# PDF_NAME = 'aaaa
'
PDF_NAME
=
'nemo_new
'
# PDF path
pdf_path
=
Path
(
BASE_DIR
)
/
"storage"
/
"pdf"
/
"
3
.pdf"
pdf_path
=
Path
(
BASE_DIR
)
/
"storage"
/
"pdf"
/
"
2
.pdf"
# Output folder
output_folder
=
Path
(
BASE_DIR
)
/
"public"
/
"image"
PDF_NAME
=
pdf_path
.
stem
# Get the stem of the PDF file
#
PDF_NAME = pdf_path.stem # Get the stem of the PDF file
#print(PDF_NAME)
os
.
makedirs
(
output_folder
,
exist_ok
=
True
)
...
...
@@ -151,9 +151,31 @@ for table in table_info:
x1
,
y1
,
x2
,
y2
=
cell
[
"cell"
]
cell_texts
=
[]
# Helper: compute overlap ratio of bbox against cell
def
overlap_ratio
(
bbox
,
cell_box
):
ix1
=
max
(
bbox
[
0
],
cell_box
[
0
])
iy1
=
max
(
bbox
[
1
],
cell_box
[
1
])
ix2
=
min
(
bbox
[
2
],
cell_box
[
2
])
iy2
=
min
(
bbox
[
3
],
cell_box
[
3
])
iw
=
max
(
0
,
ix2
-
ix1
)
ih
=
max
(
0
,
iy2
-
iy1
)
inter
=
iw
*
ih
bbox_area
=
max
(
1
,
(
bbox
[
2
]
-
bbox
[
0
])
*
(
bbox
[
3
]
-
bbox
[
1
]))
return
inter
/
float
(
bbox_area
)
# Helper: check center inside cell
def
center_inside
(
bbox
,
cell_box
):
cx
=
(
bbox
[
0
]
+
bbox
[
2
])
/
2.0
cy
=
(
bbox
[
1
]
+
bbox
[
3
])
/
2.0
return
(
cx
>=
cell_box
[
0
]
and
cx
<=
cell_box
[
2
]
and
cy
>=
cell_box
[
1
]
and
cy
<=
cell_box
[
3
])
cell_box
=
[
x1
,
y1
,
x2
,
y2
]
for
item
in
ocr_data_list
:
bx1
,
by1
,
bx2
,
by2
=
item
[
"bbox"
]
if
bx1
>=
x1
and
by1
>=
y1
and
bx2
<=
x2
and
by2
<=
y2
:
bbox
=
[
bx1
,
by1
,
bx2
,
by2
]
# Accept if bbox is largely inside the cell, or its center lies inside the cell
if
overlap_ratio
(
bbox
,
cell_box
)
>=
0.3
or
center_inside
(
bbox
,
cell_box
):
cell_texts
.
append
(
item
[
"text"
])
# thêm vào cell gốc
...
...
app/Services/OCR/table_detector.py
View file @
fec2e1e
...
...
@@ -2,46 +2,73 @@ import cv2
import
numpy
as
np
import
os
def
detect_tables
(
image_path
):
def
filter_horizontal_lines
(
lines_h
,
img_width
,
min_h_len_ratio
=
0.8
,
tol_y
=
10
):
if
lines_h
is
None
:
return
[],
[]
ys_candidates
=
[]
for
l
in
lines_h
:
x1
,
y1
,
x2
,
y2
=
l
[
0
]
if
abs
(
y1
-
y2
)
<=
3
:
# ngang
line_len
=
abs
(
x2
-
x1
)
y_mid
=
int
(
round
((
y1
+
y2
)
/
2
))
ys_candidates
.
append
((
y_mid
,
line_len
,
x1
,
x2
))
ys_candidates
.
sort
(
key
=
lambda
x
:
x
[
0
])
filtered_lines
,
line_segments
,
current_group
=
[],
[],
[]
for
y
,
length
,
x1
,
x2
in
ys_candidates
:
if
not
current_group
:
current_group
.
append
((
y
,
length
,
x1
,
x2
))
else
:
if
abs
(
y
-
current_group
[
-
1
][
0
])
<=
tol_y
:
current_group
.
append
((
y
,
length
,
x1
,
x2
))
else
:
longest
=
max
(
current_group
,
key
=
lambda
x
:
x
[
1
])
if
longest
[
1
]
>=
min_h_len_ratio
*
img_width
:
filtered_lines
.
append
(
longest
[
0
])
line_segments
.
append
((
longest
[
2
],
longest
[
3
],
longest
[
0
]))
else
:
break
current_group
=
[(
y
,
length
,
x1
,
x2
)]
if
current_group
:
longest
=
max
(
current_group
,
key
=
lambda
x
:
x
[
1
])
if
longest
[
1
]
>=
min_h_len_ratio
*
img_width
:
filtered_lines
.
append
(
longest
[
0
])
line_segments
.
append
((
longest
[
2
],
longest
[
3
],
longest
[
0
]))
total_rows
=
max
(
0
,
len
(
filtered_lines
)
-
1
)
print
(
f
"Detected {total_rows} rows"
)
return
filtered_lines
,
line_segments
def
detect_tables
(
image_path
,
gap_threshold
=
50
):
img
=
cv2
.
imread
(
image_path
)
if
img
is
None
:
raise
FileNotFoundError
(
f
"Không đọc được ảnh: {image_path}"
)
gray
=
cv2
.
cvtColor
(
img
,
cv2
.
COLOR_BGR2GRAY
)
blur
=
cv2
.
GaussianBlur
(
gray
,
(
3
,
3
),
0
)
# Edge detection
edges
=
cv2
.
Canny
(
blur
,
50
,
150
,
apertureSize
=
3
)
# --- Horizontal lines ---
lines_h
=
cv2
.
HoughLinesP
(
edges
,
1
,
np
.
pi
/
180
,
threshold
=
120
,
minLineLength
=
int
(
img
.
shape
[
1
]
*
0.6
),
maxLineGap
=
20
)
ys_candidates
,
line_segments
=
[],
[]
if
lines_h
is
not
None
:
for
l
in
lines_h
:
x1
,
y1
,
x2
,
y2
=
l
[
0
]
if
abs
(
y1
-
y2
)
<=
3
:
# ngang
y_mid
=
int
(
round
((
y1
+
y2
)
/
2
))
ys_candidates
.
append
(
y_mid
)
line_segments
.
append
((
x1
,
x2
,
y_mid
))
# gom nhóm y
ys
,
tol_y
=
[],
10
for
y
in
sorted
(
ys_candidates
):
if
not
ys
or
abs
(
y
-
ys
[
-
1
])
>
tol_y
:
ys
.
append
(
y
)
img_height
,
img_width
=
img
.
shape
[:
2
]
ys
,
line_segments
=
filter_horizontal_lines
(
lines_h
,
img_width
,
min_h_len_ratio
=
0.8
,
tol_y
=
10
)
total_rows
=
max
(
0
,
len
(
ys
)
-
1
)
# --- Vertical lines ---
lines_v
=
cv2
.
HoughLinesP
(
edges
,
1
,
np
.
pi
/
180
,
threshold
=
100
,
minLineLength
=
int
(
img
.
shape
[
0
]
*
0.
5
),
maxLineGap
=
20
)
xs
=
[]
minLineLength
=
int
(
img
.
shape
[
0
]
*
0.
4
),
maxLineGap
=
20
)
v_lines
,
xs
=
[],
[]
if
lines_v
is
not
None
:
for
l
in
lines_v
:
x1
,
y1
,
x2
,
y2
=
l
[
0
]
if
abs
(
x1
-
x2
)
<=
3
:
xs
.
append
(
int
(
round
((
x1
+
x2
)
/
2
)))
v_lines
.
append
((
int
(
round
((
x1
+
x2
)
/
2
)),
min
(
y1
,
y2
),
max
(
y1
,
y2
)))
# gom nhóm x
x_pos
,
tol_v
=
[],
10
...
...
@@ -50,26 +77,66 @@ def detect_tables(image_path):
x_pos
.
append
(
v
)
total_cols
=
max
(
0
,
len
(
x_pos
)
-
1
)
tables
=
[]
if
total_rows
>
0
and
total_cols
>
0
:
y_min
,
y_max
=
ys
[
0
],
ys
[
-
1
]
x_min
,
x_max
=
x_pos
[
0
],
x_pos
[
-
1
]
table_box
=
(
x_min
,
y_min
,
x_max
,
y_max
)
# build cells
rows_data
=
[]
for
i
in
range
(
total_rows
):
row_cells
=
[]
for
j
in
range
(
total_cols
):
j
=
0
while
j
<
total_cols
:
cell_box
=
(
x_pos
[
j
],
ys
[
i
],
x_pos
[
j
+
1
],
ys
[
i
+
1
])
row_cells
.
append
({
"cell"
:
cell_box
,
"row_idx"
:
i
,
"col_idx"
:
j
})
# Vẽ ô
cv2
.
rectangle
(
img
,
(
cell_box
[
0
],
cell_box
[
1
]),
(
cell_box
[
2
],
cell_box
[
3
]),
(
0
,
255
,
255
),
1
)
row_height
=
cell_box
[
3
]
-
cell_box
[
1
]
# Check vertical line coverage (>=70% chiều cao hàng)
has_left
=
any
(
abs
(
x
-
cell_box
[
0
])
<=
tol_v
and
(
min
(
y_end
,
cell_box
[
3
])
-
max
(
y_start
,
cell_box
[
1
]))
>=
0.7
*
row_height
for
x
,
y_start
,
y_end
in
v_lines
)
has_right
=
any
(
abs
(
x
-
cell_box
[
2
])
<=
tol_v
and
(
min
(
y_end
,
cell_box
[
3
])
-
max
(
y_start
,
cell_box
[
1
]))
>=
0.7
*
row_height
for
x
,
y_start
,
y_end
in
v_lines
)
if
has_left
and
has_right
:
col_start
=
j
col_end
=
j
# nếu cột tiếp theo không có line → merge
while
col_end
+
1
<
total_cols
:
next_box
=
(
x_pos
[
col_end
+
1
],
ys
[
i
],
x_pos
[
col_end
+
2
],
ys
[
i
+
1
])
has_next_left
=
any
(
abs
(
x
-
next_box
[
0
])
<=
tol_v
and
(
min
(
y_end
,
next_box
[
3
])
-
max
(
y_start
,
next_box
[
1
]))
>=
0.7
*
row_height
for
x
,
y_start
,
y_end
in
v_lines
)
if
not
has_next_left
:
# merge tiếp
col_end
+=
1
else
:
break
merged_box
=
(
x_pos
[
col_start
],
ys
[
i
],
x_pos
[
col_end
+
1
],
ys
[
i
+
1
])
if
col_start
==
col_end
:
col_id
=
col_start
else
:
col_id
=
f
"{col_start}-{col_end}"
row_cells
.
append
({
"cell"
:
merged_box
,
"row_idx"
:
i
,
"col_idx"
:
col_id
})
cv2
.
rectangle
(
img
,
(
merged_box
[
0
],
merged_box
[
1
]),
(
merged_box
[
2
],
merged_box
[
3
]),
(
0
,
255
,
255
),
1
)
j
=
col_end
+
1
else
:
j
+=
1
# skip ô lỗi (không có line đầy đủ)
rows_data
.
append
(
row_cells
)
tables
.
append
({
...
...
@@ -78,11 +145,9 @@ def detect_tables(image_path):
"table_box"
:
table_box
,
"cells"
:
rows_data
})
# vẽ viền bảng
cv2
.
rectangle
(
img
,
(
x_min
,
y_min
),
(
x_max
,
y_max
),
(
255
,
0
,
0
),
2
)
debug_path
=
os
.
path
.
splitext
(
image_path
)[
0
]
+
"_debug.jpg"
debug_path
=
os
.
path
.
splitext
(
image_path
)[
0
]
+
"_
fix_
debug.jpg"
cv2
.
imwrite
(
debug_path
,
img
)
return
tables
...
...
resources/views/ocr/index.blade.php
View file @
fec2e1e
This diff is collapsed.
Click to expand it.
Please
register
or
sign in
to post a comment