فهرست مقاله
در مسیرِ احتمالیِ ورودِ صفحههای اسکنشده به دانشِ سازمانی، پیشپردازشِ شدت باید قراردادِ مستقلی داشته باشد. این درس فقط یک ماتریسِ خاکستریِ ساختگی را میگیرد و میانهٔ محلی میسازد. فایلِ PDF باز نمیشود، صفحهای رندر نمیشود و هیچ متن یا پاسخِ سازمانی تولید نمیشود. هدف، فهمیدنِ دقیقِ انتخابِ رتبه و اثرِ لبه است؛ پیش از آنکه تغییرِ یک تصویر را به بهترشدنِ خواندن نسبت بدهیم.
۱. پیشپردازشِ یک ماتریس را از خواندنِ PDF جدا کنیم

ورودی، تاپلی از سطرهای تاپلیِ هماندازه است؛ هر بُعد یک تا دوازده و هر خانه دقیقاً عدد صحیحِ صفر تا ۲۵۵ است. این دامنه را کدِ دیجیتالِ هشتبیتی مینامیم، نه شدتِ نورِ کالیبرهشده. مقدارِ bool، عددِ شناور، رشته، سطرِ خالی، زیرکلاسِ عدد صحیح و جدولِ ناهموار رد میشوند. برنامه تصویرِ رنگی را پنهانی خاکستری یا مقدارِ نامعتبر را محدود نمیکند.
شکلِ خروجی با ورودی برابر است و مختصات به ترتیبِ سطر و ستون نوشته میشوند. فاصلهٔ فیزیکیِ پیکسل، وضوحِ چاپ، اندازهٔ قلم یا زاویهٔ صفحه نداریم. بنابراین تغییرِ سه خانه، اندازهگیریِ سه لکه یا اصلاحِ سه نویسه نیست. اگر این جزء بعداً در زنجیرهٔ واقعی استفاده شود، تبدیلِ فایل به ماتریس و ارزیابیِ متن باید جداگانه تعریف و آزمایش شوند.
نمونهٔ اصلی سه سطر و چهار ستون دارد. همهٔ مقدارها بیستاند، جز خانهٔ میانیِ سطرِ دوم که ۲۵۰ است. انتخابِ این اختلاف فقط شاهدی برای محاسبهٔ رتبه است؛ در دادهٔ واقعی نمیتوان از روشنتر بودنِ یک نقطه نتیجه گرفت که حتماً نویز است. یک جزئیاتِ مفید نیز ممکن است همین شکلِ محلی را داشته باشد.
۲. میانهٔ نه عضو، میانگین و فیلترِ خطی نیست

برای هر خروجی، پنجرهٔ سه در سه را با مرکزِ همان پیکسل میگیریم. نُه مقدار، با تمامِ تکرارهایشان، مرتب میشوند و عضوِ پنجم انتخاب میشود؛ اندیسِ آن در پایتون چهار است. مستندات median_filter در SciPy انتخابِ رتبهٔ میانیِ ردپا را توضیح میدهد. ردپای ما همیشه نُه عضو دارد؛ تعریفِ میانه برای تعدادِ زوج واردِ این قرارداد نمیشود.
مسیرِ اصلی بهجای مرتبکردن، برای هر شدت یک شمارنده میسازد. نُه همسایه شمارش میشوند و از شدتِ صفر به بالا، تعدادها جمع میشوند. اولین شدت با جمعِ تجمعیِ حداقل پنج، جواب است. اینجا هیستوگرامِ محلی داریم؛ با هیستوگرامِ سراسریِ آستانهگذاری Otsu فرق دارد. مکانِ همسایگی در تعیینِ هر جواب دخالت میکند.
در نمونهٔ اصلی، خروجی همهجا بیست میشود؛ اما این نتیجه تضمینِ حذفِ نویز نیست. میانه عضوِ موجودِ پنجره را برمیگزیند و شدتِ تازهای از میانگین نمیسازد. در مقابل، هموارسازیِ Gaussian با وزنهای مشخص جمعِ وزندار دارد. نمیتوان فرمولِ جمعپذیریِ آن فیلترِ خطی را بیبررسی به میانه منتقل کرد.
۳. تکرارِ لبه، تعدادِ رأیِ همسایهها را عوض میکند

