Поиск "fitLine" контуров в OpenCV
Я программа, которая находит контуры в потоке, например:
Я хочу найти "набор точек", которые могут описать эти контуры, скажем, как красная линия :
желтая часть-это моменты контуры ,
Я пытался использовать fitLine
функция opencv, но результат был нонсенс, любая идея, как я могу получить среднюю линию контуров, которые должны представлять ключевой аспект моего Контуры. кстати, я не прошу коды ! ** только намек, как я могу это сделать ?
заранее спасибо за любую помощь
2 ответов
возможно, попробуйте этот подход с преобразованием расстояния и обнаружением гребня:
cv::Mat input = cv::imread("fitLine.jpg");
cv::Mat gray;
cv::cvtColor(input,gray,CV_BGR2GRAY);
cv::Mat mask = gray>100;
cv::imshow("mask",mask);
cv::Mat dt;
cv::distanceTransform(mask,dt,CV_DIST_L1,CV_DIST_MASK_PRECISE);
cv::imshow("dt", dt/15.0f);
cv::imwrite("fitLineOut.png",255*dt/15.0f);
//care: this part doesn't work for diagonal lines, a ridge detection would be better!!
cv::Mat lines = cv::Mat::zeros(input.rows, input.cols, CV_8UC1);
//only take the maxDist of each row
for(unsigned int y=0; y<dt.rows; ++y)
{
float biggestDist = 0;
cv::Point2i biggestDistLoc(0,0);
for(unsigned int x=0; x<dt.cols; ++x)
{
cv::Point2i current(x,y);
if(dt.at<float>(current) > biggestDist)
{
biggestDist = dt.at<float>(current) ;
biggestDistLoc = current;
}
}
lines.at<unsigned char>(biggestDistLoc) = 255;
}
//and the maxDist of each row
for(unsigned int x=0; x<dt.cols; ++x)
{
float biggestDist = 0;
cv::Point2i biggestDistLoc(0,0);
for(unsigned int y=0; y<dt.rows; ++y)
{
cv::Point2i current(x,y);
if(dt.at<float>(current) > biggestDist)
{
biggestDist = dt.at<float>(current) ;
biggestDistLoc = current;
}
}
lines.at<unsigned char>(biggestDistLoc) = 255;
}
cv::imshow("max", lines);
cv::waitKey(-1);
идея состоит в том, чтобы вычислить преобразование расстояния и найти гребни в контуре.
вот как выглядит преобразованное изображение расстояния: вы можете видеть, что в середине линий есть локальный максимум гребня.
затем я использую очень простой метод: просто найдите максимальное расстояние в каждой строке/столбце. Это очень небрежно и должно быть изменено для реальный метод обнаружения или утончения гребня!!!!
редактировать: дополнительное описание:
идея в том, чтобы найти все точки, которые находятся в "середине" контур. В математике / графике медиальной осью в какой-то" середине " объекта и его определением должны быть все точки, которые имеют одинаковое минимальное расстояние, по крайней мере, до двух точек контура одновременно.
способ аппроксимации медиальной оси-вычислить преобразование расстояния. Преобразование расстояния-это матрица, которая содержит для каждого пикселя расстояние до следующей точки объекта (например, контур объекта) (см.http://en.wikipedia.org/wiki/Distance_transform тоже). Это первое изображение. Там вы можете видеть, что точки в середине линий немного ярче, чем точки ближе к границе, а это означает, что самые яркие точки вдоль линий можно интерпретировать как медиальную ось (приближение), поскольку, если вы отойдете от него (ортогонально направлению линии), расстояния станут меньше, поэтому пик-это точка, где расстояние до обеих границ близко к равенству.
таким образом, если вы можете найти эти "хребты" в преобразовании расстояния, вы закончили. Обнаружение гребня обычно выполняется оператором Harris (см.http://en.wikipedia.org/wiki/Ridge_detection).
в быстрой и грязной версии, которую я опубликовал, я пытаюсь обнаружить хребет принимать только максимальное значение в каждой строке и в каждой строке. Это нормально для большинства горизонтальных и вертикальных гребней, но не по диагонали-те. Так что, возможно, вы действительно хотите обменять for loops
С реальные ridge detection
.
интересная задача :) вот мое решение:
вот код:
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdarg.h>
#include <set>
#include "opencv2/opencv.hpp"
#include "fstream"
#include "iostream"
using namespace std;
using namespace cv;
int Thinning(unsigned char * ucBinedImg, unsigned char * ucThinnedImage, long lWidth, long lHeight, long lIterativeLimit)
{
if(ucBinedImg == NULL)
return -1;
if(ucThinnedImage == NULL)
return -2;
if(lIterativeLimit == -1)
lIterativeLimit = 60000;
unsigned char x1, x2, x3, x4, x5, x6, x7, x8, xp;
unsigned char g1, g2, g3, g4;
unsigned char b1, b2, b3, b4;
unsigned char np1, np2, npm;
unsigned char *pUp, *pDown, *pImg;
long lDeletedPoints = 0;
// set border
memcpy(ucThinnedImage, ucBinedImg, lWidth*lHeight);
for(long it=0; it<lIterativeLimit; it++)
{
lDeletedPoints = 0;
for(long i=1; i<lHeight-1; i++)
{
// init neighborhood
pUp = ucBinedImg + (i-1)*lWidth;
pImg = ucBinedImg + i*lWidth ;
pDown = ucBinedImg + (i+1)*lWidth ;
for( long j=1; j<lWidth-1; j++)
{
pUp++;
pImg++;
pDown++;
if(!*pImg)
continue;
x6 = *(pUp-1);
x5 = *(pImg-1);
x4 = *(pDown-1);
x7 = *pUp;
xp = *pImg;
x3 = *pDown;
x8 = *(pUp+1);
x1 = *(pImg + 1);
x2 = *(pDown + 1);
b1 = !x1 && (x2 == 1 || x3 == 1);
b2 = !x3 && (x4 == 1 || x5 == 1);
b3 = !x5 && (x6 == 1 || x7 == 1);
b4 = !x7 && (x8 == 1 || x1 == 1);
g1 = (b1 + b2 + b3 + b4) == 1;
np1 = x1|| x2;
np1 += x3 || x4;
np1 += x5 || x6;
np1 += x7 || x8;
np2 = x2|| x3;
np2 += x4 || x5;
np2 += x6 || x7;
np2 += x8 || x1;
npm = np1>np2?np2:np1;
g2 = npm>=2 && npm<=3;
g3 = (x1 && (x2 || x3 || !x8)) == 0;
g4 = (x5 && (x6 || x7 || !x4)) == 0;
// first part
if(g1 && g2 && g3)
{
// delete this point
ucThinnedImage[lWidth*i + j] = 0;
++lDeletedPoints;
}
}
}
//syn
memcpy(ucBinedImg, ucThinnedImage, lWidth*lHeight);
for(long i=1; i<lHeight-1; i++)
{
// init neighborhood
pUp = ucBinedImg + (i-1)*lWidth;
pImg = ucBinedImg + i*lWidth ;
pDown = ucBinedImg + (i+1)*lWidth ;
for( long j=1; j<lWidth-1; j++)
{
pUp++;
pImg++;
pDown++;
if(!*pImg)
continue;
x6 = *(pUp-1);
x5 = *(pImg-1);
x4 = *(pDown-1);
x7 = *pUp;
xp = *pImg;
x3 = *pDown;
x8 = *(pUp+1);
x1 = *(pImg + 1);
x2 = *(pDown + 1);
b1 = !x1 && (x2 == 1 || x3 == 1);
b2 = !x3 && (x4 == 1 || x5 == 1);
b3 = !x5 && (x6 == 1 || x7 == 1);
b4 = !x7 && (x8 == 1 || x1 == 1);
g1 = (b1 + b2 + b3 + b4) == 1;
np1 = x1|| x2;
np1 += x3 || x4;
np1 += x5 || x6;
np1 += x7 || x8;
np2 = x2|| x3;
np2 += x4 || x5;
np2 += x6 || x7;
np2 += x8 || x1;
npm = np1>np2?np2:np1;
g2 = npm>=2 && npm<=3;
g3 = (x1 && (x2 || x3 || !x8)) == 0;
g4 = (x5 && (x6 || x7 || !x4)) == 0;
// second part
if(g1 && g2 && g4)
{
// delete this point
ucThinnedImage[lWidth*i + j] = 0;
++lDeletedPoints;
}
}
}
//syn
memcpy(ucBinedImg, ucThinnedImage, lWidth*lHeight);
// if no points to be deleted
if(lDeletedPoints == 0)
break;
}
// clear edge bar
for(long i=0; i<lHeight; i++)
{
for(long j=0; j<lWidth; j++)
{
if(i<16)
ucThinnedImage[i*lWidth+j] = 0;
else if(i>=lHeight-16)
ucThinnedImage[i*lWidth+j] = 0;
else if(j<16)
ucThinnedImage[i*lWidth+j] = 0;
else if(j>=lWidth-16)
ucThinnedImage[i*lWidth+j] = 0;
}
}
return 0;
}
void Thinning(Mat& src,Mat& dst,long IterativeLimit=-1)
{
Mat bin_img=src&1;
if(!dst.empty()){dst.release();}
dst=Mat::zeros(src.size(),CV_8UC1);
Thinning(bin_img.data,dst.data,bin_img.cols,bin_img.rows,IterativeLimit);
dst*=255;
}
int main(int argc, char* argv[])
{
namedWindow("source");
namedWindow("result");
Mat img=imread("raw_image.jpg",0);
cv::threshold(img,img,128,255,cv::THRESH_BINARY);
int erosion_size=5;
Mat element = getStructuringElement( cv::MORPH_ELLIPSE,Size( 2*erosion_size + 1, 2*erosion_size+1 ),Point( erosion_size, erosion_size ) );
cv::dilate(img,img,element);
Mat thinned;
Thinning(img,thinned);
vector<Vec2f> lines;
HoughLines(thinned, lines, 0.5, CV_PI/360, 50, 0, 0 );
float hist_theta[2]={0,0};
float hist_rho[2]={0,0};
float n[2]={0,0};
for( size_t i = 0; i < lines.size(); i++ )
{
float rho = lines[i][0], theta = lines[i][1];
if(fabs(theta-CV_PI/2)<CV_PI/4)
{
hist_theta[0]+=theta;
hist_rho[0]+=rho;
n[0]+=1;
}else
{
hist_theta[1]+=theta;
hist_rho[1]+=rho;
n[1]+=1;
}
}
for( size_t i = 0; i < 2; i++ )
{
float rho = hist_rho[i]/n[i], theta = hist_theta[i]/n[i];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a*rho, y0 = b*rho;
pt1.x = cvRound(x0 + 1000*(-b));
pt1.y = cvRound(y0 + 1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line( thinned, pt1, pt2, Scalar(255,255,255), 1, CV_AA);
}
imshow("source",img);
imshow("result",thinned);
cv::waitKey(0);
}
вот взлом в этом источнике он использует две 1D гистограммы для постобработки. В реальном приложении он должен использовать 2D-гистограмму для усреднения близких линий.