فیلترهای تصویر با بوم

Ilmari Heikkinen

معرفی

عنصر HTML5 canvas را می توان برای نوشتن فیلترهای تصویر استفاده کرد. کاری که باید انجام دهید این است که یک تصویر را روی بوم بکشید، پیکسل های بوم را دوباره بخوانید و فیلتر خود را روی آنها اجرا کنید. سپس می‌توانید نتیجه را روی یک بوم جدید بنویسید (یا خوب، فقط از بوم قبلی استفاده مجدد کنید.)

ساده به نظر می رسد؟ خوب بیایید کرک کنیم!

تصویر اصلی تست
تصویر اصلی تست

پردازش پیکسل ها

ابتدا پیکسل های تصویر را بازیابی کنید:

Filters = {};
Filters.getPixels = function(img) {
var c = this.getCanvas(img.width, img.height);
var ctx = c.getContext('2d');
ctx.drawImage(img);
return ctx.getImageData(0,0,c.width,c.height);
};

Filters.getCanvas = function(w,h) {
var c = document.createElement('canvas');
c.width = w;
c.height = h;
return c;
};

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

Filters.filterImage = function(filter, image, var_args) {
var args = [this.getPixels(image)];
for (var i=2; i<arguments.length; i++) {
args.push(arguments[i]);
}
return filter.apply(null, args);
};

اجرای فیلترهای ساده

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

Filters.grayscale = function(pixels, args) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];
// CIE luminance for the RGB
// The human eye is bad at seeing red and blue, so we de-emphasize them.
var v = 0.2126*r + 0.7152*g + 0.0722*b;
d[i] = d[i+1] = d[i+2] = v
}
return pixels;
};

تنظیم روشنایی را می توان با افزودن یک مقدار ثابت به پیکسل ها انجام داد:

Filters.brightness = function(pixels, adjustment) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
d[i] += adjustment;
d[i+1] += adjustment;
d[i+2] += adjustment;
}
return pixels;
};

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

Filters.threshold = function(pixels, threshold) {
var d = pixels.data;
for (var i=0; i<d.length; i+=4) {
var r = d[i];
var g = d[i+1];
var b = d[i+2];
var v = (0.2126*r + 0.7152*g + 0.0722*b >= threshold) ? 255 : 0;
d[i] = d[i+1] = d[i+2] = v
}
return pixels;
};

تصاویر درگیر

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

Filters.tmpCanvas = document.createElement('canvas');
Filters.tmpCtx = Filters.tmpCanvas.getContext('2d');

Filters.createImageData = function(w,h) {
return this.tmpCtx.createImageData(w,h);
};

Filters.convolute = function(pixels, weights, opaque) {
var side = Math.round(Math.sqrt(weights.length));
var halfSide = Math.floor(side/2);
var src = pixels.data;
var sw = pixels.width;
var sh = pixels.height;
// pad output by the convolution matrix
var w = sw;
var h = sh;
var output = Filters.createImageData(w, h);
var dst = output.data;
// go through the destination image pixels
var alphaFac = opaque ? 1 : 0;
for (var y=0; y<h; y++) {
for (var x=0; x<w; x++) {
  var sy = y;
  var sx = x;
  var dstOff = (y*w+x)*4;
  // calculate the weighed sum of the source image pixels that
  // fall under the convolution matrix
  var r=0, g=0, b=0, a=0;
  for (var cy=0; cy<side; cy++) {
    for (var cx=0; cx<side; cx++) {
      var scy = sy + cy - halfSide;
      var scx = sx + cx - halfSide;
      if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
        var srcOff = (scy*sw+scx)*4;
        var wt = weights[cy*side+cx];
        r += src[srcOff] * wt;
        g += src[srcOff+1] * wt;
        b += src[srcOff+2] * wt;
        a += src[srcOff+3] * wt;
      }
    }
  }
  dst[dstOff] = r;
  dst[dstOff+1] = g;
  dst[dstOff+2] = b;
  dst[dstOff+3] = a + alphaFac*(255-a);
}
}
return output;
};

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

Filters.filterImage(Filters.convolute, image,
[  0, -1,  0,
-1,  5, -1,
  0, -1,  0 ]
);

در اینجا یک مثال دیگر از فیلتر کانولوشن، جعبه تاری است. جعبه تاری میانگین مقادیر پیکسل را در داخل ماتریس کانولوشن خروجی می دهد. راه برای انجام این کار ایجاد یک ماتریس کانولوشن به اندازه NxN است که در آن هر یک از وزن‌ها 1 / (NxN) است. به این ترتیب هر یک از پیکسل های داخل ماتریس به مقدار مساوی در تصویر خروجی کمک می کند و مجموع وزن ها یک است.

Filters.filterImage(Filters.convolute, image,
[ 1/9, 1/9, 1/9,
1/9, 1/9, 1/9,
1/9, 1/9, 1/9 ]
);

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

با توجه به اصطلاحات، "gradient" در اینجا به معنای تغییر در مقدار پیکسل در یک موقعیت تصویر است. اگر یک پیکسل یک همسایه چپ با مقدار 20 و یک همسایه راست با مقدار 50 داشته باشد، شیب افقی در پیکسل 30 خواهد بود. گرادیان عمودی نیز همین ایده را دارد اما از همسایه های بالا و پایین استفاده می کند.

var grayscale = Filters.filterImage(Filter.grayscale, image);
// Note that ImageData values are clamped between 0 and 255, so we need
// to use a Float32Array for the gradient values because they
// range between -255 and 255.
var vertical = Filters.convoluteFloat32(grayscale,
[ -1, 0, 1,
-2, 0, 2,
-1, 0, 1 ]);
var horizontal = Filters.convoluteFloat32(grayscale,
[ -1, -2, -1,
  0,  0,  0,
  1,  2,  1 ]);
var final_image = Filters.createImageData(vertical.width, vertical.height);
for (var i=0; i<final_image.data.length; i+=4) {
// make the vertical gradient red
var v = Math.abs(vertical.data[i]);
final_image.data[i] = v;
// make the horizontal gradient green
var h = Math.abs(horizontal.data[i]);
final_image.data[i+1] = h;
// and mix in some blue for aesthetics
final_image.data[i+2] = (v+h)/4;
final_image.data[i+3] = 255; // opaque alpha
}

و یک دسته کامل از فیلترهای کانولوشن جالب دیگر وجود دارد که منتظرند شما آنها را کشف کنید. به عنوان مثال، سعی کنید یک فیلتر لاپلاس را در اسباب بازی کانولوشن بالا اجرا کنید و ببینید چه کاری انجام می دهد.

نتیجه

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

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