# 報紙工作欄位自動擷取專案報告
## 摘要 (Abstract)
本專案開發了一個自動化工具,用於從掃描報紙圖像或 PDF 文件中提取獨立的工作招募欄位,並將其保存為單獨的圖像檔案,以支援數位化存檔或後續處理。該工具採用傳統電腦視覺技術,通過圖像預處理、輪廓檢測、智能過濾、嵌套分析和缺失區域檢測等步驟實現目標。經過多次優化,成功解決了過大/過小區塊、重複提取及部分遺漏等問題,在測試數據集(8 張報紙,約 249 個欄位)中達到 89.16% 的正確提取率和 93.17% 的多區塊提取率。該工具適用於文檔數位化、OCR 前處理及版面分析等場景,未來可通過深度學習、參數自適應及文字方向分析進一步提升性能。
---
## 1. 當時情境描述 (Scenario Description)
本次專案的目標是自動化處理掃描的報紙圖片(或 PDF 文件),希望能精確地將報紙上刊登的每一個獨立的工作招募欄位(Job Advertisement Block)擷取下來,儲存成獨立的圖片檔案,以便後續處理或數位化存檔。輸入可以是單張圖片或多頁 PDF 文件,輸出則為每個檢測到的欄位獨立保存的圖片以及一個組合圖像。
## 2. 第一次預想方式 (Initial Approach Idea)
初步構想是利用傳統電腦視覺技術來達成目標,主要思路如下:
- **圖片預處理**:對原始報紙圖片進行預處理,以強化欄位邊界並降低背景干擾。具體步驟包括:
- 灰階轉換(`cv2.cvtColor` 轉為灰階)。
- 高斯模糊(`cv2.GaussianBlur` 去除雜訊)。
- 自適應二值化(`cv2.adaptiveThreshold` 處理光照不均並突出邊界)。
- Canny 邊緣檢測(`cv2.Canny` 找出明確的邊緣線)。
- **輪廓檢測**:在預處理後的邊緣圖像上,使用輪廓檢測演算法(`cv2.findContours`)找出代表各個欄位的封閉區域。
- **區域裁剪與儲存**:將檢測到的每個輪廓對應的原始圖片區域裁剪下來,儲存為單獨的圖片檔案。
## 3. 第一次成果 (Initial Result)
根據初步構想實作後,成功擷取出部分工作欄位,但結果不理想,存在以下問題:
- **尺寸過大的區塊**:檢測到許多大型區域,例如報紙標題或橫跨多欄的文章區塊。
- **尺寸過小的雜訊區塊**:圖片噪點或細小線條被誤認為獨立輪廓。
- **重複或嵌套區塊**:同一區域因不同層次的邊界檢測而被重複提取。
- **遺漏明顯欄位**:部分工作欄位因邊界模糊或品質問題未被檢測到。
## 4. 優化過程與策略 (Optimization Process & Strategies)
針對初步成果中發現的問題,進行了以下幾項優化:
### a. 處理過大圖塊 (Handling Oversized Blocks)
- **問題描述**:初始輪廓檢測將報紙上的大型區域(如標題區或多欄文章)視為單一輪廓,這些並非目標工作欄位。
- **圖片範例**:
- **說明**:此圖展示一個錯誤檢測的大型區塊,包含報紙標題與多個廣告。
- **解法 (Code Implementation)**:
- 在 `process_image` 函數中,設定面積閾值 `max_area_threshold = 0.2 * image.shape[0] * image.shape[1]`(圖片總面積的 20%)。
- 對每個輪廓計算邊界框面積(`w * h`),若超過閾值則忽略。
```python
max_area_threshold = 0.2 * image.shape[0] * image.shape[1]
filtered_contours_size = []
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
area = w * h
if area > max_area_threshold:
continue
filtered_contours_size.append(contour)
```
- **思維 (Rationale)**:工作欄位通常不會佔據整張報紙的大比例面積,設定上限可排除無關的大型區塊。
- **成效 (Effectiveness)**:顯著減少了輸出中大型無關區塊的數量,保留更合理的候選區域。
### b. 處理過小圖塊/噪點 (Handling Small Blocks/Noise)
- **問題描述**:圖片噪點、文字間空隙或版面細線被誤檢測為小輪廓,這些不是有效工作欄位。
- **圖片範例**: 
- **說明**:這些圖展示錯誤檢測的小型區塊,如標題或廣告的片段。
- **解法 (Code Implementation)**:
- 設定最小尺寸 `min_dim = 120`(寬度和高度均需大於 120 像素)。
- 設定長寬比限制 `max_aspect_ratio = 5.0`(寬高比和 Highness/Width 比需小於 5)。
```python
min_dim = 120
max_aspect_ratio = 5.0
for contour in filtered_contours_size:
x, y, w, h = cv2.boundingRect(contour)
if w > min_dim and h > min_dim:
if w / h < max_aspect_ratio and h / w < max_aspect_ratio:
valid_contours_initial.append(contour)
```
- **思維 (Rationale)**:有效欄位應有一定最小尺寸,且形狀不應過於狹長,這些條件可濾除噪點和線條。
- **成效 (Effectiveness)**:大幅減少了微小、無意義的圖片數量,提升輸出品質。
### c. 處理重複/嵌套圖塊 (Handling Duplicate/Nested Blocks)
- **問題描述**:輪廓檢測可能在不同層次檢測到邊界,導致嵌套或重複提取同一區域。
- **圖片範例**:<img src="https://hackmd.io/_uploads/SkUOKyNyee.jpg" width="150" /> <img src="https://hackmd.io/_uploads/B1af5yVyxg.jpg" width="150" />
- **說明**:此兩張圖為同一區域,因大小不同被切成兩張。
- **解法 (Code Implementation)**:
- 使用 `is_contained_bbox(bbox1, bbox2, tolerance=10)` 函數檢查一個邊界框是否被另一個包含(容差 10 像素)。
- 在初次過濾和最終檢查中,移除被包含的較小輪廓,保留較大區域。
```python
def is_contained_bbox(bbox1, bbox2, tolerance=10):
x1, y1, w1, h1 = bbox1
x2, y2, w2, h2 = bbox2
return (x1 >= (x2 - tolerance) and y1 >= (y2 - tolerance) and
(x1 + w1) <= (x2 + w2 + tolerance) and (y1 + h1) <= (y2 + h2 + tolerance))
```
- 初次過濾中比較面積,保留較大者:
```python
for i, contour in enumerate(valid_contours_initial):
bbox_i = temp_initial_bboxes[i]
for j, other_bbox in enumerate(temp_initial_bboxes):
if i != j and other_bbox is not None and is_contained_bbox(bbox_i, other_bbox):
area_i = bbox_i[2] * bbox_i[3]
area_j = other_bbox[2] * other_bbox[3]
if area_i >= area_j:
temp_initial_bboxes[j] = None
else:
is_contained_by_others = True
break
```
- **思維 (Rationale)**:被包含的輪廓通常是外部輪廓的一部分,移除冗餘可保留完整區塊。
- **成效 (Effectiveness)**:減少了重複提取的情況,輸出更乾淨。
### d. 處理遺漏的區塊 (Handling Missed Blocks)
- **問題描述**:因圖片品質或欄位邊界模糊,`cv2.findContours` 未檢測到部分工作欄位。
- **圖片範例**:<img src="https://hackmd.io/_uploads/r1gR_e4Jex.jpg" width="200" /> <img src="https://hackmd.io/_uploads/Bke0OxN1gl.jpg" width="200" />
- **說明**:左圖顯示初次處理後遺漏區塊,右圖為填補後結果。
- **解法 (Code Implementation)**:
- **後處理填補策略**:
1. 使用初次檢測的區塊建立遮罩(`mask`),標記已覆蓋區域。
2. 應用形態學操作(膨脹和侵蝕,`cv2.dilate` 和 `cv2.erode`,核大小 30)平滑遮罩。
3. 在反轉遮罩(`inv_mask`)上檢測未覆蓋區域(`cv2.findContours`)。
4. 過濾條件:最小面積 5000、最小尺寸 120、長寬比小於 5、與初次區塊重疊比例低於 50%。
```python
mask = np.zeros((mask_height, mask_width), dtype=np.uint8)
for (x, y, w, h) in initial_block_bboxes:
relative_y = y - min_y_overall
relative_x = x - min_x_overall
mask[relative_y:relative_y + h, relative_x:relative_x + w] = 255
kernel = np.ones((30, 30), np.uint8)
mask = cv2.dilate(mask, kernel, iterations=3)
mask = cv2.erode(mask, kernel, iterations=3)
inv_mask = cv2.bitwise_not(mask)
missing_contours, _ = cv2.findContours(inv_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
```
- 將通過過濾的未覆蓋區域加入最終輸出。
- **思維 (Rationale)**:假設初次檢測勾勒出主要內容,空白區域可能是遺漏欄位,填補可提高完整性。
- **成效 (Effectiveness)**:生成更完整的組合圖像(`_final_combined.jpg`),填補部分遺漏,但可能引入非目標內容。
## 5. 潛在問題與限制 (Potential Issues & Limitations)
- **輪廓合併問題**:多個欄位緊密相連或被共同邊框包圍時,`cv2.findContours` 可能將其視為單一輪廓,導致提取的圖片包含多個欄位。
- **參數敏感性**:預處理參數(如高斯模糊核大小 5x5、自適應閾值塊大小 11)、過濾條件(如 `min_dim=120`、`max_area_threshold=0.2`)對不同圖片敏感,需針對具體輸入調優。
- **填補限制**:後處理填補可能誤將空白或分隔線納入,且無法保證語義獨立性。
- **旋轉未處理**:程式碼未處理欄位旋轉問題,需額外分析文字方向。
## 6. 最終成功率 (Final Success Rate)
經過多次優化後,系統在測試數據集(8 張報紙掃描圖片,包含約 249 個工作招募欄位)上的表現如下:
- **正確擷取**:222 個欄位(獨立、完整、無重複或錯誤的欄位)。
- **錯誤擷取**:
- 10 個欄位(多區塊區域)。
- 22 個欄位(無效區域)。
- **遺漏欄位**:27 個欄位(未檢測到的真實工作欄位)。
- **成功率計算**:
- 總真實欄位數 = 正確擷取數 + 遺漏數 = 222 + 27 = 249。
- 多區塊欄位數 = 正確擷取數 + 多區塊欄位數 = 222 + 10 = 232。
- 真實成功率 = 正確擷取數 / 總真實欄位數 = 222 / 249 = **89.16%**。
- 多區塊成功率 = 多區塊擷取數 / 總真實欄位數 = 232 / 249 = **93.17%**。
## 7. 可應用的場域 (Applicable Fields)
此方法適用於以下場景:
- **文檔數位化**:從掃描件中快速分離內容區塊。
- **OCR 前處理**:為文字識別提供候選區域。
- **資料標註輔助**:生成初步區域,減少人工標註工作。
- **版面分析**:研究出版物佈局特徵。
## 8. 優化建議 (Optimization Suggestions)
- **進階分割**:引入分水嶺演算法或深度學習(如 YOLO、Mask R-CNN)處理輪廓合併問題。
- **參數自適應**:根據圖片分辨率動態調整閾值和尺寸參數。
- **旋轉檢測**:整合文字方向分析(如 Tesseract OCR)處理旋轉欄位。
- **整合 OCR**:提取欄位後自動識別文字,提升自動化程度。
---
[GitHub 專案連結](https://github.com/tom1030507/newspaper_job_extractor)
---