检出红色圈线的区域

使用lpvsdk实现流程如下:
/**
* @file Hull.cpp
* @brief 船体轮廓凹陷区域测量案例
*
* 逻辑:
* 1. 读取 hull 图像并转换为 8 位灰度图。
* 2. 通过阈值、差集、连通域、面积过滤和形态学得到主体区域。
* 3. 对主体区域构造凸包掩码,再与主体掩码做差得到凹陷候选。
* 4. 按面积和凸性过滤最终区域,输出面积与中心点到 JSON。
*/
#include "CaseUtils.h"
#include "LPVBlob.h"
#include "LPVImgProc.h"
#include <algorithm>
using namespace LPVBlobLib;
using namespace LPVImgProcLib;
int main()
{
CoInitialize(nullptr);
const std::string outputPath = resultOutputDir() + "/Hull.json";
ILImagePtr image = LImage::Create();
const std::string imagePath = findImageByName("hull");
if (imagePath.empty())
throw std::runtime_error("failed to locate image: hull");
LPVErrorCode err = image->Load(toWide(imagePath).c_str());
if (err != LPVErrorCode::LPVNoError)
throw std::runtime_error("failed to load image: hull");
// 将输入统一为灰度图,保证固定阈值在同一灰度空间执行。
ILImageConvertPtr convert = LImageConvert::Create();
ILImagePtr grayImage = image;
if (image->ImageFormat != LPVImageFormatGrayscale8) {
grayImage = LImage::Create();
convert->BGRToGray(image, grayImage);
}
const int width = grayImage->Width;
const int height = grayImage->Height;
std::vector<unsigned char> whiteData(static_cast<size_t>(width) * static_cast<size_t>(height), 255);
std::vector<unsigned char> blackData(static_cast<size_t>(width) * static_cast<size_t>(height), 0);
// 全图掩码表示完整图像域,后续差集在二值图上完成。
ILImagePtr fullMask = LImage::Create();
fullMask->ImageFormat = LPVImageFormatGrayscale8;
fullMask->SetImageData(width, height, whiteData.data(), width, true);
// 固定低灰度阈值提取暗区域。
ILImageThresholdPtr threshold = LImageThreshold::Create();
threshold->SetThreshold(0, 80);
ILImagePtr darkMask = LImage::Create();
threshold->Binarize(grayImage, darkMask);
ILImageOpPtr imageOp = LImageOp::Create();
// 从完整图像域中扣除暗区域,得到亮色主体候选掩码。
ILImagePtr invertedDarkMask = LImage::Create();
imageOp->Invert(darkMask, invertedDarkMask);
ILImagePtr lightMask = LImage::Create();
imageOp->BitAnd(fullMask, invertedDarkMask, lightMask);
// 对亮色主体候选做连通域分析并保留大面积区域。
ILBlobAnalysisPtr lightAnalysis = LBlobAnalysis::Create();
lightAnalysis->ColorMode = LPVImageFormatGrayscale8;
lightAnalysis->FillHole = false;
lightAnalysis->AddBlobRange(255, 255);
lightAnalysis->MaxCount = 10000;
lightAnalysis->ContourType = LPVBlobContourType::LPVBlobContourExternal;
lightAnalysis->Hierarchy = 0;
ILBlobResultsPtr lightBlobs;
lightAnalysis->Build(lightMask, ILRegionPtr(), &lightBlobs);
ILBlobFilterPtr noHullAreaFilter = LBlobFilter::Create();
noHullAreaFilter->SetFilterFeature(LPVBlobFeatures::LPVBlobArea, 50000.0, 9999999.0);
ILBlobResultsPtr noHullCandidates = lightBlobs ? noHullAreaFilter->FilterResults(lightBlobs) : ILBlobResultsPtr();
// 将筛选出的主体候选光栅化为掩码,供圆形闭运算使用。
ILImagePtr noHullCandidateMask = LImage::Create();
noHullCandidateMask->ImageFormat = LPVImageFormatGrayscale8;
noHullCandidateMask->SetImageData(width, height, blackData.data(), width, true);
if (noHullCandidates) {
for (int i = 0; i < noHullCandidates->Count(); ++i) {
ILBlobPtr blob = noHullCandidates->Item(i);
if (!blob)
continue;
ILMaskRegionPtr region = blob->ToRegion();
if (!region)
continue;
ILImagePtr mask = region->ToMask(0, 0, width, height);
if (!mask || mask->Width <= 0 || mask->Height <= 0)
continue;
ILImagePtr merged = LImage::Create();
imageOp->BitOr(noHullCandidateMask, mask, merged);
noHullCandidateMask = merged;
}
}
// 椭圆结构元闭运算填补主体候选的小缺口。
ILImageMorphPtr closeMorph = LImageMorph::Create();
closeMorph->SetMorphShape(LPVMorphEllipse, 27, 27);
ILImagePtr noHullMask = LImage::Create();
closeMorph->Close(noHullCandidateMask, noHullMask);
// 从完整图像域扣除闭运算后的主体,获得补集候选。
ILImagePtr invertedNoHullMask = LImage::Create();
imageOp->Invert(noHullMask, invertedNoHullMask);
ILImagePtr regionMask = LImage::Create();
imageOp->BitAnd(fullMask, invertedNoHullMask, regionMask);
// 椭圆结构元开运算清理补集候选中的小噪声。
ILImageMorphPtr openMorph = LImageMorph::Create();
openMorph->SetMorphShape(LPVMorphEllipse, 5, 5);
ILImagePtr regionOpeningMask = LImage::Create();
openMorph->Open(regionMask, regionOpeningMask);
// 对清理后的候选做连通域分析并保留大面积主体区域。
ILBlobAnalysisPtr regionAnalysis = LBlobAnalysis::Create();
regionAnalysis->ColorMode = LPVImageFormatGrayscale8;
regionAnalysis->FillHole = false;
regionAnalysis->AddBlobRange(255, 255);
regionAnalysis->MaxCount = 10000;
regionAnalysis->ContourType = LPVBlobContourType::LPVBlobContourExternal;
regionAnalysis->Hierarchy = 0;
ILBlobResultsPtr regionBlobs;
regionAnalysis->Build(regionOpeningMask, ILRegionPtr(), ®ionBlobs);
ILBlobFilterPtr regionAreaFilter = LBlobFilter::Create();
regionAreaFilter->SetFilterFeature(LPVBlobFeatures::LPVBlobArea, 5000.0, 9999999.0);
ILBlobResultsPtr regionHullBlobs = regionBlobs ? regionAreaFilter->FilterResults(regionBlobs) : ILBlobResultsPtr();
// 光栅化筛选后的主体区域,作为凸包差集的被扣除掩码。
ILImagePtr regionHullMask = LImage::Create();
regionHullMask->ImageFormat = LPVImageFormatGrayscale8;
regionHullMask->SetImageData(width, height, blackData.data(), width, true);
if (regionHullBlobs) {
for (int i = 0; i < regionHullBlobs->Count(); ++i) {
ILBlobPtr blob = regionHullBlobs->Item(i);
if (!blob)
continue;
ILMaskRegionPtr region = blob->ToRegion();
if (!region)
continue;
ILImagePtr mask = region->ToMask(0, 0, width, height);
if (!mask || mask->Width <= 0 || mask->Height <= 0)
continue;
ILImagePtr merged = LImage::Create();
imageOp->BitOr(regionHullMask, mask, merged);
regionHullMask = merged;
}
}
// 对主体区域构造凸包并光栅化为统一掩码。
ILImagePtr convexHullMask = LImage::Create();
convexHullMask->ImageFormat = LPVImageFormatGrayscale8;
convexHullMask->SetImageData(width, height, blackData.data(), width, true);
if (regionHullBlobs) {
for (int i = 0; i < regionHullBlobs->Count(); ++i) {
ILBlobPtr blob = regionHullBlobs->Item(i);
if (!blob)
continue;
ILPolygonPtr hull = blob->GetConvexHull();
if (!hull)
continue;
ILPolyRegionPtr hullRegion = hull->ToPolyRegion();
if (!hullRegion)
continue;
ILImagePtr mask = hullRegion->ToMask(0, 0, width, height);
if (!mask || mask->Width <= 0 || mask->Height <= 0)
continue;
ILImagePtr merged = LImage::Create();
imageOp->BitOr(convexHullMask, mask, merged);
convexHullMask = merged;
}
}
// 凸包掩码扣除主体掩码,得到凹陷候选区域。
ILImagePtr invertedRegionHullMask = LImage::Create();
imageOp->Invert(regionHullMask, invertedRegionHullMask);
ILImagePtr deviationMask = LImage::Create();
imageOp->BitAnd(convexHullMask, invertedRegionHullMask, deviationMask);
// 对凹陷候选做连通域、面积和凸性过滤。
ILBlobAnalysisPtr deviationAnalysis = LBlobAnalysis::Create();
deviationAnalysis->ColorMode = LPVImageFormatGrayscale8;
deviationAnalysis->FillHole = false;
deviationAnalysis->AddBlobRange(255, 255);
deviationAnalysis->MaxCount = 10000;
deviationAnalysis->ContourType = LPVBlobContourType::LPVBlobContourExternal;
deviationAnalysis->Hierarchy = 0;
ILBlobResultsPtr deviationBlobs;
deviationAnalysis->Build(deviationMask, ILRegionPtr(), &deviationBlobs);
ILBlobFilterPtr largeHoleFilter = LBlobFilter::Create();
largeHoleFilter->SetFilterFeature(LPVBlobFeatures::LPVBlobArea, 2000.0, 99999.0);
ILBlobResultsPtr largeHoles = deviationBlobs ? largeHoleFilter->FilterResults(deviationBlobs) : ILBlobResultsPtr();
ILBlobFilterPtr convexityFilter = LBlobFilter::Create();
convexityFilter->SetFilterFeature(LPVBlobFeatures::LPVBlobConvexity, 0.0, 0.85);
ILBlobResultsPtr holes = largeHoles ? convexityFilter->FilterResults(largeHoles) : ILBlobResultsPtr();
struct HoleMeasurement {
double area = 0.0;
double row = 0.0;
double column = 0.0;
};
std::vector<HoleMeasurement> measurements;
const int holeCount = holes ? holes->Count() : 0;
measurements.reserve(static_cast<size_t>(holeCount));
for (int i = 0; i < holeCount; ++i) {
ILBlobPtr blob = holes->Item(i);
if (!blob)
continue;
HoleMeasurement item;
item.area = blob->GetFeature(LPVBlobFeatures::LPVBlobArea);
item.row = blob->GetFeature(LPVBlobFeatures::LPVBlobCenterY);
item.column = blob->GetFeature(LPVBlobFeatures::LPVBlobCenterX);
measurements.push_back(item);
}
for (size_t i = 0; i < measurements.size(); ++i) {
for (size_t j = i + 1; j < measurements.size(); ++j) {
const bool shouldSwap = (measurements[j].row < measurements[i].row) ||
(measurements[j].row == measurements[i].row && measurements[j].column < measurements[i].column);
if (shouldSwap)
std::swap(measurements[i], measurements[j]);
}
}
std::vector<std::string> holeEntries;
holeEntries.reserve(measurements.size());
for (size_t i = 0; i < measurements.size(); ++i) {
const HoleMeasurement& hole = measurements[i];
std::string entry;
entry += " {\n";
entry += " \"id\": " + std::to_string(static_cast<int>(i) + 1) + ",\n";
entry += " \"area\": " + formatDouble(hole.area, 3) + ",\n";
entry += " \"row\": " + formatDouble(hole.row, 3) + ",\n";
entry += " \"column\": " + formatDouble(hole.column, 3) + "\n";
entry += " }";
holeEntries.push_back(entry);
}
std::string jsonText;
jsonText += "{\n";
jsonText += " \"case_name\": \"hull\",\n";
jsonText += " \"image_name\": \"hull\",\n";
jsonText += " \"hole_count\": " + std::to_string(static_cast<int>(measurements.size())) + ",\n";
jsonText += " \"holes\": [\n";
if (!holeEntries.empty())
jsonText += join(holeEntries, ",\n") + "\n";
jsonText += " ]\n";
jsonText += "}\n";
writeTextFile(outputPath, jsonText);
CoUninitialize();
return 0;
}