Из статьи на Википедии:

В компьютерном зрении перекрёстный оператор Робертса — один из ранних алгоритмов выделения границ, который вычисляет сумму квадратов разниц между диагонально смежными пикселами. Это может быть выполнено сверткой изображения с двумя ядрами:

Иными словами, каждый пиксель получаемого изображения вычисляется по правилу:

a = input_image(x, y) - input_image(x + 1, y + 1)
b = input_image(x + 1,y) - input_image(x, y + 1)
output_image(x, y) = sqrt(a^2 + b^2)

Что это всё значит разберём чуть позже, а пока давайте взглянем на работу фильтра:

Оригинальное изображение

После фильтрации с помощью перекрёстного оператора Робертса

Оригинальное изображение

После фильтрации с помощью перекрёстного оператора Робертса

Оригинальное изображение

После фильтрации с помощью перекрёстного оператора Робертса

Описание программы

Изображение является двоичным файлом со следующей структурой:

Формат входного файла

Входное и выходное изображения храняться в массивах:

int *input = new int[w * h];
int *output = new int[w * h];

Так как четыре компоненты цвета храняться в одном int, то для работы с ними на GPU используется распаковка и запаковка их обратно с помощью сдвигов (подсмотрено в примерах CUDA):

__device__ int pack(int4 rgba)
{
    return (rgba.w << 24) | (rgba.z << 16) | (rgba.y << 8) | rgba.x;
}
__device__ int4 unpack(int c)
{
  int4 rgba;
  rgba.x = c & 0xff;
  rgba.y = (c>>8) & 0xff;
  rgba.z = (c>>16) & 0xff;
  rgba.w = (c>>24) & 0xff;
  return rgba;
}

Яркость находится из формул конверсии RGB -> YUV:

Y = 0.299 * R + 0.587 * G + 0.114 * B;
U = -0.14713 * R - 0.28886 * G + 0.436 * B + 128;
V = 0.615 * R - 0.51499 * G - 0.10001 * B + 128;

здесь Y — яркостная составляющая, U и V — цветоразностные составляющие.

В итоге для яркости:

__device__ double intensity(int4 rgba)
{
    return 0.299 * rgba.x + 0.587 * rgba.y + 0.114 * rgba.z;
}

Для каждого пикселя нового изображения (матрицы w* h) считается новое значение цвета по формуле для перекрёстного оператора Робертса:

double m = intensity(unpack(get_element(dev_input, w, h, i, j)));
double br = intensity(unpack(get_element(dev_input, w, h, i + 1, j + 1)));
double r = intensity(unpack(get_element(dev_input, w, h, i, j + 1)));
double b = intensity(unpack(get_element(dev_input, w, h, i + 1, j)));
double tmp1 = m - br;
double tmp2 = r - b;
int gradient = (int) sqrt(tmp1 * tmp1 + tmp2 * tmp2);
// Нормировка
if(gradient > 255) gradient = 255;
// Записываем интенсивность в RGB
dev_output[i * w + j] = pack(make_int4(gradient, gradient, gradient, 0));

Работа программы была проверена на различных вычислительных сетках (количество блоков и потоков).

Для построения графиков использовался Python + matplotlib.

import os
from os.path import basename, splitext
from numpy import linspace, array
import matplotlib
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from multiprocessing import Pool

# Настройка шрифтов
matplotlib.rc('font', family='Tahoma', size=16)
matplotlib.rc('axes', titlesize=22)
matplotlib.rc('axes', labelsize=18)
matplotlib.rc('legend', fontsize=16)
matplotlib.rc('xtick', labelsize=12)
matplotlib.rc('ytick', labelsize=12)


# Время выполнения для теста <size> для программы <program_name>
def calculate_time(params):
    program_name = params['program_name']
    size = params['size']
    in_name = 'in{}.data'.format(size)
    if not os.path.isfile(in_name):
        return None
    out_name = 'out{}.data'.format(size)
    output = os.popen('echo {} {} | {}'.format(in_name, out_name, program_name)).read()
    output = float(output)
    print('Программа {}, тест {}, время {} мс'.format(program_name, in_name, output))
    return {'size': size, 'time': output}


