找轮廓
#include <opencv2/opencv.hpp>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
int main( int argc, char** argv ){
Mat srcImage=imread("1.jpg", 0);//载入原始图,且必须以二值图模式载入
imshow("原始图",srcImage);
Mat dstImage = Mat::zeros(srcImage.rows, srcImage.cols, CV_8UC3);//初始化结果图
srcImage = srcImage > 119;//取大于119的部分(注意载入时已经是二值图)
imshow( "取阈值后的原始图", srcImage );
vector<vector<Point> > contours;//定义轮廓
vector<Vec4i> hierarchy;//定义层次,每个轮廓的后,前,父,子轮廓
findContours( srcImage, contours, hierarchy,RETR_CCOMP, CHAIN_APPROX_SIMPLE );//查找轮廓
int index = 0;
for( ; index >= 0; index = hierarchy[index][0] ){// 遍历所有顶层的轮廓, 以随机颜色绘制出每个连接组件颜色
Scalar color( rand()&255, rand()&255, rand()&255 );
drawContours( dstImage, contours, index, color, FILLED, 8, hierarchy );
}
imshow( "轮廓图", dstImage );//显示最后的轮廓图
waitKey(0);
}
轮廓检测可以使用findContours函数,检测步骤是:
1. 使用拉普拉斯或Canny等边缘检测算子处理图像,获得仅包含边界的二值图像
2. 使用findContorus方法,获取图像所有的边界连续像素序列,并保存在contours向量中
3. 标示出contours向量中所有的轮廓序列
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main( ){
Mat image(600, 600, CV_8UC3);
RNG& rng = theRNG();
while(1){
char key;//键值
int count = (unsigned)rng%100 + 3;//随机生成点的数量
vector<Point> points; //点值
for(int i = 0; i < count; i++ ){//随机生成点坐标
Point point;
point.x = rng.uniform(image.cols/4, image.cols*3/4);
point.y = rng.uniform(image.rows/4, image.rows*3/4);
points.push_back(point);
}
vector<int> hull;//检测凸包
convexHull(Mat(points), hull, true);//输入,输出,默认真(假只返回凸包点数)
image = Scalar::all(0);//绘制出随机颜色的点
for(int i = 0; i < count; i++ )
circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA);
int hullcount = (int)hull.size();//凸包的边数
Point point0 = points[hull[hullcount-1]];//连接凸包边的坐标点
for(int i = 0; i < hullcount; i++ ){//绘制凸包的边
Point point = points[hull[i]];
line(image, point0, point, Scalar(255, 255, 255), 2, LINE_AA);
point0 = point;
}
imshow("凸包检测示例", image);//显示效果图
key = (char)waitKey();//按下ESC,Q,或者q,程序退出
if( key == 27 || key == 'q' || key == 'Q' )
break;
}
return 0;
}
凸包原理请见ACM
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
int main(){
Mat image(600, 600, CV_8UC3);//初始化变量
RNG& rng = theRNG();//初始化随机值
while(1){//参数初始化
int count = rng.uniform(3, 103);//随机生成点的数量
vector<Point> points;//点值
for(int i = 0; i < count; i++ ){//随机生成点坐标
Point point;
point.x = rng.uniform(image.cols/4, image.cols*3/4);
point.y = rng.uniform(image.rows/4, image.rows*3/4);
points.push_back(point);
}
RotatedRect box = minAreaRect(Mat(points));//对给定的 2D 点集,寻找最小面积的包围矩形!!!全文关键!
Point2f vertex[4];
box.points(vertex);
image = Scalar::all(0);//绘制出随机颜色的点
for( int i = 0; i < count; i++ )
circle( image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA );
for( int i = 0; i < 4; i++ )//绘制出最小面积的包围矩形
line(image, vertex[i], vertex[(i+1)%4], Scalar(100, 200, 211), 2, LINE_AA);
imshow( "矩形包围示例", image );//显示窗口
char key = (char)waitKey(); //按下ESC,Q,或者q,程序退出
if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'
break;
}
return 0;
}
关于最小矩形覆盖的算法:
Graham’s Scan法构建凸包,时间复杂度O(nlogn)
利用旋转卡壳的思想,最小矩形至少存在一条边与凸包共线。所以直接枚举底边,利用旋转卡壳确定其余三个点即可
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
using namespace std;
int main( ){//初始化变量和随机值
Mat image(600, 600, CV_8UC3);
RNG& rng = theRNG();
while(1){//循环,按下ESC,Q,q键程序退出,否则有键按下便一直更新
int count = rng.uniform(3, 103);//随机生成点的数量
vector<Point> points;//点值
for(int i = 0; i < count; i++ ){//随机生成点坐标
Point point;
point.x = rng.uniform(image.cols/4, image.cols*3/4);
point.y = rng.uniform(image.rows/4, image.rows*3/4);
points.push_back(point);
}
Point2f center;//对给定的 2D 点集,寻找最小面积的包围圆
float radius = 0;
minEnclosingCircle(Mat(points), center, radius);//全文关键,算法解释见下文
image = Scalar::all(0);//绘制出随机颜色的点
for( int i = 0; i < count; i++ )
circle( image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA );
circle(image, center, cvRound(radius), Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 2, LINE_AA);//绘制出最小面积的包围圆
imshow( "圆形包围示例", image );//显示窗口
char key = (char)waitKey();//按下ESC,Q,或者q,程序退出
if( key == 27 || key == 'q' || key == 'Q' ) // 'ESC'
break;
}
return 0;
}
矩的定义不难理解,就是每个点与均值之差的K次方和;然后是周长,把点集读一次就不是周长了吗?面积的话,把轮廓点集打上标记,然后任取一个内部点开始深搜就可以知道有多少个内部点了
分水岭:说得简单点,就是从灰度最低(或者用户自义的点)开始填充,填到梯度忽然变大的分割线(边缘)
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
#define WINDOW_NAME1 "【程序窗口1】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【分水岭算法效果图】" //为窗口标题定义的宏
Mat g_maskImage, g_srcImage;
Point prevPt(-1, -1);
static void ShowHelpText();
static void on_Mouse( int event, int x, int y, int flags, void* );
int main( int argc, char** argv ){
//【1】载入原图并显示,初始化掩膜和灰度图
g_srcImage = imread("1.jpg", 1);
imshow( WINDOW_NAME1, g_srcImage );
Mat srcImage,grayImage;
g_srcImage.copyTo(srcImage);
cvtColor(g_srcImage, g_maskImage, COLOR_BGR2GRAY);
cvtColor(g_maskImage, grayImage, COLOR_GRAY2BGR);
g_maskImage = Scalar::all(0);
//【2】设置鼠标回调函数
setMouseCallback( WINDOW_NAME1, on_Mouse, 0 );
//【3】轮询按键,进行处理
while(1){
//获取键值
int c = waitKey(0);
//若按键键值为ESC时,退出
if( (char)c == 27 )
break;
//按键键值为2时,恢复源图
if( (char)c == '2' ){
g_maskImage = Scalar::all(0);
srcImage.copyTo(g_srcImage);
imshow( "image", g_srcImage );
}
//若检测到按键值为1或者空格,则进行处理
if( (char)c == '1' || (char)c == ' ' ){
//定义一些参数
int i, j, compCount = 0;
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
//寻找轮廓
findContours(g_maskImage, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
//轮廓为空时的处理
if( contours.empty() )
continue;
//拷贝掩膜
Mat maskImage(g_maskImage.size(), CV_32S);
maskImage = Scalar::all(0);
//循环绘制出轮廓
for( int index = 0; index >= 0; index = hierarchy[index][0], compCount++ )
drawContours(maskImage, contours, index, Scalar::all(compCount+1), -1, 8, hierarchy, INT_MAX);
//compCount为零时的处理
if( compCount == 0 )
continue;
//生成随机颜色
vector<Vec3b> colorTab;
for( i = 0; i < compCount; i++ ){
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
colorTab.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
//计算处理时间并输出到窗口中
double dTime = (double)getTickCount();
watershed( srcImage, maskImage );
dTime = (double)getTickCount() - dTime;
printf( "\t处理时间 = %gms\n", dTime*1000./getTickFrequency() );
//双层循环,将分水岭图像遍历存入watershedImage中
Mat watershedImage(maskImage.size(), CV_8UC3);
for( i = 0; i < maskImage.rows; i++ )
for( j = 0; j < maskImage.cols; j++ ){
int index = maskImage.at<int>(i,j);
if( index == -1 )
watershedImage.at<Vec3b>(i,j) = Vec3b(255,255,255);
else if( index <= 0 || index > compCount )
watershedImage.at<Vec3b>(i,j) = Vec3b(0,0,0);
else
watershedImage.at<Vec3b>(i,j) = colorTab[index - 1];
}
//混合灰度图和分水岭效果图并显示最终的窗口
watershedImage = watershedImage*0.5 + grayImage*0.5;
imshow( WINDOW_NAME2, watershedImage );
}
}
return 0;
}
static void on_Mouse( int event, int x, int y, int flags, void* ){
//处理鼠标不在窗口中的情况
if( x < 0 || x >= g_srcImage.cols || y < 0 || y >= g_srcImage.rows )
return;
//处理鼠标左键相关消息
if( event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON) )
prevPt = Point(-1,-1);
else if( event == EVENT_LBUTTONDOWN )
prevPt = Point(x,y);
//鼠标左键按下并移动,绘制出白色线条
else if( event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON) ){
Point pt(x, y);
if( prevPt.x < 0 )
prevPt = pt;
line( g_maskImage, prevPt, pt, Scalar::all(255), 5, 8, 0 );
line( g_srcImage, prevPt, pt, Scalar::all(255), 5, 8, 0 );
prevPt = pt;
imshow(WINDOW_NAME1, g_srcImage);
}
}
图像修补
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/photo/photo.hpp"
#include <iostream>
using namespace cv;
using namespace std;
#define WINDOW_NAME0 "【原始图参考】" //为窗口标题定义的宏
#define WINDOW_NAME1 "【原始图】" //为窗口标题定义的宏
#define WINDOW_NAME2 "【修补后的效果图】" //为窗口标题定义的宏
Mat srcImage0,srcImage1, inpaintMask;
Point previousPoint(-1,-1);//原来的点坐标
static void On_Mouse( int event, int x, int y, int flags, void* ){
//鼠标左键弹起消息
if( event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON) )
previousPoint = Point(-1,-1);
//鼠标左键按下消息
else if( event == EVENT_LBUTTONDOWN )
previousPoint = Point(x,y);
//鼠标按下并移动,进行绘制
else if( event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON) ) {
Point pt(x,y);
if( previousPoint.x < 0 )
previousPoint = pt;
//绘制白色线条
line( inpaintMask, previousPoint, pt, Scalar::all(255), 5, 8, 0 );
line( srcImage1, previousPoint, pt, Scalar::all(255), 5, 8, 0 );
previousPoint = pt;
imshow(WINDOW_NAME1, srcImage1);
}
}
int main( int argc, char** argv ){
//载入原始图并进行掩膜的初始化
Mat srcImage = imread("1.jpg", -1);
if(!srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定图片存在~! \n"); return false; }
srcImage0 = srcImage.clone();
srcImage1 = srcImage.clone();
inpaintMask = Mat::zeros(srcImage1.size(), CV_8U);
//显示原始图参考
imshow(WINDOW_NAME0, srcImage0);
//显示原始图
imshow(WINDOW_NAME1, srcImage1);
//设置鼠标回调消息
setMouseCallback( WINDOW_NAME1, On_Mouse, 0 );
//轮询按键,根据不同的按键进行处理
while (1){
//获取按键键值
char c = (char)waitKey();
//键值为ESC,程序退出
if( c == 27 )break;
//键值为2,恢复成原始图像
if( c == '2' ){
inpaintMask = Scalar::all(0);
srcImage.copyTo(srcImage1);
imshow(WINDOW_NAME1, srcImage1);
}
//键值为1或者空格,进行图像修补操作
if( c == '1' || c == ' ' ){
Mat inpaintedImage;
inpaint(srcImage1, inpaintMask, inpaintedImage, 3, INPAINT_TELEA);
//入图,掩膜(非零像素将被修补),出图,每个需修补的邻域,修补方法
imshow(WINDOW_NAME2, inpaintedImage);
}
}
return 0;
}
图像修补算法思路:
对待修补区域,一层一层地往内填补,收缩,每补一个点,就计算其邻域各点的梯度,利用梯度求出该点预想值,然后综合邻域各点对其预想值(平均或加权平均)得到填充的值