Skip to content

Commit

Permalink
add CCLGetStats
Browse files Browse the repository at this point in the history
  • Loading branch information
dtlnor committed May 2, 2024
1 parent 2602f2e commit 0037ea6
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 1 deletion.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Vapoursynth Connected Components Label Filtering from OpenCV.

Using OpenCV's [`connectedComponentsWithStats`](https://docs.opencv.org/5.x/d3/dc0/group__imgproc__shape.html#ga107a78bf7cd25dec05fb4dfc5c9e765f) to compute the connected components labeled image of a boolean image and also get statistics for each label. Then filter the labeled content based on the statistical results.

Require VS API >= 4.0

## Usage
Exclude the labels **above** `ccl_thr`
```
Expand All @@ -14,6 +16,10 @@ Exclude the labels **under** `ccl_thr`
```
core.cv_ccl.ExcludeCCLUnder(clip mask, int cc_thr[, int connectivity, int ccl_type, int cc_stat_type])
```
Read all the stats from `cv::connectedComponentsWithStats` to FrameProps
```
core.cv_ccl.GetCCLStats(clip mask[, int connectivity, int ccl_type])
```

For example, if you want to remove all the connected components smaller than 2500 pixels (in area) inside your mask clip, you can simply write:
```python
Expand All @@ -34,6 +40,24 @@ core.cv_ccl.ExcludeCCLUnder(mask, 2500)

</details>

If you want to get all the ccl stats data, using `core.cv_ccl.GetCCLStats`, you will get FrameProps like:

```
_CCLStatNumLabels
_CCLStatAreas
_CCLStatLefts
_CCLStatTops
_CCLStatWidths
_CCLStatHeights
_CCLStatCentroids_x
_CCLStatCentroids_y
```

which should contains array (except `_CCLStatNumLabels`) if the input clip isn't a blank clip. Note that the first label is always the background label, so you will like to get the frame prop like this
```py
f.props.get('_CCLStatAreas', None)[1:]
```

***Parameters:***

- **mask**
Expand Down
1 change: 1 addition & 0 deletions msvc_project/cv_ccl_filter.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="..\src\CCLAboveFilter.cpp" />
<ClCompile Include="..\src\CCLGetStats.cpp" />
<ClCompile Include="..\src\shared.cpp" />
<ClCompile Include="..\src\CCLUnderFilter.cpp" />
</ItemGroup>
Expand Down
116 changes: 116 additions & 0 deletions src/CCLGetStats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include "shared.h"

static void process_ccl_stats(const VSFrame* src, VSFrame* dst, const MaskData* const VS_RESTRICT d, const VSAPI* vsapi) {
const int w = vsapi->getFrameWidth(src, 0);
const int h = vsapi->getFrameHeight(src, 0);
ptrdiff_t stride = vsapi->getStride(src, 0);
const uint8_t* maskp = vsapi->getReadPtr(src, 0);
uint8_t* dstp = vsapi->getWritePtr(dst, 0);
VSMap* props = vsapi->getFramePropertiesRW(dst);

cv::Mat maskImg(h, w, CV_8UC1, (void*)maskp);
cv::Mat labels, stats, centroids;

auto num_labels = cv::connectedComponentsWithStats(maskImg, labels, stats, centroids, d->connectivity, CV_16U, d->ccl_type);

// becareful with the number of labels, if there is only one label, it is the background label
vsapi->mapSetInt(props, "_CCLStatNumLabels", num_labels, maReplace);

// get the stats for each label, if there is only one label, it is the background label, and its data type will be a int/float.
for (int label = 0; label < num_labels; label++) {

auto area = stats.at<int>(label, cv::CC_STAT_AREA);
auto left = stats.at<int>(label, cv::CC_STAT_LEFT);
auto top = stats.at<int>(label, cv::CC_STAT_TOP);
auto width = stats.at<int>(label, cv::CC_STAT_WIDTH);
auto height = stats.at<int>(label, cv::CC_STAT_HEIGHT);
// get the centroid at label
auto centroidx = centroids.at<cv::Point2d>(label).x;
auto centroidy = centroids.at<cv::Point2d>(label).y;

vsapi->mapSetInt(props, "_CCLStatAreas", area, maAppend);
vsapi->mapSetInt(props, "_CCLStatLefts", left, maAppend);
vsapi->mapSetInt(props, "_CCLStatTops", top, maAppend);
vsapi->mapSetInt(props, "_CCLStatWidths", width, maAppend);
vsapi->mapSetInt(props, "_CCLStatHeights", height, maAppend);
vsapi->mapSetFloat(props, "_CCLStatCentroids_x", centroidx, maAppend);
vsapi->mapSetFloat(props, "_CCLStatCentroids_y", centroidy, maAppend);
}

for (int y = 0; y < h; y++) {
memcpy(dstp, maskImg.ptr(y), w);
dstp += stride;
}
}