سیاستِ این برنامه، ادامهدادنِ نزدیکترین پیکسلِ لبه است. مختصاتِ بیرون، به نزدیکترین مختصاتِ معتبر محدود میشود؛ همسایه حذف نمیشود. تعریفِ edge در numpy.pad نیز تکرارِ مقدارِ لبه را بیان میکند. این مرجع برای روشنکردنِ قرارداد است؛ در اجرایِ حاضر NumPy و SciPy فراخوانی نشدهاند.
ماتریسِ دو در دو با سطرهای ده، دویست و سی، چهل را در نظر بگیرید. پنجرهٔ گوشهٔ بالاچپ پس از مرتبشدن چنین است: ده، ده، ده، ده، سی، سی، چهل، دویست، دویست. پس جواب سی است. ده چهار بار رأی دارد، چون همان پیکسلِ گوشه چهار جایگاهِ پنجره را پوشش داده است. حذفِ مقدارهای تکراری، مسئله را عوض میکند.
با فرضِ بیرونِ صفر، همین پنجره پنج صفر دارد و جوابِ گوشه صفر میشود. با بازتابِ حولِ مرکزِ پیکسلِ انتهایی، یعنی mirror در اصطلاحِ SciPy، جوابِ این گوشه چهل است. این سه جواب ناشی از سه قراردادند. پیشفرضِ reflect در SciPy بازتابِ نیمنمونهای است؛ برای پنجرهٔ مرکزیِ سه در سه و تنها یک خانهٔ بیرون، با nearest یکسان میشود. این برابریِ محدود را به هر اندازهٔ پنجره یا مبدأ تعمیم نمیدهیم.
۴. کدِ کامل و مرجعِ مستقلِ پنجرهٔ مرتب

