Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 43 additions & 53 deletions src/api/c/canny.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <ireduce.hpp>
#include <logic.hpp>
#include <reduce.hpp>
#include <scan.hpp>
#include <sobel.hpp>
#include <tile.hpp>
#include <transpose.hpp>
Expand All @@ -47,6 +48,7 @@ using detail::ireduce;
using detail::logicOp;
using detail::reduce;
using detail::reduce_all;
using detail::scan;
using detail::sobelDerivatives;
using detail::uchar;
using detail::uint;
Expand All @@ -71,80 +73,68 @@ Array<float> gradientMagnitude(const Array<float>& gx, const Array<float>& gy,
}
}

Array<float> otsuThreshold(const Array<float>& supEdges,
const unsigned NUM_BINS, const float maxVal) {
Array<uint> hist = histogram<float>(supEdges, NUM_BINS, 0, maxVal, false);
Array<float> otsuThreshold(const Array<float>& in, const unsigned NUM_BINS,
const float maxVal) {
Array<uint> hist = histogram<float>(in, NUM_BINS, 0, maxVal, false);

const dim4& hDims = hist.dims();

// reduce along histogram dimension i.e. 0th dimension
auto totals = reduce<af_add_t, uint, float>(hist, 0);

// tile histogram total along 0th dimension
auto ttotals = tile(totals, dim4(hDims[0]));

// pixel frequency probabilities
auto probability =
arithOp<float, af_div_t>(cast<float, uint>(hist), ttotals, hDims);
const dim4& inDims = in.dims();
const dim4& hDims = hist.dims();

const dim4 oDims(1, hDims[1], hDims[2], hDims[3]);
vector<af_seq> seqBegin(4, af_span);
vector<af_seq> seqRest(4, af_span);
vector<af_seq> sliceIndex(4, af_span);

seqBegin[0] = af_make_seq(0, static_cast<double>(hDims[0] - 1), 1);
seqRest[0] = af_make_seq(0, static_cast<double>(hDims[0] - 1), 1);

const dim4& iDims = supEdges.dims();

Array<float> sigmas = createEmptyArray<float>(hDims);
Array<float> TWOS = createValueArray<float>(oDims, 2.0f);
Array<float> UnitP = createValueArray<float>(oDims, 1.0f);
Array<float> histf = cast<float, uint>(hist);
Array<float> totals = createValueArray<float>(hDims, inDims[0] * inDims[1]);
Array<float> weights =
iota<float>(dim4(NUM_BINS), oDims); // a.k.a histogram shape

// pixel frequency probabilities
auto freqs = arithOp<float, af_div_t>(histf, totals, hDims);
auto cumFreqs = scan<af_add_t, float, float>(freqs, 0);
auto oneMCumFreqs = arithOp<float, af_sub_t>(UnitP, cumFreqs, hDims);
auto qLqH = arithOp<float, af_mul_t>(cumFreqs, oneMCumFreqs, hDims);
auto product = arithOp<float, af_mul_t>(weights, freqs, hDims);
auto cumProduct = scan<af_add_t, float, float>(product, 0);
auto weightedSum = reduce<af_add_t, float, float>(product, 0);

dim4 sigmaDims(NUM_BINS - 1, hDims[1], hDims[2], hDims[3]);
Array<float> sigmas = createEmptyArray<float>(sigmaDims);
for (unsigned b = 0; b < (NUM_BINS - 1); ++b) {
const dim4 fDims(b + 1, hDims[1], hDims[2], hDims[3]);
const dim4 eDims(NUM_BINS - 1 - b, hDims[1], hDims[2], hDims[3]);

sliceIndex[0] = {double(b), double(b), 1};
seqBegin[0].end = static_cast<double>(b);
seqRest[0].begin = static_cast<double>(b + 1);

auto frontPartition = createSubArray(probability, seqBegin, false);
auto endPartition = createSubArray(probability, seqRest, false);

auto qL = reduce<af_add_t, float, float>(frontPartition, 0);
auto qH = reduce<af_add_t, float, float>(endPartition, 0);

const dim4 fdims(b + 1, hDims[1], hDims[2], hDims[3]);
const dim4 edims(NUM_BINS - 1 - b, hDims[1], hDims[2], hDims[3]);

const dim4 tdims(1, hDims[1], hDims[2], hDims[3]);
auto frontWeights = iota<float>(dim4(b + 1), tdims);
auto endWeights = iota<float>(dim4(NUM_BINS - 1 - b), tdims);
auto offsetValues = createValueArray<float>(edims, b + 1);

endWeights = arithOp<float, af_add_t>(endWeights, offsetValues, edims);
auto __muL =
arithOp<float, af_mul_t>(frontPartition, frontWeights, fdims);
auto __muH = arithOp<float, af_mul_t>(endPartition, endWeights, edims);
auto _muL = reduce<af_add_t, float, float>(__muL, 0);
auto _muH = reduce<af_add_t, float, float>(__muH, 0);
auto muL = arithOp<float, af_div_t>(_muL, qL, tdims);
auto muH = arithOp<float, af_div_t>(_muH, qH, tdims);
auto TWOS = createValueArray<float>(tdims, 2.0f);
auto diff = arithOp<float, af_sub_t>(muL, muH, tdims);
auto sqrd = arithOp<float, af_pow_t>(diff, TWOS, tdims);
auto op2 = arithOp<float, af_mul_t>(qL, qH, tdims);
auto sigma = arithOp<float, af_mul_t>(sqrd, op2, tdims);

vector<af_seq> sliceIndex(4, af_span);
sliceIndex[0] = {double(b), double(b), 1};
auto qL = createSubArray(cumFreqs, sliceIndex, false);
auto qH = arithOp<float, af_sub_t>(UnitP, qL, oDims);
auto _muL = createSubArray(cumProduct, sliceIndex, false);
auto _muH = arithOp<float, af_sub_t>(weightedSum, _muL, oDims);
auto muL = arithOp<float, af_div_t>(_muL, qL, oDims);
auto muH = arithOp<float, af_div_t>(_muH, qH, oDims);
auto diff = arithOp<float, af_sub_t>(muL, muH, oDims);
auto sqrd = arithOp<float, af_pow_t>(diff, TWOS, oDims);
auto op2 = createSubArray(qLqH, sliceIndex, false);
auto sigma = arithOp<float, af_mul_t>(sqrd, op2, oDims);

auto binRes = createSubArray<float>(sigmas, sliceIndex, false);

copyArray(binRes, sigma);
}

dim4 odims = sigmas.dims();
odims[0] = 1;
Array<float> thresh = createEmptyArray<float>(odims);
Array<uint> locs = createEmptyArray<uint>(odims);
Array<float> thresh = createEmptyArray<float>(oDims);
Array<uint> locs = createEmptyArray<uint>(oDims);

ireduce<af_max_t, float>(thresh, locs, sigmas, 0);

return cast<float, uint>(tile(locs, dim4(iDims[0], iDims[1], 1, 1)));
return cast<float, uint>(tile(locs, dim4(inDims[0], inDims[1])));
}

Array<float> normalize(const Array<float>& supEdges, const float minVal,
Expand Down
42 changes: 24 additions & 18 deletions src/backend/cpu/kernel/canny.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ void nonMaxSuppression(Param<T> output, CParam<T> magnitude, CParam<T> dxParam,
}

template<typename T>
void traceEdge(T* out, const T* strong, const T* weak, int t, int width) {
void traceEdge(T* out, const T* strong, const T* weak, int t, int stride1) {
if (!out || !strong || !weak) return;

const T EDGE = 1;
Expand All @@ -129,12 +129,12 @@ void traceEdge(T* out, const T* strong, const T* weak, int t, int width) {
// get indices of 8 neighbours
std::array<dim_t, 8> potentials;

potentials[0] = t - width - 1; // north-west
potentials[0] = t - stride1 - 1; // north-west
potentials[1] = potentials[0] + 1; // north
potentials[2] = potentials[1] + 1; // north-east
potentials[3] = t - 1; // west
potentials[4] = t + 1; // east
potentials[5] = t + width - 1; // south-west
potentials[5] = t + stride1 - 1; // south-west
potentials[6] = potentials[5] + 1; // south
potentials[7] = potentials[6] + 1; // south-east

Expand All @@ -151,27 +151,33 @@ void traceEdge(T* out, const T* strong, const T* weak, int t, int width) {

template<typename T>
void edgeTrackingHysteresis(Param<T> out, CParam<T> strong, CParam<T> weak) {
const af::dim4 dims = strong.dims();
const af::dim4 dims = strong.dims();
const dim_t batchCount = dims[2] * dims[3];
const dim_t jMax = dims[1] - 1;
const dim_t iMax = dims[0] - 1;

dim_t t = dims[0] +
1; // skip the first coloumn and first element of second coloumn
dim_t jMax = dims[1] - 1; // max Y value to traverse, ignore right coloumn
dim_t iMax = dims[0] - 1; // max X value to traverse, ignore bottom border

T* optr = out.get();
const T* sptr = strong.get();
const T* wptr = weak.get();
T* optr = out.get();

for (dim_t j = 1; j <= jMax; ++j) {
for (dim_t i = 1; i <= iMax; ++i, ++t) {
// if current pixel(sptr) is part of a edge
// and output doesn't have it marked already,
// mark it and trace the pixels from here.
if (sptr[t] > 0 && optr[t] != 1) {
optr[t] = 1;
traceEdge(optr, sptr, wptr, t, dims[0]);
for (dim_t batchId = 0; batchId < batchCount; ++batchId) {
// Skip processing borders
dim_t t = dims[0] + 1;

for (dim_t j = 1; j <= jMax; ++j) {
for (dim_t i = 1; i <= iMax; ++i, ++t) {
// if current pixel(sptr) is part of a edge
// and output doesn't have it marked already,
// mark it and trace the pixels from here.
if (sptr[t] > 0 && optr[t] != 1) {
optr[t] = 1;
traceEdge(optr, sptr, wptr, t, dims[0]);
}
}
}
optr += out.strides(2);
sptr += strong.strides(2);
wptr += weak.strides(2);
}
}
} // namespace kernel
Expand Down
50 changes: 46 additions & 4 deletions test/canny.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ void cannyImageOtsuTest(string pTestFile, bool isColor) {
af_array mulArray = 0;
af_array outArray = 0;
af_array goldArray = 0;
dim_t nElems = 0;

inFiles[testId].insert(0, string(TEST_DIR "/CannyEdgeDetector/"));
outFiles[testId].insert(0, string(TEST_DIR "/CannyEdgeDetector/"));
Expand All @@ -129,12 +128,9 @@ void cannyImageOtsuTest(string pTestFile, bool isColor) {
ASSERT_SUCCESS(
af_load_image_native(&goldArray, outFiles[testId].c_str()));

ASSERT_SUCCESS(af_get_elements(&nElems, goldArray));

ASSERT_SUCCESS(af_canny(&_outArray, inArray,
AF_CANNY_THRESHOLD_AUTO_OTSU, 0.08, 0.32, 3,
false));

unsigned ndims = 0;
dim_t dims[4];

Expand Down Expand Up @@ -220,3 +216,49 @@ TEST(CannyEdgeDetector, Sobel5x5_Invalid) {

ASSERT_SUCCESS(af_release_array(inArray));
}

template<typename T>
void cannyImageOtsuBatchTest(string pTestFile, const dim_t targetBatchCount) {
SUPPORTED_TYPE_CHECK(T);
if (noImageIOTests()) return;

using af::array;
using af::canny;
using af::loadImage;
using af::loadImageNative;
using af::tile;

vector<dim4> inDims;
vector<string> inFiles;
vector<dim_t> outSizes;
vector<string> outFiles;

readImageTests(pTestFile, inDims, inFiles, outSizes, outFiles);

size_t testCount = inDims.size();

for (size_t testId = 0; testId < testCount; ++testId) {
inFiles[testId].insert(0, string(TEST_DIR "/CannyEdgeDetector/"));
outFiles[testId].insert(0, string(TEST_DIR "/CannyEdgeDetector/"));

af_dtype type = (af_dtype)dtype_traits<T>::af_type;
array readGold = loadImageNative(outFiles[testId].c_str());
array goldIm = tile(readGold, 1, 1, targetBatchCount);
array readImg = loadImage(inFiles[testId].c_str(), false).as(type);
array inputIm = tile(readImg, 1, 1, targetBatchCount);

array outIm =
canny(inputIm, AF_CANNY_THRESHOLD_AUTO_OTSU, 0.08, 0.32, 3, false);
outIm *= 255.0;

ASSERT_IMAGES_NEAR(outIm.as(u8), goldIm, 1.0e-3);
}
}

TEST(CannyEdgeDetector, BatchofImagesUsingCPPAPI) {
// DO NOT INCREASE BATCH COUNT BEYOND 4
// This is a limitation on the test assert macro that is saving
// images to disk which can't handle a batch of images.
cannyImageOtsuBatchTest<float>(
string(TEST_DIR "/CannyEdgeDetector/gray.test"), 3);
}