Skip to content

Commit

Permalink
Кошкин Матвей. Задача 3. Вариант 29. Выделение ребер на изображении с…
Browse files Browse the repository at this point in the history
… использованием оператора Собеля. (#852)

SEQ (последовательная реализация): Алгоритм выполняется на одном
процессе, используя свертки с ядрами Собеля для вычисления градиентов
изображения. Реализованы этапы: подготовка данных, вычисления, проверка
и запись результата.

MPI (параллельная реализация): Изображение делится на блоки и
обрабатывается несколькими процессами с использованием Boost.MPI.
Главный процесс распределяет данные, собирает результаты, а вычисления
выполняются локально на каждом процессе с учетом перекрытий.
  • Loading branch information
KoshkinMatvey authored Dec 29, 2024
1 parent eb57a26 commit cabfead
Show file tree
Hide file tree
Showing 8 changed files with 623 additions and 0 deletions.
82 changes: 82 additions & 0 deletions tasks/mpi/koshkin_m_sobel/func_tests/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <gtest/gtest.h>

#include <cstdint>
#include <cstdlib>
#include <memory>
#include <random>
#include <vector>

#include "../include/ops_mpi.hpp"
#include "boost/mpi/communicator.hpp"
#include "core/task/include/task.hpp"

static std::vector<uint8_t> make_img(size_t width, size_t height) {
std::random_device dev;
std::mt19937 gen(dev());
std::uniform_int_distribution<> distrib(0, 255);
std::vector<uint8_t> vec(width * height);
for (size_t i = 0; i < width * height; i++) {
vec[i] = distrib(gen);
}
return vec;
}

static std::shared_ptr<ppc::core::TaskData> test_sobel_mk_taskdata(std::vector<uint8_t> &in, std::vector<uint8_t> &out,
uint32_t width, uint32_t height) {
auto taskData = std::make_shared<ppc::core::TaskData>();

if (boost::mpi::communicator{}.rank() == 0) {
taskData->inputs = {reinterpret_cast<uint8_t *>(in.data())};
taskData->inputs_count = {width, height};

taskData->outputs = {reinterpret_cast<uint8_t *>(out.data())};
taskData->outputs_count = {width, height};
}

return taskData;
}

static void test_sobel_io(std::vector<uint8_t> &&in, uint32_t width, uint32_t height, std::vector<uint8_t> &out) {
ASSERT_EQ(in.size(), width * height);

auto taskData = test_sobel_mk_taskdata(in, out, width, height);

koshkin_m_sobel_mpi::TestTaskParallel testTaskParallel(taskData);
ASSERT_EQ(testTaskParallel.validation(), true);
testTaskParallel.pre_processing();
testTaskParallel.run();
testTaskParallel.post_processing();
}

static void test_sobel(uint32_t width, uint32_t height) {
std::vector<uint8_t> in = make_img(width, height);
std::vector<uint8_t> out;
if (boost::mpi::communicator{}.rank() == 0) {
out.resize(in.size());
}
test_sobel_io(std::move(in), width, height, out);

if (boost::mpi::communicator{}.rank() == 0) {
std::vector<uint8_t> ref(in.size());

koshkin_m_sobel_mpi::TestTaskSequential testTaskSequential(test_sobel_mk_taskdata(in, ref, width, height));
ASSERT_EQ(testTaskSequential.validation(), true);
testTaskSequential.pre_processing();
testTaskSequential.run();
testTaskSequential.post_processing();

ASSERT_EQ(out, ref);
}
}

TEST(koshkin_m_sobel_mpi, Image_Random_1x1) { test_sobel(1, 1); }
TEST(koshkin_m_sobel_mpi, Image_Random_2x2) { test_sobel(2, 2); }
TEST(koshkin_m_sobel_mpi, Image_Random_2x3) { test_sobel(2, 3); }
TEST(koshkin_m_sobel_mpi, Image_Random_3x3) { test_sobel(3, 3); }
TEST(koshkin_m_sobel_mpi, Image_Random_3x7) { test_sobel(3, 7); }
TEST(koshkin_m_sobel_mpi, Image_Random_7x3) { test_sobel(7, 3); }
TEST(koshkin_m_sobel_mpi, Image_Random_7x13) { test_sobel(7, 13); }
TEST(koshkin_m_sobel_mpi, Image_Random_13x7) { test_sobel(13, 7); }
TEST(koshkin_m_sobel_mpi, Image_Random_17x13) { test_sobel(17, 13); }
TEST(koshkin_m_sobel_mpi, Image_Random_1x13) { test_sobel(1, 13); }
TEST(koshkin_m_sobel_mpi, Image_Random_17x17) { test_sobel(17, 17); }
55 changes: 55 additions & 0 deletions tasks/mpi/koshkin_m_sobel/include/ops_mpi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <array>
#include <boost/mpi/collectives.hpp>
#include <boost/mpi/communicator.hpp>
#include <vector>

#include "core/task/include/task.hpp"

namespace koshkin_m_sobel_mpi {

// clang-format off
static inline const std::array<std::array<int8_t, 3>, 3> SOBEL_KERNEL_X = {{
{{-1, 0, 1}},
{{-2, 0, 2}},
{{-1, 0, 1}}
}};
static inline const std::array<std::array<int8_t, 3>, 3> SOBEL_KERNEL_Y = {{
{{-1, -2, -1}},
{{ 0, 0, 0}},
{{ 1, 2, 1}}
}};
// clang-format on

class TestTaskSequential : public ppc::core::Task {
public:
explicit TestTaskSequential(std::shared_ptr<ppc::core::TaskData> taskData_) : Task(std::move(taskData_)) {}
bool pre_processing() override;
bool validation() override;
bool run() override;
bool post_processing() override;

private:
std::pair<size_t, size_t> imgsize;
std::vector<uint8_t> image;
std::vector<uint8_t> resimg;
};

class TestTaskParallel : public ppc::core::Task {
public:
explicit TestTaskParallel(std::shared_ptr<ppc::core::TaskData> taskData_) : Task(std::move(taskData_)) {}
bool pre_processing() override;
bool validation() override;
bool run() override;
bool post_processing() override;

private:
std::pair<size_t, size_t> imgsize;
std::vector<uint8_t> image;
std::vector<uint8_t> resimg;

boost::mpi::communicator world;
};

} // namespace koshkin_m_sobel_mpi
94 changes: 94 additions & 0 deletions tasks/mpi/koshkin_m_sobel/perf_tests/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#include <gtest/gtest.h>

#include <boost/mpi/timer.hpp>
#include <random>
#include <vector>

#include "../include/ops_mpi.hpp"
#include "boost/mpi/communicator.hpp"
#include "core/perf/include/perf.hpp"

static std::vector<uint8_t> make_img(size_t width, size_t height) {
std::random_device dev;
std::mt19937 gen(dev());
std::uniform_int_distribution<> distrib(0, 255);
std::vector<uint8_t> vec(width * height);
for (size_t i = 0; i < width * height; i++) {
vec[i] = distrib(gen);
}
return vec;
}

TEST(koshkin_m_sobel_mpi_perf_test, test_pipeline_run) {
boost::mpi::communicator world;

const auto width = 1200;
const auto height = 1200;
std::vector<uint8_t> in = make_img(width, height);
std::vector<uint8_t> out(in.size());

// Create TaskData
std::shared_ptr<ppc::core::TaskData> taskDataPar = std::make_shared<ppc::core::TaskData>();
if (world.rank() == 0) {
taskDataPar->inputs = {reinterpret_cast<uint8_t *>(in.data())};
taskDataPar->inputs_count = {width, height};
taskDataPar->outputs = {reinterpret_cast<uint8_t *>(out.data())};
taskDataPar->outputs_count = {width, height};
}

// Create Task
auto testTaskParallel = std::make_shared<koshkin_m_sobel_mpi::TestTaskParallel>(taskDataPar);

// Create Perf attributes
auto perfAttr = std::make_shared<ppc::core::PerfAttr>();
perfAttr->num_running = 10;
const boost::mpi::timer current_timer;
perfAttr->current_timer = [&] { return current_timer.elapsed(); };

// Create and init perf results
auto perfResults = std::make_shared<ppc::core::PerfResults>();

// Create Perf analyzer
auto perfAnalyzer = std::make_shared<ppc::core::Perf>(testTaskParallel);
perfAnalyzer->pipeline_run(perfAttr, perfResults);
if (world.rank() == 0) {
ppc::core::Perf::print_perf_statistic(perfResults);
}
}

TEST(koshkin_m_sobel_mpi_perf_test, test_task_run) {
boost::mpi::communicator world;

const auto width = 1200;
const auto height = 1200;
std::vector<uint8_t> in = make_img(width, height);
std::vector<uint8_t> out(in.size());

// Create TaskData
std::shared_ptr<ppc::core::TaskData> taskDataPar = std::make_shared<ppc::core::TaskData>();
if (world.rank() == 0) {
taskDataPar->inputs = {reinterpret_cast<uint8_t *>(in.data())};
taskDataPar->inputs_count = {width, height};
taskDataPar->outputs = {reinterpret_cast<uint8_t *>(out.data())};
taskDataPar->outputs_count = {width, height};
}

// Create Task
auto testTaskParallel = std::make_shared<koshkin_m_sobel_mpi::TestTaskParallel>(taskDataPar);

// Create Perf attributes
auto perfAttr = std::make_shared<ppc::core::PerfAttr>();
perfAttr->num_running = 10;
const boost::mpi::timer current_timer;
perfAttr->current_timer = [&] { return current_timer.elapsed(); };

// Create and init perf results
auto perfResults = std::make_shared<ppc::core::PerfResults>();

// Create Perf analyzer
auto perfAnalyzer = std::make_shared<ppc::core::Perf>(testTaskParallel);
perfAnalyzer->task_run(perfAttr, perfResults);
if (world.rank() == 0) {
ppc::core::Perf::print_perf_statistic(perfResults);
}
}
149 changes: 149 additions & 0 deletions tasks/mpi/koshkin_m_sobel/src/ops_mpi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#include "../include/ops_mpi.hpp"

#include <algorithm>
#include <boost/serialization/utility.hpp>
#include <climits>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <utility>

int16_t conv_kernel3(const std::array<std::array<int8_t, 3>, 3>& kernel, const std::vector<uint8_t>& img, size_t i,
size_t j, size_t height) {
// clang-format off
return kernel[0][0] * img[(i - 1) * height + (j - 1)]
+ kernel[0][1] * img[(i - 1) * height + j]
+ kernel[0][2] * img[(i - 1) * height + (j + 1)]
+ kernel[1][0] * img[i * height + (j - 1)]
+ kernel[1][1] * img[i * height + j]
+ kernel[1][2] * img[i * height + (j + 1)]
+ kernel[2][0] * img[(i + 1) * height + (j - 1)]
+ kernel[2][1] * img[(i + 1) * height + j]
+ kernel[2][2] * img[(i + 1) * height + (j + 1)];
// clang-format on
}

bool koshkin_m_sobel_mpi::TestTaskSequential::pre_processing() {
internal_order_test();

imgsize = {taskData->inputs_count[0], taskData->inputs_count[1]};
auto& [width, height] = imgsize;
const int padding = 2;
image.resize((width + padding) * (height + padding));
resimg.resize(width * height, 0);

const auto* in = reinterpret_cast<uint8_t*>(taskData->inputs[0]);
for (size_t row = 0; row < height; row++) {
std::copy(in + (row * width), in + ((row + 1) * width), image.begin() + ((row + 1) * (width + padding) + 1));
}

return true;
}

bool koshkin_m_sobel_mpi::TestTaskSequential::validation() {
internal_order_test();
return taskData->inputs_count[0] > 0 && taskData->inputs_count[1] > 0 &&
taskData->outputs_count[0] == taskData->inputs_count[0] &&
taskData->outputs_count[1] == taskData->inputs_count[1];
}

bool koshkin_m_sobel_mpi::TestTaskSequential::run() {
internal_order_test();

const auto [width, height] = imgsize;

for (size_t i = 1; i < (width + 2) - 1; i++) {
for (size_t j = 1; j < (height + 2) - 1; j++) {
const auto accX = conv_kernel3(SOBEL_KERNEL_X, image, i, j, (height + 2));
const auto accY = conv_kernel3(SOBEL_KERNEL_Y, image, i, j, (height + 2));
resimg[(i - 1) * height + (j - 1)] = std::clamp(std::sqrt(std::pow(accX, 2) + std::pow(accY, 2)), 0., 255.);
}
}

return true;
}

bool koshkin_m_sobel_mpi::TestTaskSequential::post_processing() {
internal_order_test();
std::copy(resimg.begin(), resimg.end(), reinterpret_cast<uint8_t*>(taskData->outputs[0]));
return true;
}

bool koshkin_m_sobel_mpi::TestTaskParallel::pre_processing() {
internal_order_test();

if (world.rank() == 0) {
imgsize = {taskData->inputs_count[0], taskData->inputs_count[1]};
auto& [width, height] = imgsize;

image.resize((width + 2) * (height + 2));
resimg.resize(width * height, 0);

const auto* in = reinterpret_cast<uint8_t*>(taskData->inputs[0]);
for (size_t row = 0; row < height; row++) {
std::copy(in + (row * width), in + ((row + 1) * width), image.begin() + ((row + 1) * (width + 2) + 1));
}
}

return true;
}

bool koshkin_m_sobel_mpi::TestTaskParallel::validation() {
internal_order_test();
return world.rank() != 0 || (taskData->inputs_count[0] > 0 && taskData->inputs_count[1] > 0 &&
taskData->outputs_count[0] == taskData->inputs_count[0] &&
taskData->outputs_count[1] == taskData->inputs_count[1]);
}

bool koshkin_m_sobel_mpi::TestTaskParallel::run() {
internal_order_test();

const int pad = 2;

boost::mpi::broadcast(world, imgsize, 0);
const auto [width, height] = imgsize;
const auto [pwidth, pheight] = std::make_pair(width + pad, height + pad);

const int perproc = width / world.size();
const int leftover = width % world.size();

std::vector<int> sendcounts(world.size(), 0);
std::vector<int> senddispls(world.size(), 0);
std::vector<int> recvcounts(world.size(), 0);
std::vector<int> recvdispls(world.size(), 0);

for (int i = 0; i < world.size(); i++) {
const int extra = i < leftover ? 1 : 0;
sendcounts[i] = (perproc + extra + pad) * pheight;
recvcounts[i] = (perproc + extra) * height;
}
for (int i = 1; i < world.size(); i++) {
senddispls[i] = senddispls[i - 1] + sendcounts[i - 1] - pheight * pad;
recvdispls[i] = recvdispls[i - 1] + recvcounts[i - 1];
}

std::vector<uint8_t> locimg(sendcounts[world.rank()]);
boost::mpi::scatterv(world, image, sendcounts, senddispls, locimg.data(), locimg.size(), 0);

const int actw = sendcounts[world.rank()] / pheight;
std::vector<uint8_t> locres(recvcounts[world.rank()]);
for (int i = 1; i < actw - 1; i++) {
for (size_t j = 1; j < (height + 2) - 1; j++) {
const auto accX = conv_kernel3(SOBEL_KERNEL_X, locimg, i, j, (height + 2));
const auto accY = conv_kernel3(SOBEL_KERNEL_Y, locimg, i, j, (height + 2));
locres[(i - 1) * height + (j - 1)] = std::clamp(std::sqrt(std::pow(accX, 2) + std::pow(accY, 2)), 0., 255.);
}
}

boost::mpi::gatherv(world, locres, resimg.data(), recvcounts, recvdispls, 0);

return true;
}

bool koshkin_m_sobel_mpi::TestTaskParallel::post_processing() {
internal_order_test();
if (world.rank() == 0) {
std::copy(resimg.begin(), resimg.end(), reinterpret_cast<uint8_t*>(taskData->outputs[0]));
}
return true;
}
Loading

0 comments on commit cabfead

Please sign in to comment.