فایلِ median_nearest_border.py با CPython 3.14.5 و فقط کتابخانهٔ استاندارد، یک بار اجرا شده است. کد را کامل ذخیره کنید و با دستورِ python median_nearest_border.py اجرا کنید. تابعِ عمومی فقط همین ورودیِ محدود و همین پنجره و مرز را میپذیرد. اعتبارسنجی پیش از ساختِ خروجی انجام میشود و خطاهای شکل و پیکسل، دستههای جدا دارند.
"""Bounded synthetic grayscale3x3 median with nearest-edge sample repetition.
Pure standard library; no PDF/OCR, NumPy/SciPy execution or quality claim.
"""
from enum import IntEnum
from itertools import product
import json
import platform
def _shape(image):
if type(image) is not tuple or not 1 <= len(image) <= 12:
raise ValueError("image_shape")
if type(image[0]) is not tuple or not 1 <= len(image[0]) <= 12:
raise ValueError("image_shape")
width = len(image[0])
if any(type(row) is not tuple or len(row) != width for row in image):
raise ValueError("image_shape")
if any(type(value) is not int or not 0 <= value <= 255
for row in image for value in row):
raise ValueError("gray8_pixel")
return len(image), width
def median3_nearest(image):
"""Fifth order statistic of all nine centered samples, including repeats."""
height, width = _shape(image)
output = []
for row in range(height):
line = []
for col in range(width):
counts = [0] * 256
for dy in (-1, 0, 1):
y = min(max(row + dy, 0), height - 1)
for dx in (-1, 0, 1):
x = min(max(col + dx, 0), width - 1)
counts[image[y][x]] += 1
cumulative = 0
for value, count in enumerate(counts):
cumulative += count
if cumulative >= 5:
line.append(value)
break
output.append(tuple(line))
return tuple(output)
def sorted_padded_oracle(image):
"""Valid-fixture reference: explicit duplicated frame then nine-value sort.
No main call, validation helper, histogram or clamped-coordinate formula.
"""
framed_rows = [(row[0],) + row + (row[-1],) for row in image]
framed = [framed_rows[0]] + framed_rows + [framed_rows[-1]]
height, width = len(image), len(image[0])
return tuple(tuple(sorted(
framed[top][left]
for top in range(row, row + 3)
for left in range(col, col + 3)
)[4] for col in range(width)) for row in range(height))
def transpose(image):
return tuple(tuple(row[c] for row in image) for c in range(len(image[0])))
def mapped(image, function):
return tuple(tuple(function(v) for v in row) for row in image)
def rejects(image, category):
try:
median3_nearest(image)
except ValueError as error:
return str(error) == category
return False
def run():
image = ((20, 20, 20, 20), (20, 250, 20, 20), (20, 20, 20, 20))
actual = median3_nearest(image)
corner = ((10, 200), (30, 40))
corner_out = median3_nearest(corner)
nearest_corner_nine = [10, 10, 200, 10, 10, 200, 30, 30, 40]
zero_corner_nine = [0, 0, 0, 0, 10, 200, 0, 30, 40]
mirror_corner_nine = [40, 30, 40, 200, 10, 200, 40, 30, 40]
checks = {}
checks["literal_isolated_bright_sample_and_shape"] = (
actual == ((20, 20, 20, 20),) * 3
and len(actual) == 3 and all(len(row) == 4 for row in actual)
)
checks["literal_corner_multiset_and_fifth_rank"] = (
corner_out == ((30, 40), (30, 40))
and sorted(nearest_corner_nine) == [10, 10, 10, 10, 30, 30, 40, 200, 200]
and sorted(nearest_corner_nine)[4] == 30
)
checks["literal_zero_and_whole_sample_mirror_are_other_contracts"] = (
sorted(zero_corner_nine)[4] == 0
and sorted(mirror_corner_nine)[4] == 40
and corner_out[0][0] not in (0, 40)
)
checks["single_pixel_and_single_row_or_column_keep_nine_samples"] = (
all(median3_nearest(((v,),)) == ((v,),) for v in (0, 1, 127, 255))
and median3_nearest(((0, 255, 0),)) == ((0, 0, 0),)
and median3_nearest(((0,), (255,), (0,))) == ((0,), (0,), (0,))
)
binary_cases = 0
binary_ok = True
for height in range(1, 4):
for width in range(1, 4):
for values in product((0, 255), repeat=height * width):
fixture = tuple(tuple(values[r * width + c] for c in range(width))
for r in range(height))
binary_ok &= median3_nearest(fixture) == sorted_padded_oracle(fixture)
binary_cases += 1
checks["independent_explicit_frame_sorted_oracle_all682_binary_fixtures"] = (
binary_cases == 682 and bool(binary_ok)
)
gray_cases = 0
gray_ok = True
for height, width in product(range(1, 13), repeat=2):
fixture = tuple(tuple((r * 73 + c * 37 + height * width) % 256
for c in range(width)) for r in range(height))
got = median3_nearest(fixture)
gray_ok &= got == sorted_padded_oracle(fixture)
gray_ok &= all(0 <= v <= 255 for row in got for v in row)
gray_cases += 1
checks["independent_sorted_oracle_all144_bounded_grayscale_shapes"] = (
gray_cases == 144 and bool(gray_ok)
)
checks["all256_constant_fields_preserved_including_boundaries"] = all(
median3_nearest(((v,) * 4,) * 3) == ((v,) * 4,) * 3
for v in range(256)
)
fixture = ((0, 10, 90, 40), (70, 200, 30, 5), (250, 80, 60, 110))
filtered = median3_nearest(fixture)
checks["transpose_and_both_axis_reflections_preserve_geometry"] = (
median3_nearest(transpose(fixture)) == transpose(filtered)
and median3_nearest(fixture[::-1]) == filtered[::-1]
and median3_nearest(tuple(row[::-1] for row in fixture))
== tuple(row[::-1] for row in filtered)
)
checks["order_reversing_complement_preserves_odd_median"] = (
median3_nearest(mapped(fixture, lambda v: 255 - v))
== mapped(filtered, lambda v: 255 - v)
)
low = ((0, 10, 20), (30, 40, 50), (60, 70, 80))
checks["within_range_positive_affine_intensity_map_preserves_rank"] = (
median3_nearest(mapped(low, lambda v: 2 * v + 5))
== mapped(median3_nearest(low), lambda v: 2 * v + 5)
)
ordered_pairs = 0
increasing_ok = True
for level in range(16):
base = tuple(tuple((r * 11 + c * 7 + level) % 128
for c in range(4)) for r in range(3))
upper = mapped(base, lambda v: v + level)
left, right = median3_nearest(base), median3_nearest(upper)
increasing_ok &= all(a <= b for ra, rb in zip(left, right)
for a, b in zip(ra, rb))
ordered_pairs += 1
checks["pointwise_order_preserved_on16_finite_image_pairs"] = (
ordered_pairs == 16 and bool(increasing_ok)
)
a, b = ((0, 0, 100),), ((0, 100, 0),)
summed = tuple(tuple(x + y for x, y in zip(ra, rb)) for ra, rb in zip(a, b))
fa, fb = median3_nearest(a), median3_nearest(b)
summed_results = tuple(tuple(x + y for x, y in zip(ra, rb))
for ra, rb in zip(fa, fb))
line = ((0, 255, 0, 255, 0),)
once = median3_nearest(line)
twice = median3_nearest(once)
checks["finite_nonlinearity_and_nonidempotence_counterexamples"] = (
median3_nearest(summed) == ((0, 100, 100),)
and summed_results == ((0, 0, 100),)
and once == ((0, 0, 255, 0, 0),) and twice == ((0, 0, 0, 0, 0),)
)
class Foreign(IntEnum):
ONE = 1
class IntegerSubclass(int):
pass
class TupleSubclass(tuple):
pass
bad_shapes = [None, [], (), ((),), ((0,), (1, 2)), ([0],),
TupleSubclass(((0,),)), (TupleSubclass((0,)),),
((0,),) * 13, ((0,) * 13,)]
bad_pixels = [True, False, 1.0, float("nan"), float("inf"), -1, 256,
"1", None, Foreign.ONE, IntegerSubclass(1)]
checks["strict_input_types_shapes_ranges_maximum_and_no_mutation"] = (
all(rejects(v, "image_shape") for v in bad_shapes)
and all(rejects(((v,),), "gray8_pixel") for v in bad_pixels)
and median3_nearest(((255,) * 12,) * 12) == ((255,) * 12,) * 12
and image == ((20, 20, 20, 20), (20, 250, 20, 20), (20, 20, 20, 20))
and all(type(row) is tuple for row in actual)
)
if not all(type(value) is bool and value for value in checks.values()):
raise AssertionError(checks)
return {
"python": platform.python_version(), "dependencies": "standard library only",
"contract": {"grayscale": "exactint0..255 digital codes, not calibrated units",
"shape": "exacttuple of exacttuple, dimensions1..12",
"window": "centered3x3 with nearest-edge repetitions, fifth of nine",
"origin": 0, "numpy_executed": False, "scipy_executed": False},
"synthetic_input": image, "median_output": actual,
"corner_input": corner, "corner_output": corner_out,
"corner_sorted_nearest_samples": sorted(nearest_corner_nine),
"corner_alternative_contracts": {"nearest": 30, "constant_zero": 0,
"whole_sample_mirror": 40},
"nonlinear_sum_filter_output": median3_nearest(summed),
"sum_of_separate_filter_outputs": summed_results,
"nonidempotent_input": line, "first_output": once, "second_output": twice,
"oracle_cases_inside_groups": {"binary": binary_cases, "grayscale_shapes": gray_cases,
"ordered_pairs": ordered_pairs},
"checks": checks, "check_count": len(checks), "all_passed": True,
"limitations": ["Only a finite synthetic matrix component; no PDF read/render, OCR or ingestion run.",
"No universal noise-removal, image-quality, character-preservation or speed claim.",
"No arbitrary footprint/origin/color/float/NaN or library API-conformance claim.",
"For centered radius1, half-sample reflect and nearest share the same one-cell extension."],
}
if __name__ == "__main__":
print(json.dumps(run(), ensure_ascii=False, indent=2, allow_nan=False))
مرجعِ مستقل، دورِ ماتریس یک قابِ صریح میسازد: ابتدا مقدارِ اول و آخرِ هر سطر را تکرار میکند و سپس سطرِ اول و آخرِ قابدار را اضافه میکند. هر پنجرهٔ نُهعضوی واقعاً مرتب میشود. این مسیر از هیستوگرام، تابعِ اصلی یا فرمولِ محدودکردنِ مختصات استفاده نمیکند. استقلال، در روشِ ساختِ همسایه و انتخابِ رتبه است؛ هر دو مسیر باید همان قراردادِ مرزی را حل کنند.
همهٔ ۶۸۲ ماتریسِ دودامنهای با ابعادِ یک تا سه، در یک گروه با مرجع مقایسه شدند. یک گروهِ دیگر، ۱۴۴ شکلِ یک تا دوازده را با شدتهای متنوع میسنجد. حفظِ میدانِ ثابت، ترانهاده و بازتابِ تصویر، وارونگیِ شدت و نمونههای نامعتبر نیز بررسی شدهاند. تمامِ سیزده گروهِ نامدار گذشتند؛ شمارِ حالتهای داخلِ گروه را گروهِ اضافی نمینامیم.
{
"python": "3.14.5",
"dependencies": "standard library only",
"contract": {
"grayscale": "exactint0..255 digital codes, not calibrated units",
"shape": "exacttuple of exacttuple, dimensions1..12",
"window": "centered3x3 with nearest-edge repetitions, fifth of nine",
"origin": 0,
"numpy_executed": false,
"scipy_executed": false
},
"synthetic_input": [
[
20,
20,
20,
20
],
[
20,
250,
20,
20
],
[
20,
20,
20,
20
]
],
"median_output": [
[
20,
20,
20,
20
],
[
20,
20,
20,
20
],
[
20,
20,
20,
20
]
],
"corner_input": [
[
10,
200
],
[
30,
40
]
],
"corner_output": [
[
30,
40
],
[
30,
40
]
],
"corner_sorted_nearest_samples": [
10,
10,
10,
10,
30,
30,
40,
200,
200
],
"corner_alternative_contracts": {
"nearest": 30,
"constant_zero": 0,
"whole_sample_mirror": 40
},
"nonlinear_sum_filter_output": [
[
0,
100,
100
]
],
"sum_of_separate_filter_outputs": [
[
0,
0,
100
]
],
"nonidempotent_input": [
[
0,
255,
0,
255,
0
]
],
"first_output": [
[
0,
0,
255,
0,
0
]
],
"second_output": [
[
0,
0,
0,
0,
0
]
],
"oracle_cases_inside_groups": {
"binary": 682,
"grayscale_shapes": 144,
"ordered_pairs": 16
},
"checks": {
"literal_isolated_bright_sample_and_shape": true,
"literal_corner_multiset_and_fifth_rank": true,
"literal_zero_and_whole_sample_mirror_are_other_contracts": true,
"single_pixel_and_single_row_or_column_keep_nine_samples": true,
"independent_explicit_frame_sorted_oracle_all682_binary_fixtures": true,
"independent_sorted_oracle_all144_bounded_grayscale_shapes": true,
"all256_constant_fields_preserved_including_boundaries": true,
"transpose_and_both_axis_reflections_preserve_geometry": true,
"order_reversing_complement_preserves_odd_median": true,
"within_range_positive_affine_intensity_map_preserves_rank": true,
"pointwise_order_preserved_on16_finite_image_pairs": true,
"finite_nonlinearity_and_nonidempotence_counterexamples": true,
"strict_input_types_shapes_ranges_maximum_and_no_mutation": true
},
"check_count": 13,
"all_passed": true,
"limitations": [
"Only a finite synthetic matrix component; no PDF read/render, OCR or ingestion run.",
"No universal noise-removal, image-quality, character-preservation or speed claim.",
"No arbitrary footprint/origin/color/float/NaN or library API-conformance claim.",
"For centered radius1, half-sample reflect and nearest share the same one-cell extension."
]
}
۵. چهار پرسش دربارهٔ مرز، جزئیات و محدودیت

