برنامه نویسی

نوشتن برنامه چند رشته ای پراکندگی تصویر با Rust 🦀🖼️

پروژه را اینجا ببینید: https://github.com/NabeelAhmed1721/ordered-dithering/

من در چند ماه گذشته به زبان برنامه نویسی Rust علاقه مند شده ام. با توجه به جاوا اسکریپت، یک زبان با تایپ ضعیف، متوجه شدم که Rust بسیار سخت‌گیرانه‌تر است و در هنگام ایجاد یک جریان کنترل نیاز به تفکر بیشتری دارد.

فقط برای ثبت، dithering معمولا در CPU انجام نمی شود. این وظیفه ای است که برای یک GPU مناسب است، جایی که می تواند از موازی سازی استفاده کند. من از این پروژه به عنوان راهی برای یادگیری در مورد Rust و Multithreading استفاده کردم. اگر می خواهید راهنمای انجام دیترینگ در یک GPU انجام شود، به این نگاه کنید.

بررسی اجمالی

به زبان ساده، دیترینگ فرآیند افزودن نویز به داده های کوانتیزه شده است. برای درک داده های کوانتیزه شده، به حذف اطلاعات مورد نیاز برای نمایش برخی داده ها فکر کنید، به عنوان مثال، از رنگ کمتر برای بیان یک تصویر استفاده کنید.

توجه به این نکته مهم است که تداخل یک اصطلاح کلی در پردازش اطلاعات است. با استفاده از آن در صدا، رادار، آب و هوا و بسیاری از برنامه های کاربردی دیگر، به تصاویر محدود نمی شود.

چندین تکنیک پراکندگی تصویر وجود دارد، پروژه من از Ordered یا Bayer Dithering استفاده می کند. آیا کاربردی ترین است؟ احتمالا نه. اما اگر از من بپرسید، فکر می کنم از نظر بصری جالب ترین به نظر می رسد.

به هر حال، قبل از اینکه به خود پروژه بپردازیم، در اینجا نتایجی وجود دارد که شما را به ادامه خواندن ترغیب می کند:

تصویر پراکنده اپل (رنگی 8 بیتی)

تصویر پریشان از نور خورشید از طریق برگ ها

تصویر پراکنده از نور خورشید از طریق برگها (رنگ 8 بیتی)

تصویر پریشان گدازه

تصویر پراکنده گدازه (رنگ 8 بیتی)

دستور Dithering

برای پراکندگی تصویر با استفاده از پراکندگی مرتب، باید هر پیکسل در تصویر منبع را با یک پالت ورودی و یک ماتریس آستانه (که معمولاً به عنوان ماتریس بایر یا فیلتر نامیده می‌شود) مقایسه کنیم.

برای ثبات، پروژه ما از یک پالت رنگ 8 بیتی استفاده می کند که شامل رنگ های زیر است:

const PALETTE: [Color; 8] = [
    Color(0, 0, 0),          // black
    Color(255, 0, 0),        // red
    Color(0, 255, 0),        // green 
    Color(0, 0, 255),        // blue
    Color(255, 255, 0),      // yellow
    Color(255, 0, 255),      // magenta
    Color(0, 255, 255),      // cyan
    Color(255, 255, 255),    // white
];
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شما می توانید از هر مجموعه رنگی استفاده کنید، با این حال، من متوجه شدم که طیف رنگی 8 بیتی دارای تعادل قابل اعتمادی از رنگ ها است که برای اکثر تصاویر کار می کند.

برای ایجاد یک ماتریس آستانه، می‌توانیم از فرمول بازگشتی زیر برای ایجاد یک ماتریس بایر برای


nn

سطح :

بآyهr(n)=[4Bayer(n1)+04Bayer(n1)+24Bayer(n1)+34Bayer(n1)+1]

Bayer(n) = \begin{bmatrix} 4 \cdot Bayer(n – 1) + 0 & 4 \cdot Bayer(n – 1) + 2 \\ 4 \cdot Bayer(n – 1) + 3 & 4 \cdot Bayer(n – 1) + 1 \end{bmatrix}

کجا برای هر

nn

سطح، ماتریس است

2n+1×2n+12^{n+1} \times 2^{n+1}

و شامل اعداد از

00

به

22n+22^{2n+2}

.