# Расчёт времени выполнения для сеток от 1x1 до 3000x3000 для существующих тестов
def run_test_for_program(program_name):
    params = [{'program_name': program_name, 'size': n} for n in range(3000 + 1)]
    result = Pool(4).map(calculate_time, params)
    result = [x for x in result if x is not None]
    x = array([float(x['size']) for x in result])
    y = array([x['time'] for x in result])
    return x, y


# Данные измерений
def get_functions():
    return [
        {
            'label': 'блоки: 8x8, потоки: 16x16',
            'color': 'red',
            'data': run_test_for_program('lab2_8x8_16x16.exe')
        },
        {
            'label': 'блоки: 16x16, потоки: 16x16',
            'color': 'green',
            'data': run_test_for_program('lab2_16x16_16x16.exe')
        },
        {
            'label': 'блоки: 32x32, потоки: 16x16',
            'color': 'blue',
            'data': run_test_for_program('lab2_32x32_16x16.exe')
        },
        {
            'label': 'блоки: 8x8, потоки: 32x32',
            'color': 'purple',
            'data': run_test_for_program('lab2_8x8_32x32.exe')
        },
        {
            'label': 'блоки: 16x16, потоки: 32x32',
            'color': 'orange',
            'data': run_test_for_program('lab2_16x16_32x32.exe')
        },
        {
            'label': 'блоки: 32x32, потоки: 32x32',
            'color': 'pink',
            'data': run_test_for_program('lab2_32x32_32x32.exe')
        }
    ]


# Функция апроксимации
def fit_quadratic_function(x, a, b, c):
    return a * x ** 2 + b * x + c


def main():
    plt.figure(figsize=(22, 11), dpi=80)

    x_min, x_max = None, None
    y_min, y_max = None, None

    for f in get_functions():
        label = f['label']
        color = f['color']
        x, y = f['data']

        if not all([x_min, x_max, y_min, y_max]):
            x_min, x_max = x.min(), x.max()
            y_min, y_max = y.min(), y.max()
        else:
            x_min, x_max = min(x_min, x.min()), max(x_max, x.max())
            y_min, y_max = min(y_min, y.min()), max(y_max, y.max())

        x_new = linspace(x.min(), x.max(), 100)
        f = interp1d(x, y, kind='cubic')
        plt.plot(x, y, marker='o', linestyle='', color=color)
        plt.plot(x_new, f(x_new), linestyle='-', color=color, label=label)

    # Внешний вид
    plt.xlim([x_min, x_max])
    plt.ylim([y_min, y_max])
    plt.xlabel('Размер матрицы')
    plt.ylabel('Время выполнения, мс')
    plt.legend(loc='best')
    plt.grid(True)

    # Сохранить в PNG и показать
    file_name, file_extension = splitext(__file__)
    plt.savefig('{:s}.png'.format(basename(file_name)))
    plt.show()


if __name__ == "__main__":
    main()

Графики производительности для разных сеток:

Сравнение производительности для разных сеток

Полный код программы (CUDA C++):

#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <cmath>
#include <algorithm>

#define uint unsigned int

#define DEBUG true
#define DEBUG_MATRIX false
#define DEBUG_TIMER true

using namespace std;

void print(int *matrix, int w, int h) {
  for (int i = 0; i < h; i++) {
    for (int j = 0; j < w; j++) {
      int c = matrix[i * w + j];
      cout << setw(4) << (int)(c & 0xff) << ",";
      cout << setw(4) << (int)((c >> 8) & 0xff) << ",";
      cout << setw(4) << (int)((c >> 16) & 0xff) << ",";
      cout << setw(4) << (int)((c >> 24) & 0xff) << "\t";
    }
    cout << endl;
  }
}

__device__ int get_element(int *input, int w, int h, int i, int j) {
  i = max(0, min(i, h - 1));
  j = max(0, min(j, w - 1));
  return input[i * w + j];
}

__device__ int pack(int4 rgba) {
  return (rgba.w << 24) | (rgba.z << 16) | (rgba.y << 8) | rgba.x;
}

