import re
import cv2
import easyocr
import os
from glob import glob
import numpy as np
from PIL import Image, ImageDraw, ImageFont
# import torch
## ==== ์ฌ์ ์ค์ ====
# EasyOCR ์ค์ ๋ฐ ํ์ผ ๊ฒฝ๋ก
OCR_LANGS = ['ko', 'en'] # ํ์ํ ์ธ์ด
INPUT_DIR = "idc_samples" # ์
๋ ฅ ์ด๋ฏธ์ง ํด๋
OUTPUT_DIR = "idc_results" # ๊ฒฐ๊ณผ ์ ์ฅ ํด๋
os.makedirs(OUTPUT_DIR, exist_ok=True)
# ์ ๊ท์ (3๊ฐ์ง ์ ํ ์ปค๋ฒ)
rrn_combined_regex = re.compile(r"\b\d{6}\s*-\s*\d{7}\b") # 6์๋ฆฌ-7์๋ฆฌ
rrn_nohyphen_regex = re.compile(r"\b\d{13}\b") # 13์๋ฆฌ ๋ถ์ ๊ฒฝ์ฐ
rrn_front_regex = re.compile(r"\b\d{6}\b") # ์ 6์๋ฆฌ
rrn_back_regex = re.compile(r"\b\d{7}\b") # ๋ค 7์๋ฆฌ
## ==== ์ธ๋ถ ์ค์ ๊ฐ ====
# ๊ฒฐํฉ ์๊ณ๊ฐ ์ค์
MAX_X_DISTANCE = 50
MAX_Y_DIFFERENCE = 10
# ๋น์๋ณํ ๋ฐฉ์ ๋ฐ ์ค์
MASK_METHOD = "black" # "black", "mosaic", "blur", "replace_text"
REPLACE_TEXT = "[ REDACTED ]" # replace_text ๋ชจ๋์์ ํ์ํ ํ
์คํธ (ํ๊ธ ์ฌ์ฉ x) # OpenCV ์์ฒด์์ ์ ๊ณตํ๋ ๋ด์ฅ ํฐํธ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ํ๊ธ ์ง์์ด ์ ๋ฉ๋๋ค.
MOSAIC_SCALE = 0.05 # (0.01 ~ 0.03 ์ ๋ ๊ถ์ฅ, ์์์๋ก ๊ฐํ ๋ชจ์์ดํฌ)
BLUR_KERNEL = (51, 51) # ๊ฐ์ฐ์์ ๋ธ๋ฌ ์ปค๋ ํฌ๊ธฐ (๋ฐ๋์ ํ์๊ฐ ์
๋ ฅ, ํด์๋ก ๊ฐํจ)
OCR_LOW_TEXT = 0.4 # ์ผ๋ง๋ ๋ฎ์ ์ ๋ขฐ๋์ ํ
์คํธ๊น์ง ํฌํจํ ์ง ๊ฒฐ์ (๊ธฐ๋ณธ๊ฐ: 0.4)
# ํ์ง์ด ๋ฎ๊ฑฐ๋ ํ๋ฆฐ ๋ฌธ์๋ผ๋ฉด โ ๋
ธ์ด์ฆ๊ฐ ๋ง์ ์ด๋ฏธ์ง๋ผ๋ฉด โ ์ถ์ฒ
# -----------------------
# ==== ๋ง์คํน ์ ํธ๋ฆฌํฐ ํจ์ ====
def mask_bbox(img, bbox, method=MASK_METHOD):
"""
EasyOCR bbox (4์ ์ขํ)๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ์ฌ ํ
์คํธ๋ง ๋ง์คํน
- bbox: EasyOCR์์ ๋ฐํํ 4์ ์ขํ
- method: ๋ง์คํน ๋ฐฉ์ (black, mosaic, blur, replace_text) """
pts = np.array(bbox, dtype=np.int32)
if method == "black":
cv2.fillPoly(img, [pts], (0, 0, 0))
elif method == "mosaic":
""" ์์ ์ด๋ฏธ์ง๋ก ์ถ์ ํ ๋ค์ ํ๋ํ์ฌ ํฝ์
ํ (๋ชจ์์ดํฌ) """
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts) # bbox ํฌ๊ธฐ ๊ณ์ฐ
roi = img[y:y+h, x:x+w]
mask_roi = mask[y:y+h, x:x+w]
if roi.size != 0:
small_w = max(1, int(w * MOSAIC_SCALE))
small_h = max(1, int(h * MOSAIC_SCALE))
small = cv2.resize(roi, (small_w, small_h))
mosaic = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
roi[mask_roi==255] = mosaic[mask_roi==255]
elif method == "blur":
""" ๊ฐ์ฐ์์ ๋ธ๋ฌ ์ ์ฉ """
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts)
roi = img[y:y+h, x:x+w]
mask_roi = mask[y:y+h, x:x+w]
if roi.size != 0:
blur = cv2.GaussianBlur(roi, BLUR_KERNEL, 0)
roi[mask_roi==255] = blur[mask_roi==255]
elif method == "replace_text":
# bbox ์ขํ ์ถ์ถ
pts = np.array(bbox, dtype=np.int32)
# bbox ํฌ๊ธฐ์ ์ค์ฌ ๊ณ์ฐ
x, y, w, h = cv2.boundingRect(pts)
center_x = x + w // 2
center_y = y + h // 2
# ํฐ์ ๋ฐ์ค ๋ง์คํน
cv2.fillPoly(img, [pts], (255, 255, 255))
# ๊ธฐ์ธ๊ธฐ ๊ณ์ฐ (๊ฐ์ฅ ๊ธด ๋ณ ๊ธฐ์ค)
max_len = 0
angle = 0
for i in range(4):
dx = pts[(i+1)%4][0] - pts[i][0]
dy = pts[(i+1)%4][1] - pts[i][1]
length = np.hypot(dx, dy)
if length > max_len:
max_len = length
angle = -np.degrees(np.arctan2(dy, dx))
# PIL ์ด๋ฏธ์ง ์์ฑ
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
diagonal = np.hypot(w, h)
font_size = min(max(int(diagonal * 0.1), 12), h)
# ํฐํธ ์ค์ (๊ตต์ ๋นจ๊ฐ ๊ธ์จ)
try:
font = ImageFont.truetype("malgunbd.ttf", font_size) # ๋ง์ ๊ณ ๋ bold except:
font = ImageFont.load_default()
# 7) ํ
์คํธ ํฌ๊ธฐ ๊ณ์ฐ
bbox_text = draw.textbbox((0, 0), REPLACE_TEXT, font=font)
text_w = bbox_text[2] - bbox_text[0]
text_h = bbox_text[3] - bbox_text[1]
# 8) ํ
์คํธ ์์น ๊ณ์ฐ (bbox ์ค์ฌ ๊ธฐ์ค)
offset = 5 # ํ
์คํธ ์๋ก ์ฌ๋ฆด ํฝ์
์
text_x = center_x - text_w / 2
text_y = center_y - text_h / 2 - offset
# 9) ํ
์คํธ ๋ ์ด์ด ์์ฑ
text_layer = Image.new("RGBA", pil_img.size, (255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_layer)
text_draw.text((text_x, text_y), REPLACE_TEXT, font=font, fill=(255, 0, 0, 255))
# 10) ํ
์คํธ ๋ ์ด์ด ํ์ (bbox ์ค์ฌ ๊ธฐ์ค)
rotated_layer = text_layer.rotate(angle, center=(center_x, center_y), resample=Image.BICUBIC)
# 11) ํฉ์ฑ
pil_img = Image.alpha_composite(pil_img.convert("RGBA"), rotated_layer)
img[:] = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)
return img
def combine_bboxes(bbox1, bbox2):
xs1 = [p[0] for p in bbox1]
ys1 = [p[1] for p in bbox1]
xs2 = [p[0] for p in bbox2]
ys2 = [p[1] for p in bbox2]
x_min = min(min(xs1), min(xs2))
x_max = max(max(xs1), max(xs2))
y_min = min(min(ys1), min(ys2))
y_max = max(max(ys1), max(ys2))
return [[x_min, y_min], [x_max, y_min], [x_max, y_max], [x_min, y_max]]
# ==== ๋ฉ์ธ ์ฒ๋ฆฌ ํจ์ ====def process_image(image_path, reader):
image = cv2.imread(image_path)
if image is None or image.size == 0:
print(f"[ERROR] ์ด๋ฏธ์ง๋ฅผ ์ฝ์ ์ ์์: {image_path}")
return 0
try:
ocr_results = reader.readtext(image, low_text=OCR_LOW_TEXT)
except Exception as e:
print(f"[ERROR] EasyOCR ์คํ ์ค ์ค๋ฅ ๋ฐ์: {e}")
return 0
found = 0
masked_bboxes_coords = []
# 1) ํตํฉ ํจํด ์ฒ๋ฆฌ
for bbox, text, conf in ocr_results:
m_combined = rrn_combined_regex.search(text)
if m_combined:
image = mask_bbox(image, bbox, method=MASK_METHOD)
found += 1
masked_bboxes_coords.append(bbox)
print(f"[MASKED] Combined RRN='{m_combined.group()}' | conf={conf:.2f}")
continue
m_nohyphen = rrn_nohyphen_regex.search(text)
if m_nohyphen:
image = mask_bbox(image, bbox, method=MASK_METHOD)
found += 1
masked_bboxes_coords.append(bbox)
print(f"[MASKED] NoHyphen RRN='{m_nohyphen.group()}' | conf={conf:.2f}")
continue
# 2) ๋ถ๋ฆฌ ํจํด ์ฒ๋ฆฌ
front_parts, back_parts = [], []
for bbox, text, conf in ocr_results:
clean_text = re.sub(r'[- ]', '', text)
if rrn_front_regex.search(clean_text) and len(clean_text) == 6:
front_parts.append({'bbox': bbox, 'text': text, 'conf': conf})
elif rrn_back_regex.search(clean_text) and len(clean_text) == 7:
back_parts.append({'bbox': bbox, 'text': text, 'conf': conf})
masked_fronts = set()
masked_backs = set()
for i, front in enumerate(front_parts):
if i in masked_fronts:
continue
front_xs = [p[0] for p in front['bbox']]
front_ys = [p[1] for p in front['bbox']]
front_x_max = max(front_xs)
front_y_center = (min(front_ys) + max(front_ys)) / 2
for j, back in enumerate(back_parts):
if j in masked_backs:
continue
back_xs = [p[0] for p in back['bbox']]
back_ys = [p[1] for p in back['bbox']]
back_x_min = min(back_xs)
back_y_center = (min(back_ys) + max(back_ys)) / 2
x_distance = back_x_min - front_x_max
y_diff = abs(front_y_center - back_y_center)
if 0 <= x_distance <= MAX_X_DISTANCE and y_diff <= MAX_Y_DIFFERENCE:
combined_bbox = combine_bboxes(front['bbox'], back['bbox'])
image = mask_bbox(image, combined_bbox, method=MASK_METHOD)
found += 1
# ์ค๋ณต ๋ฐฉ์ง
masked_fronts.add(i)
masked_backs.add(j)
combined_text = f"{front['text']}-{back['text']}"
print(f"[MASKED] Combined (Split) RRN='{combined_text}' | dist={x_distance:.1f} | y_diff={y_diff:.1f}")
break
# ๊ฒฐ๊ณผ ์ ์ฅ
fname = os.path.basename(image_path)
out_path = os.path.join(OUTPUT_DIR, f"EasyOCR_masked_{MASK_METHOD}_{fname}")
cv2.imwrite(out_path, image)
print(f"์๋ฃ: {out_path} (์ด {found}๊ฐ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ฒ๋ฆฌ)")
return found
def main():
reader = easyocr.Reader(OCR_LANGS, gpu=True)
img_files = []
for ext in ("*.jpg", "*.jpeg", "*.png"):
img_files.extend(glob(os.path.join(INPUT_DIR, ext)))
if not img_files:
print(f"[INFO] ์
๋ ฅ ํด๋์ ์ด๋ฏธ์ง๊ฐ ์์ต๋๋ค: {INPUT_DIR}")
return
total_masked = 0
for img_path in img_files:
print(f"\n์ฒ๋ฆฌ ์ค: {img_path}")
total_masked += process_image(img_path, reader)
# GPU VRAM ์ํ ์ถ๋ ฅ (GB ๋จ์)
# if torch.cuda.is_available(): # allocated = torch.cuda.memory_allocated(0) / 1024**3 # GB # reserved = torch.cuda.memory_reserved(0) / 1024**3 # GB # print(f"ํ์ฌ VRAM ์ฌ์ฉ: {allocated:.3f} GB, ์์ฝ๋ VRAM: {reserved:.3f} GB")
print(f"\n=== ์ ์ฒด ์๋ฃ ===\n์ด {len(img_files)}์ฅ ์ฒ๋ฆฌ, {total_masked}๊ฐ ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ฒ๋ฆฌ")
if __name__ == "__main__":
main()
import os
import re
import cv2
import numpy as np
from paddleocr import PaddleOCR
from PIL import Image, ImageDraw, ImageFont
# import paddle
# GPU ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ํ์ธ
# mem_alloc = paddle.device.cuda.memory_allocated()
# mem_reserved = paddle.device.cuda.max_memory_reserved()
# print(f"์์ ์ ๋ฉ๋ชจ๋ฆฌ: {mem_alloc/1024**2:.2f} MB")
# print(f"์ต๋ ์์ฝ๋ ๋ฉ๋ชจ๋ฆฌ: {mem_reserved / 1024**2:.2f} MB")
# ----- OCR ์ด๊ธฐํ -----
ocr = PaddleOCR(
lang='korean',
use_doc_orientation_classify=False,
use_doc_unwarping=False,
use_textline_orientation=False,
device='gpu:0',
)
# ----- ์ฃผ๋ฏผ๋ฑ๋ก๋ฒํธ ์ ๊ท์ -----
patterns = [
re.compile(r"\b\d{6}\s*-\s*\d{7}\b"),
re.compile(r"\b\d{13}\b")
]
# ----- ๋ง์คํน ํจ์ (dt_polys ์ขํ ๊ทธ๋๋ก ์ฌ์ฉ) -----
def mask_black(img, box):
pts = np.array(box, dtype=np.int32)
cv2.fillPoly(img, [pts], color=(0,0,0))
return img
def mask_blur(img, box):
pts = np.array(box, dtype=np.int32)
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts)
roi = img[y:y+h, x:x+w]
roi_mask = mask[y:y+h, x:x+w]
if roi.size > 0:
kernel = (51, 51) # kernel: ๋ธ๋ฌ ๊ฐ๋ ์กฐ์ (ํ์๊ฐ ์
๋ ฅ, ๊ฐ์ด ํด์๋ก ๊ฐ๋ โ)
blurred = cv2.GaussianBlur(roi, kernel, 30)
roi[roi_mask==255] = blurred[roi_mask==255]
img[y:y+h, x:x+w] = roi
return img
def mask_mosaic(img, box, ratio=0.05): # ratio: ๋ชจ์์ดํฌ ๊ฐ๋ ์กฐ์ (๊ฐ์ด ๋ฎ์์๋ก ๊ฐ๋ โ)
pts = np.array(box, dtype=np.int32)
mask = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask, [pts], 255)
x, y, w, h = cv2.boundingRect(pts)
roi = img[y:y+h, x:x+w]
roi_mask = mask[y:y+h, x:x+w]
if roi.size > 0:
small = cv2.resize(roi, (max(1,int(w*ratio)), max(1,int(h*ratio))), interpolation=cv2.INTER_LINEAR)
mosaic = cv2.resize(small, (w,h), interpolation=cv2.INTER_NEAREST)
roi[roi_mask==255] = mosaic[roi_mask==255]
img[y:y+h, x:x+w] = roi
return img
def mask_replace_text(img, box, text="[ REDACTED ]"):
pts = np.array(box, dtype=np.int32)
x, y, w, h = cv2.boundingRect(pts)
center_x = x + w // 2
center_y = y + h // 2
cv2.fillPoly(img, [pts], (255, 255, 255))
max_len = 0
angle = 0
for i in range(4):
dx = pts[(i+1)%4][0] - pts[i][0]
dy = pts[(i+1)%4][1] - pts[i][1]
length = np.hypot(dx, dy)
if length > max_len:
max_len = length
angle = -np.degrees(np.arctan2(dy, dx))
pil_img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
diagonal = np.hypot(w, h)
font_size = min(max(int(diagonal * 0.1), 12), h)
try:
font = ImageFont.truetype("malgunbd.ttf", font_size) # ๋ง์ ๊ณ ๋ bold
except:
font = ImageFont.load_default()
bbox_text = draw.textbbox((0, 0), text, font=font)
text_w = bbox_text[2] - bbox_text[0]
text_h = bbox_text[3] - bbox_text[1]
offset = 5 # ํ
์คํธ ์๋ก ์ฌ๋ฆด ํฝ์
์
text_x = center_x - text_w / 2
text_y = center_y - text_h / 2 - offset
text_layer = Image.new("RGBA", pil_img.size, (255, 255, 255, 0))
text_draw = ImageDraw.Draw(text_layer)
text_draw.text((text_x, text_y), text, font=font, fill=(255, 0, 0, 255))
rotated_layer = text_layer.rotate(angle, center=(center_x, center_y), resample=Image.BICUBIC)
pil_img = Image.alpha_composite(pil_img.convert("RGBA"), rotated_layer)
img[:] = cv2.cvtColor(np.array(pil_img.convert("RGB")), cv2.COLOR_RGB2BGR)
return img
masking_methods = {
"black": mask_black,
"blur": mask_blur,
"mosaic": mask_mosaic,
"replace_text": mask_replace_text
}
# ----- ์ด๋ฏธ์ง ์ฒ๋ฆฌ ํจ์ -----
def process_image(img_path, save_path, masking="black"):
img = cv2.imread(img_path)
if img is None:
print(f"์ด๋ฏธ์ง๋ฅผ ์ฝ์ ์ ์์ต๋๋ค: {img_path}")
return
result = ocr.predict(img_path)[0] # result[0] ์ฌ์ฉ
rec_texts = result['rec_texts']
#print(rec_texts) # OCRํ ํ
์คํธ๋ฅผ ์ถ๋ ฅํด๋ณด๊ณ ์ถ์ผ๋ฉด ์ฃผ์์ ์ง์ฐ์ธ์ฉ
dt_polys = result['dt_polys']
for text, box in zip(rec_texts, dt_polys):
if any(p.search(text) for p in patterns):
if masking in masking_methods:
img = masking_methods[masking](img, box)
cv2.imwrite(save_path, img)
print(f"์ ์ฅ ์๋ฃ: {save_path}")
# ----- ๋ฉ์ธ -----
if __name__ == "__main__":
input_dir = "idc_samples"
output_dir = "idc_results"
os.makedirs(output_dir, exist_ok=True)
# ์ ํ: "black", "blur", "mosaic", "replace_text"
masking_type = "black"
for file in os.listdir(input_dir):
if file.lower().endswith((".jpg", ".jpeg", ".png")):
input_path = os.path.join(input_dir, file)
output_path = os.path.join(output_dir, f"PaddleOCR_{masking_type}_{file}")
process_image(input_path, output_path, masking=masking_type)
# gpu ๋ฉ๋ชจ๋ฆฌ ํ์ธ
# mem_alloc = paddle.device.cuda.memory_allocated()
# mem_reserved = paddle.device.cuda.max_memory_reserved()
# print(f"๋ชจ๋ธ ๋ก๋ ํ ๋ฉ๋ชจ๋ฆฌ: {mem_alloc/1024**2:.2f} MB")
# print(f"์ต๋ ์์ฝ๋ ๋ฉ๋ชจ๋ฆฌ: {mem_reserved / 1024**2:.2f} MB")
# gpu ๋ฉ๋ชจ๋ฆฌ ํ์ธ
# mem_alloc = paddle.device.cuda.memory_allocated()
# mem_reserved = paddle.device.cuda.max_memory_reserved()
# print(f"OCR ์คํ ํ ๋ฉ๋ชจ๋ฆฌ: {paddle.device.cuda.memory_allocated()/1024**2:.2f} MB")
# print(f"์ต๋ ์์ฝ๋ ๋ฉ๋ชจ๋ฆฌ: {mem_reserved / 1024**2:.2f} MB")