generated from learning-process/parallel_programming_course
-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Кошкин Матвей. Задача 3. Вариант 29. Выделение ребер на изображении с…
… использованием оператора Собеля. (#852) SEQ (последовательная реализация): Алгоритм выполняется на одном процессе, используя свертки с ядрами Собеля для вычисления градиентов изображения. Реализованы этапы: подготовка данных, вычисления, проверка и запись результата. MPI (параллельная реализация): Изображение делится на блоки и обрабатывается несколькими процессами с использованием Boost.MPI. Главный процесс распределяет данные, собирает результаты, а вычисления выполняются локально на каждом процессе с учетом перекрытий.
- Loading branch information
1 parent
eb57a26
commit cabfead
Showing
8 changed files
with
623 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.