static const VSFrame* VS_CC getCCLStatsGetFrame(int n, int activationReason, void* instanceData, void** frameData, VSFrameContext* frameCtx, VSCore* core, const VSAPI* vsapi) {
auto d{ static_cast<MaskData*>(instanceData) };

if (activationReason == arInitial) {
vsapi->requestFrameFilter(n, d->node, frameCtx);
}
else if (activationReason == arAllFramesReady) {
const VSFrame* src = vsapi->getFrameFilter(n, d->node, frameCtx);

const VSVideoFormat* fi = vsapi->getVideoFrameFormat(src);
int height = vsapi->getFrameHeight(src, 0);
int width = vsapi->getFrameWidth(src, 0);
VSFrame* dst = vsapi->newVideoFrame(fi, width, height, src, core);

if (d->vi->format.colorFamily == cfGray) {
process_ccl_stats(src, dst, d, vsapi);
}

vsapi->freeFrame(src);
return dst;
}
return nullptr;
}

static void VS_CC getCCLStatsMapFree(void* instanceData, VSCore* core, const VSAPI* vsapi) {
auto d{ static_cast<MaskData*>(instanceData) };
vsapi->freeNode(d->node);
delete d;
}

void VS_CC getCCLStatsCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi) {
auto d{ std::make_unique<MaskData>() };
int err{ 0 };

d->node = vsapi->mapGetNode(in, "mask", 0, nullptr);
d->vi = vsapi->getVideoInfo(d->node);

auto ccl_type = vsapi->mapGetIntSaturated(in, "ccl_type", 0, &err);
if (err)
d->ccl_type = cv::CCL_DEFAULT;
else {
if (ccl_type < -1 || ccl_type > 5) {
vsapi->mapSetError(out, "GetCCLStats: ccl_type must be between -1 and 6.");
vsapi->freeNode(d->node);
return;
}
d->ccl_type = ccl_type;
}

auto connectivity = vsapi->mapGetIntSaturated(in, "connectivity", 0, &err);
if (err)
d->connectivity = 8;
else {
if (connectivity != 4 && connectivity != 8) {
vsapi->mapSetError(out, "GetCCLStats: connectivity must be 4 or 8.");
vsapi->freeNode(d->node);
return;
}
d->connectivity = connectivity;
}

if (d->vi->format.bytesPerSample != 1 || (d->vi->format.colorFamily != cfGray)) {
vsapi->mapSetError(out, "GetCCLStats: only Gray8 formats supported.");
vsapi->freeNode(d->node);
return;
}

VSFilterDependency deps[]{ {d->node, rpGeneral} };
vsapi->createVideoFilter(out, "GetCCLStats", d->vi, getCCLStatsGetFrame, getCCLStatsMapFree, fmParallel, deps, 1, d.get(), core);
d.release();
}
1 change: 1 addition & 0 deletions src/shared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ VS_EXTERNAL_API(void) VapourSynthPluginInit2(VSPlugin* plugin, const VSPLUGINAPI
vspapi->configPlugin("com.dtlnor.cv_ccl", "cv_ccl", "Mask connected components label filtering", VS_MAKE_VERSION(1, 0), VAPOURSYNTH_API_VERSION, 0, plugin);
vspapi->registerFunction("ExcludeCCLAbove", "mask:vnode;cc_thr:int;connectivity:int:opt;ccl_type:int:opt;cc_stat_type:int:opt;", "clip:vnode;", excludeCCLAboveCreate, nullptr, plugin);
vspapi->registerFunction("ExcludeCCLUnder", "mask:vnode;cc_thr:int;connectivity:int:opt;ccl_type:int:opt;cc_stat_type:int:opt;", "clip:vnode;", excludeCCLUnderCreate, nullptr, plugin);
vspapi->registerFunction("GetCCLStats", "mask:vnode;connectivity:int:opt;ccl_type:int:opt;", "clip:vnode;", getCCLStatsCreate, nullptr, plugin);
}
3 changes: 2 additions & 1 deletion src/shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ struct MaskData final {
};

extern void VS_CC excludeCCLAboveCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi);
extern void VS_CC excludeCCLUnderCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi);
extern void VS_CC excludeCCLUnderCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi);
extern void VS_CC getCCLStatsCreate(const VSMap* in, VSMap* out, void* userData, VSCore* core, const VSAPI* vsapi);

0 comments on commit 0037ea6

Please sign in to comment.