اعتبار کامل این معادله و تشکر ویژه از این مقاله فوق العاده سورما است.

با این حال، در عمل، تولید این نوع محاسبات در طول زمان اجرا به سرعت گران می شود، بنابراین ارجاع از یک ماتریس از پیش تولید شده منطقی تر است. از این رو، چرا برنامه من از یک از پیش تولید شده استفاده می کند

8×88 \ برابر 8

ماتریس آستانه که به شکل زیر است:

[0328402341042481656245018582612444361446638602852206230542233511431339415119592749175725154773913455376331552361295321]

\begin{bmatrix} 0 & 32 & 8 & 40 & 2 & 34 & 10 & 42 \\ 48 & 16 & 56 & 24 & 50 & 18 & 58 & 26 \\ 12 & 44 & 4 & 36 & 14 & 46 & 6 & 38 \\ 60 & 28 & 52 & 20 & 62 & 30 & 54 & 22 \\ 3 & 35 & 11 & 43 & 1 & 33 & 9 & 41 \\ 51 & 19 & 59 & 27 & 49 & 17 و 57 و 25 \\ 15 و 47 و 7 و 39 و 13 و 45 و 5 و 37 \\ 63 و 31 و 55 و 23 و 61 و 29 و 53 و 21 \\ \پایان{bmatrix}

تفاوت در اندازه های ماتریس نشان دهنده پیچیدگی الگوی پراکندگی در تصویر خروجی است. یک کوچک

2×22 \ برابر 2

ماتریس خروجی با کنتراست قابل توجه و الگوی پراکندگی ناهموار تولید می کند. در حالی که بزرگتر

32×3232 \ برابر 32

ماتریس منجر به کنتراست صاف تر با الگوی ریزش دانه می شود. با این حال، بازده های کاهشی با اندازه های ماتریس بزرگتر وجود دارد – من متوجه شدم که یک

8×88 \ برابر 8

ماتریس بهترین عملکرد را برای صاف کردن رنگ ها دارد اما آن را نیز حفظ می کند نگاهی پر از پیکسل.

برای کاهش پیچیدگی، ماتریس را به صورت یک آرایه دو بعدی بیان می کنم و از طریق محاسبات زیر می توانم مختصات xy تصویر را به مقداری تبدیل کنم که بتوانم از آن برای فهرست بندی آرایه استفاده کنم:

// 8x8 Bayer Matrix
const MATRIX_WIDTH: u32 = 8;
const MATRIX: [u16; 64] = [
    0, 32, 8, 40, 2, 34, 10, 42, 48, 16, 56, 24, 50, 18, 58, 26, 12, 44, 4, 36,
    14, 46, 6, 38, 60, 28, 52, 20, 62, 30, 54, 22, 3, 35, 11, 43, 1, 33, 9, 41,
    51, 19, 59, 27, 49, 17, 57, 25, 15, 47, 7, 39, 13, 45, 5, 37, 63, 31, 55,
    23, 61, 29, 53, 21,
];

fn get_bayer(x: u32, y: u32) -> u16 {
    let pos = x % MATRIX_WIDTH
            + ((y * MATRIX_WIDTH) % (MATRIX_WIDTH * MATRIX_WIDTH));

    MATRIX[pos as usize]
}

وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با این حال، باید مقدار آستانه را از آن ترسیم کنیم

[0,22n+2][0, 2^{2n+2}]

به

[0,255][0, 255]

زیرا مقادیر رنگ RGB تصویر ورودی بین 0 تا 255 خواهد بود. برای حل این مشکل، از یک فرمول نگاشت محدوده ساده استفاده کردم:

