Привет, Habr!
Недавно начал изучать либу OpenCV, не обнаружил там некоторых нужных (имхо) функции на уровне предварительной обработки.
Решил исправить пробел, представлю их здесь. На полноту не претендую, функции рассчитаны на работу с изображением в градациях серого.
Пока две функции — морфологическая реконструкция и удаление объектов на границе изображения.
Если кому понравится положу еще утолщение без замыкания и утончение до остова.
Функция восстановления изображения (морфологическая реконструкция).
// восстановление изобр
// src - вх массив изобр
// dst - вых массив изобр
// mask - исход массив изобр
// minBright - порог по яркости
void ImgProcAuxFunc::Reconstruct(cv::Mat& src, cv::Mat& dst, cv::Mat& mask, int minBright)
{
// обходим вх изобр
for (int j = 0; j < src.rows; j++)
{
const uchar* curr = src.ptr<const uchar>(j);
const uchar* res = dst.ptr<const uchar>(j);
for (int i = 0; i < src.cols; i++) {
if ((curr[i] >= minBright) && (curr[i] != res[i]))
RoundLinkObj(mask, dst, cv::Point2i(i, j), minBright);
}
}
}
Функция удаления объектов находящихся на границе изображения.
// удаление объектов наход на границе
// src - вх массив изобр
// dst - вых массив изобр
// border - граница изобр
void ImgProcAuxFunc::ClearObjOnBorder(cv::Mat& src, cv::Mat& dst, BorderImg border)
{
auto rndObj = [this](cv::Mat& src, cv::Mat& auxRes, int stRow, int endRow, int stCol, int endCol){
for (int j = stRow; j < endRow; j++) {
const uchar* curr = src.ptr<const uchar>(j);
const uchar* res = auxRes.ptr<const uchar>(j);
for (int i = stCol; i < endCol; i++) {
if (curr[i] != res[i])
RoundLinkObj(src, auxRes, cv::Point2i(i, j));
}
}
};
cv::Mat auxRes(src.size(), src.type(), cv::Scalar(0));
switch (border)
{
case down:
rndObj(src, auxRes, src.rows - 1, src.rows, 0, src.cols);
dst = src ^ auxRes;
break;
case up:
rndObj(src, auxRes, 0, 1, 0, src.cols);
dst = src ^ auxRes;
break;
case left:
rndObj(src, auxRes, 0, src.rows, 0, 1);
dst = src ^ auxRes;
break;
case right:
rndObj(src, auxRes, 0, src.rows, src.cols - 1, src.cols);
dst = src ^ auxRes;
break;
case all:
rndObj(src, auxRes, src.rows - 1, src.rows, 0, src.cols);
rndObj(src, auxRes, 0, 1, 0, src.cols);
rndObj(src, auxRes, 0, src.rows, 0, 1);
rndObj(src, auxRes, 0, src.rows, src.cols - 1, src.cols);
dst = src ^ auxRes;
break;
}
}
Вспом функция — выделение связанных областей. Ничего нового, аналог ф-ии floodFill.
// обход связанного объекта изобр
// src - вх массив изобр
// startPnt - старт точка обхода
// minBright - порог яркости для включения
// isFourConn - связность пкс (true - 4 связн)
void ImgProcAuxFunc::RoundLinkObj(cv::Mat& src, cv::Mat& dst, cv::Point2i& startPnt,
int minBright, bool isFourConn)
{
std::vector<cv::Point2i> pntList = std::vector<cv::Point2i>();
// первый проход по линии начал точки //
RoundLine(src, dst, startPnt, pntList, minBright, isFourConn);
// проходим по получен списку //
while(!pntList.empty())
{
cv::Point2i nextPnt(pntList.back());
pntList.pop_back();
RoundLine(src, dst, nextPnt, pntList, minBright, isFourConn);
}
}
Вспом функция — обход гориз линии.
// обход связанной гориз линии на изобр
// src - вх массив изобр
// dst - вых массив изобр
// startPnt - старт точка обхода
// slist - список пикс, по кот идем
// minBright - порог яркости
// isFourConn - связность пкс (true - 4 связн)
void ImgProcAuxFunc::RoundLine(cv::Mat& src, cv::Mat& dst, cv::Point2i& startPnt, std::vector<cv::Point2i>& slist,
int minBright, bool isFourConn)
{
bool isUpEna = ((startPnt.y + 1) < src.rows) ? true : false; // низ ?
bool isDownEna = ((startPnt.y - 1) >= 0) ? true : false; // верх ?
const uchar* currSrc = src.ptr<const uchar>(startPnt.y);
const uchar* upSrc = isUpEna ? src.ptr<const uchar>(startPnt.y + 1) : src.ptr<const uchar>(startPnt.y);
const uchar* downSrc = isDownEna ? src.ptr<const uchar>(startPnt.y - 1) : src.ptr<const uchar>(startPnt.y);
uchar* currDst = dst.ptr<uchar>(startPnt.y);
uchar* upDst = isUpEna ? dst.ptr<uchar>(startPnt.y + 1) : dst.ptr<uchar>(startPnt.y);
uchar* downDst = isDownEna ? dst.ptr<uchar>(startPnt.y - 1) : dst.ptr<uchar>(startPnt.y);
bool isPxlUp = false; // один пкс на строку сверху уже добавлен
bool isPxlDown = false; // один пкс на строку снизу уже добавлен
// смотрим старт точку
if (currSrc[startPnt.x] < minBright) return;
// идем влево, смотрим вниз и вверх, попавшихся соседей добавляем в промеж список
for (int i = startPnt.x; i >= 0; i--) {
// отмечаем тек точку
currDst[i] = currSrc[i];
// смотрим вниз
if (isDownEna)
if (downSrc[i] < minBright) isPxlDown = false;
else if (!isPxlDown && (downDst[i] != downSrc[i])){
downDst[i] = downSrc[i];
slist.push_back(cv::Point2i(i, startPnt.y - 1));
isPxlDown = true;
}
// смотрим вверх
if (isUpEna)
if (upSrc[i] < minBright) isPxlUp = false;
else if (!isPxlUp && (upDst[i] != upSrc[i])){
upDst[i] = upSrc[i];
slist.push_back(cv::Point2i(i, startPnt.y + 1));
isPxlUp = true;
}
// выходим, если на границе
if (i == 0) break;
// смотрим влево
if (currSrc[i - 1] < minBright)
{
if (!isFourConn)
{
// смотрим влево-вниз
if (isDownEna)
if (!isPxlDown && (downSrc[i - 1] >= minBright) && (downDst[i - 1] != downSrc[i - 1]))
{
downDst[i - 1] = downSrc[i - 1];
slist.push_back(cv::Point2i(i - 1, startPnt.y - 1));
}
// смотрим влево-вверх
if (isUpEna)
if (!isPxlUp && (upSrc[i - 1] >= minBright) && (upDst[i - 1] != upSrc[i - 1]))
{
upDst[i - 1] = upSrc[i - 1];
slist.push_back(cv::Point2i(i - 1, startPnt.y + 1));
}
}
// выходим
break;
}
}
if (downSrc[startPnt.x] >= minBright) isPxlDown = true; // один пкс на строку сверху уже добавлен
else isPxlDown = false;
if (upSrc[startPnt.x] >= minBright) isPxlUp = true; // один пкс на строку снизу уже добавлен
else isPxlUp = false;
// идем вправо, смотрим вниз и вверх, попавшихся соседей добавляем в промеж список
for (int i = startPnt.x; i < src.cols; i++) {
// отмечаем тек точку
currDst[i] = currSrc[i];
// смотрим вниз
if (isDownEna)
if (downSrc[i] < minBright) isPxlDown = false;
else if (!isPxlDown && (downDst[i] != downSrc[i])){
downDst[i] = downSrc[i];
slist.push_back(cv::Point2i(i, startPnt.y - 1));
isPxlDown = true;
}
// смотрим вверх
if (isUpEna)
if (upSrc[i] < minBright) isPxlUp = false;
else if (!isPxlUp && (upDst[i] != upSrc[i])){
upDst[i] = upSrc[i];
slist.push_back(cv::Point2i(i, startPnt.y + 1));
isPxlUp = true;
}
// выходим, если на границе
if (i == src.cols - 1) break;
// смотрим вправо
if (currSrc[i + 1] < minBright)
{
if (!isFourConn)
{
// смотрим вправо-вниз
if (isDownEna)
if (!isPxlDown && (downSrc[i + 1] >= minBright) && (downDst[i + 1] != downSrc[i + 1]))
{
downDst[i + 1] = downSrc[i + 1];
slist.push_back(cv::Point2i(i + 1, startPnt.y - 1));
}
// смотрим вправо-вверх
if (isUpEna)
if (!isPxlUp && (upSrc[i + 1] >= minBright) && (upDst[i + 1] != upSrc[i + 1]))
{
upDst[i + 1] = upSrc[i + 1];
slist.push_back(cv::Point2i(i + 1, startPnt.y + 1));
}
}
// выходим
break;
}
}
}