پرسش اول: تکپیکسلِ ۲۵۵ چه خروجی میدهد؟ پاسخ: همان ۲۵۵؛ نُه جایگاهِ پنجره همگی از همان پیکسل خوانده میشوند. اندازهٔ تصویر اجازه نمیدهد پنجره را به یک نمونه با قراردادی دیگر تبدیل کنیم. این نتیجه به تکرارِ لبه وابسته است؛ با بیرونِ صفر، پاسخِ همان تکپیکسل متفاوت خواهد بود.
پرسش دوم: آیا میانهٔ جمع، جمعِ میانههاست؟ پاسخ: خیر. برای دو سطرِ صفر، صفر، صد و صفر، صد، صفر، جمعِ خروجیهای جدا صفر، صفر، صد است؛ ولی فیلترِ مجموعِ ورودیها صفر، صد، صد میدهد. تمامِ شدتهای این شاهد داخلِ دامنهاند؛ تفاوت به محدودسازی یا سرریز نسبت داده نمیشود.
پرسش سوم: اجرای دوباره بیاثر است؟ پاسخ: الزاماً نه. سطرِ صفر، ۲۵۵، صفر، ۲۵۵، صفر ابتدا به صفر، صفر، ۲۵۵، صفر، صفر میرسد و بارِ دوم تماماً صفر میشود. پس این فیلتر را مانندِ یک عملگرِ همواره پایدار با اجرایِ دوم معرفی نمیکنیم. تعدادِ گذرها باید جزو قرارداد باشد.
پرسش چهارم: تطابقِ دو روش، بهبودِ OCR را ثابت میکند؟ پاسخ: خیر؛ هیچ صفحه، نویسه یا متنِ مرجعی نداریم. فقط محاسبهٔ رتبهٔ محدود تأیید شده است. منابعِ جاریِ SciPy 1.18.0 و NumPy 2.5 در ۲۷ مهر ۱۴۰۵ خوانده شدند؛ تاریخِ اولیهٔ انتشارِ این صفحهها معلوم نیست. این آموزش، خبرِ تازه یا آزمونِ کتابخانهها نیست و سرعت، حفظِ حروف یا کیفیتِ پاسخگویی را گزارش نمیکند.
منابع و مطالعهٔ بیشتر
بررسی منابع: ۲۷ مهر ۱۴۰۵. تحلیلها و پیشنهادهای اجرایی، جمعبندی نویسندهاند.