pub fn map_range_value(
    value: u32,
    range_in: (u32, u32),
    range_out: (u32, u32),
) -> u32 {
    return (value - range_in.0) * (range_out.1 - range_out.0)
        / (range_in.1 - range_in.0)
        + range_out.0;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با ترکیب آنچه می دانیم، می توانیم مقدار آستانه خود را برای یک پیکسل معین در مختصات xy با عبارت زیر محاسبه کنیم:

let bay = utility::map_range_value(
    Dither::get_bayer(x, y),
    (0, 64),
    (0, 255),
);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای محاسبه مقدار کوانتیزه یک رنگ پیکسل معین، عدد را ضرب می کنیم bay ارزش با a spread ارزش. سپس محصول با رنگ ورودی جمع می شود که با a تنظیم می شود gamma ارزش:

let quantize_value = |c: u8| -> u8 {
    f32::min(
        255.0 * f32::powf(f32::from(c) / 255.0, self.gamma)
            + self.spread * f32::from(bay),
        255.0,
    ) as u8
};

let query_color =
    Color(quantize_value(r), quantize_value(g), quantize_value(b));
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در نهایت استفاده می کنیم query_color برای جستجوی نزدیکترین تطابق در پالت تعریف شده. سپس نزدیکترین تطابق رنگ به عنوان مقدار پیکسل در مختصات xy داده شده در تصویر خروجی تنظیم می شود. تکرار این فرآیند برای هر پیکسل در یک تصویر ورودی منجر به یک تصویر خروجی پراکنده می شود.

چند رشته ای

از آنجایی که فرآیند dithering خود می تواند بر روی هر پیکسل به طور مستقل اجرا شود، به عبارت دیگر، یک پیکسل جداگانه نیازی به دانستن وضعیت پیکسل دیگر ندارد، این یک فرصت ایده آل برای چند رشته ای ایجاد می کند. خوشبختانه، به دلیل بررسی قرض Rust، چند رشته ای ساده و شهودی است. مواجهه با باگ های رایج مانند مسابقه داده ها، قفل ها و نشت حافظه سخت تر است.

بسته به برنامه کاربردی، مدیریت چند رشته ای ممکن است دشوار باشد. به نظر من تجسم جریان کنترل برای جلوگیری از سردرگمی هنگام برنامه‌نویسی مفید است. انتظار دارم برنامه من چگونه اجرا شود به شرح زیر است:

فرآیند تصویر چند رشته ای

تقسیم تصویر به تکه‌های مجزا و مجزا، امکان جمع‌آوری راحت را فراهم می‌کند. از آنجایی که به هر رشته یک شناسه اختصاص داده شده است، می توانیم از فرمول های زیر برای محاسبه مکان های شروع و پایان هر چاک استفاده کنیم:

let thread_location_start =
    (area / self.thread_count) * thread_id;

let mut thread_location_end =
    (area / self.thread_count) * (thread_id + 1) - 1;

// looping through specific chunk
for i in thread_location_start..thread_location_end {
    // dithering logic...
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای شناسایی و مدیریت موضوعات، ماژول کمکی جداگانه ای به نام ایجاد کردم worker. در داخل ماژول، یک ساختار فراخوانی شده است Manager همه رشته ها را ذخیره می کند (به صورت جداگانه نامیده می شود Worker) در یک vec و a را اجرا می کند collect روش زمانی که نخ ها کامل می شوند. به طور کلی، این به من امکان داد تا منطق چند رشته ای را انتزاعی کنم و کد خود را قابل مدیریت تر نگه دارم.

let mut manager = worker::Manager::<DitherJob>::new(THREAD_COUNT);
let worker_count = manager.worker_count;

let dither = Arc::new(Dither::new(
    worker_count,
    reference_image,
    &PALETTE,
    GAMMA,
    SPREAD,
));

manager.set_workers(&|id| {
    let dither = Arc::clone(&dither);
    thread::spawn(move || dither.dither_section(id))
});

manager.collect(&mut output);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توجه کنید که چگونه Dither پیچیده شده است Arc::new. برای به اشتراک گذاشتن ایمن مالکیت داده ها در سراسر رشته ها، باید از شمارنده مرجع اتمی (Arc) استفاده شود. تعداد مالکان را نگه می دارد و زمانی که هیچ رشته ای از آن استفاده نمی کند، مقدار را کاهش می دهد.

نتیجه

به طور کلی، من از نحوه اجرای برنامه راضی هستم. با اینکه هنوز Rust را یاد می‌گیرم، این پروژه به من کمک کرد تا در استفاده از زبان اعتماد به نفس بیشتری پیدا کنم و به من امکان داد تا ایده‌های جدید در پردازش تصویر را کشف کنم.

امیدوارم از خواندن مقاله من لذت برده باشید، و دوباره می توانید پروژه زیر را بررسی کنید:

https://github.com/NabeelAhmed1721/ordered-dithering/

متشکرم،
نبیل احمد

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

همچنین ببینید
بستن
دکمه بازگشت به بالا