__device__ int4 unpack(int c) {
  int4 rgba;
  rgba.x = c & 0xff;
  rgba.y = (c >> 8) & 0xff;
  rgba.z = (c >> 16) & 0xff;
  rgba.w = (c >> 24) & 0xff;
  return rgba;
}

__device__ double intensity(int4 rgba) {
  return 0.299 * rgba.x + 0.587 * rgba.y + 0.114 * rgba.z;
}

__global__ void roberts_cross_kernel(int *input, int *output, int w, int h) {
  int idx = blockIdx.x * blockDim.x + threadIdx.x;
  int idy = blockIdx.y * blockDim.y + threadIdx.y;
  int offsetx = gridDim.x * blockDim.x;
  int offsety = gridDim.y * blockDim.y;

  for (int i = idx; i < h; i += offsetx) {
    for (int j = idy; j < w; j += offsety) {
      double m = intensity(unpack(get_element(input, w, h, i, j)));
      double br = intensity(unpack(get_element(input, w, h, i + 1, j + 1)));
      double r = intensity(unpack(get_element(input, w, h, i, j + 1)));
      double b = intensity(unpack(get_element(input, w, h, i + 1, j)));

      double tmp1 = m - br;
      double tmp2 = r - b;

      // Нормировка
      int result = min(255, (int)sqrt(tmp1 * tmp1 + tmp2 * tmp2));

      // Записываем интенсивность в RGB
      output[i * w + j] = pack(make_int4(result, result, result, 0));
    }
  }
}

void roberts_cross_gpu(int *input, int *output, int w, int h) {
  dim3 BLOCKS(16, 16);
  dim3 THREADS(32, 32);

  int *dev_input;
  int *dev_output;
  cudaMalloc(&dev_input, sizeof(int) * w * h);
  cudaMalloc(&dev_output, sizeof(int) * w * h);
  cudaMemcpy(dev_input, input, sizeof(int) * w * h, cudaMemcpyHostToDevice);
  cudaMemcpy(dev_output, output, sizeof(int) * w * h, cudaMemcpyHostToDevice);

  cudaEvent_t start, stop;

  if (DEBUG && DEBUG_TIMER) {
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    cudaEventRecord(start, 0);
  }

  roberts_cross_kernel<<<BLOCKS, THREADS>>>(dev_input, dev_output, w, h);

  if (DEBUG && DEBUG_TIMER) {
    cudaGetLastError();
    cudaEventRecord(stop, 0);
    cudaEventSynchronize(stop);
    float t;
    cudaEventElapsedTime(&t, start, stop);
    cout << t << endl;
    cudaEventDestroy(start);
    cudaEventDestroy(stop);
  }

  cudaMemcpy(output, dev_output, sizeof(int) * w * h, cudaMemcpyDeviceToHost);
  cudaFree(dev_input);
  cudaFree(dev_output);
}

int main() {
  string path_in;
  cin >> path_in;

  string path_out;
  cin >> path_out;

  int w, h;
  ifstream input_stream;
  input_stream.open(path_in.c_str(), ios::binary);

  // Размеры изображения
  input_stream.read((char *)&w, sizeof(int));
  input_stream.read((char *)&h, sizeof(int));

  // Входное и выходное изображения
  int *input = new int[w * h];
  int *output = new int[w * h];

  input_stream.read((char *)input, sizeof(int) * w * h);
  input_stream.close();

  if (DEBUG && DEBUG_MATRIX) {
    cout << "Before:" << endl;
    print(input, w, h);
    cout << endl;
  }

  roberts_cross_gpu(input, output, w, h);

  if (DEBUG && DEBUG_MATRIX) {
    cout << "After:" << endl;
    print(output, w, h);
    cout << endl;
  }

  // Запись в файл
  ofstream output_stream;
  output_stream.open(path_out.c_str(), ios::binary);
  output_stream.write((char *)&w, sizeof(int));
  output_stream.write((char *)&h, sizeof(int));
  output_stream.write((char *)output, sizeof(int) * w * h);
  output_stream.close();

  delete[] input;
  delete[] output;

  return 0;
}