ROI的使用与批量存储

1. ROI简介

ROI(Region of Interest)是指图像中的一个矩形区域,可能你后续的程序需要单独处理这一个小区域,如图所示:

图1 ROI的解释

ROI在实际工作中有很重要的作用,在很多情况下,使用它们会提高计算机视觉代码的执行速度。这是因为它们允许对图像的某一小部分进行操作,而不是对整个图像进行运算。在OpenCV中,所有的对图像操作的函数都支持ROI,如果你想打开ROI,可以使用函数cvSetImageROI(),并给函数传递一个矩形子窗口。而cvResetImageROI()是用于关闭ROI的。注意,在程序中,一旦使用了ROI做完相应的运算,就一定要用cvResetImageROI()来关闭ROI,否则,其他操作执行时还会使用ROI的定义。

IplImage 结构解读:

typedef struct _IplImage
{
    intnSize;
    int ID;
    intnChannels;
    intalphaChannel;
    int depth;
    charcolorModel[4];
    charchannelSeq[4];
    intdataOrder;
    int origin;
    int align;
    int width;
    int height;
    struct _IplROI *roi;
    struct_IplImage *maskROI;
    void*imageId;
    struct_IplTileInfo *tileInfo;
    intimageSize;
    char*imageData;
    intwidthStep;
    intBorderMode[4];
    intBorderConst[4];
    char*imageDataOrigin;
}IplImage;

origin变量可以有两个取值:IPL_ORIGIN_TL或者IPL_ORIGIN_BL,分别代表图像坐标系原点在左上角或是左下角。相应的,在计算机视觉领域,一个重要的错误来源就是原点位置的定义不统一。例如,图像的来源不同,操作系统不同,视频解码codec不同,存储方式不同等等,都可以造成原点位置的变化。

IplROI结构体包含了xOffset,yOffset,height,width,coi成员变量,其中xOffset,yOffset是x,y坐标,coi代表channel of interest(感兴趣的通道)。有时候,OpenCV图像函数不是作用于整个图像,而是作用于图像的某一个部分。这是,我们就可以使用roi成员变量了。如果IplImage变量中设置了roi,则OpenCV函数就会使用该roi变量。如果coi被设置成非零值,则对该图像的操作就只作用于被coi指定的通道上了。不幸的是,许多OpenCV函数忽略了coi的值。

【注意】

(1)ROI只是原始图像的一个子区域,没有自己的内存空间,对ROI的操作会影响原始图像。

(2)对ROI区域的遍历还是要根据矩形框在原始图像中的相对位置遍历。笔者曾经犯了利用原始图像src的src->imageData做基地址,用src->height、src->width做边界条件,用src->widthStep计算步长来遍历ROI图像的错误。这是因为抱有IplImage结构体中的成员变量数值会随ROI图像而改变的错误思想。

2. 对应的相关函数

SetImageROI

基于给定的矩形设置感兴趣区域

void cvSetImageROI( IplImage* image, CvRectrect );

参数介绍

image

图像头.

rect

ROI矩形.

函数 cvSetImageROI 基于给定的矩形设置图像的ROI(感兴趣区域)。如果ROI是NULL并且参数RECT的值不等于整个图像, ROI被分配。不像 COI,大多数的 OpenCV 函数支持 ROI 并且处理它就像它是一个分离的图像 (例如,所有的像素坐标从ROI的左上角或左下角(基于图像的结构)计算。

ResetImageROI

释放图像的ROI

voidcvResetImageROI( IplImage* image );

【参数介绍】

image

图像头.

函数 cvResetImageROI 释放图像ROI。释放之后整个图像被认为是全部被选中的。相似的结果可以通过下述办法

cvSetImageROI(image, cvRect( 0, 0, image->width, image->height ));

cvSetImageCOI(image, 0 );

但是后者的变量不分配 image->roi

3.   利用ROI剪切图像并批量保存

图像的剪切有多种方法,其中一种是使用ROI的方法:

第一步:将需要剪切的图像图像不部分设置为ROI

cvSetImageROI(src,cvRect(x,y,width,height));

第二步:新建一个与需要剪切的图像部分同样大小的新图像

cvCreateImage(cvSize(width,height),IPL_DEPTH,nchannels);

第三步:将源图像复制到新建的图像中

cvCopy(src,dst,0);

第四步:释放ROI区域

cvResetImageROI(src);

笔者在做火焰检测时需要提取火焰疑似区域进行进一步的判断,需要在以上方法的基础上批量存储ROI区域图像到硬盘,这里有一个技巧是使用字符串格式化sprintf把要保存的路径及文件名写入字符缓冲区。如:

sprintf(imageName,"%s_%d-%04d年%02d月%02d日%02d时%02分%02d秒.jpg","E:\\Test\\ROI\\fire",countROI,
				tm_ptr.tm_year-100+2000,tm_ptr.tm_mon + 1, tm_ptr.tm_mday,
				tm_ptr.tm_hour,tm_ptr.tm_min, tm_ptr.tm_sec);

此语句把当前时间与编号都写入字符数组中以备cvSaveImage( const char*filename, const CvArr* image )函数使用。

以下是源码的主函数:

int main(){
	IplImage *img = NULL;		//输入图像,8bit 3通道
	IplImage *colTemp = NULL;	//颜色分割后(有内部空洞)的火焰图片
	IplImage *gray = NULL;		//灰度图
	IplImage *mask = NULL;		//二值图,用于复制图像的掩膜
	IplImage *dst = NULL;		//输出火焰疑似图像,8bit、3通道
	IplImage *ROI[5] = {NULL};	//定义ROI图像头的数组
	char imageName[50];			//存储图像的名称
	CvMemStorage *stor;			//存储轮廓序列
	CvSeq *cont;				//可动态增长元素序列
	time_t the_time;			//时间
	stor=cvCreateMemStorage(0);	//创建一内存块并返回指向块首的指针,默认64K
	cont=cvCreateSeq(CV_SEQ_ELTYPE_POINT,sizeof(CvSeq),sizeof(CvPoint),stor);	//创建一序列 

	img = cvLoadImage("E:\\Test\\SegTest\\fire40.JPG");					//载入原始图片
	colTemp =  cvCreateImage(cvGetSize(img),img->depth,img->nChannels);	//经过颜色分割后(有内部空洞)的火焰图片
	gray = cvCreateImage(cvGetSize(img),img->depth,1);
	mask = cvCreateImage(cvGetSize(img),img->depth,1);
	dst = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);		//保存经过填补后的火焰图片
	cvZero(dst);
	colorModel(img,colTemp);											//使用颜色模型对火焰图像分割
	cvCvtColor(colTemp,gray,CV_BGR2GRAY);
	//使用cvFindContours函数与cvFillPoly填充连通区内部空洞
	fillSeg(gray,mask);
	cvCopy(img,dst,mask);
	//使用cvFindContours根据火焰疑似区域(已填充内部空洞)的掩膜(单通道)寻找轮廓;
	//随之使用cvRectangle找到轮廓的外接矩形,从而可以得到ROI。
	cvFindContours(mask,stor,&cont,sizeof(CvContour),CV_RETR_EXTERNAL ,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));
	cvDrawContours(img,cont,cvScalar(255,255,255),cvScalar(0,0,0),1,1);
	int countROI = 0;		//ROI编号
	for(;cont;cont = cont->h_next){
		double area = fabs(cvContourArea(cont,CV_WHOLE_SEQ));
		double len = cvArcLength(cont, CV_WHOLE_SEQ,-1);
		double circularity = (4*PI*area)/(len*len);		//计算疑似火焰区域的圆形度
		CvRect r = ((CvContour*)cont)->rect;//子类转换为父类例子
		if(r.height * r.width > CONTOUR_MAX_AERA && circularity < 0.4 && circularity > 0.07 ) //面积小的外接矩形以及圆形度过大或过小的轮廓抛弃掉
		{
			printf("\n圆形度:%f\n",circularity);
			cvRectangle(img, cvPoint(r.x,r.y),cvPoint(r.x + r.width, r.y + r.height),CV_RGB(255,0,0), 1, CV_AA,0);
			cvSetImageROI(img,r);
			ROI[countROI] = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);
			cvCopy(img,ROI[countROI],0);
			cvResetImageROI(img);

			(void) time(&the_time);				//获得从纪元(1970年1月1日)开始至今的秒数
			tm tm_ptr = *localtime(&the_time);  //从time_t格式的时间里返回本地时间
			//保存的图片名
			sprintf(imageName,"%s_%d-%04d年%02d月%02d日%02d时%02分%02d秒.jpg","E:\\Test\\ROI\\fire",countROI,
				tm_ptr.tm_year-100+2000,tm_ptr.tm_mon + 1, tm_ptr.tm_mday,
				tm_ptr.tm_hour,tm_ptr.tm_min, tm_ptr.tm_sec);
			//sprintf(imageName, "%s%d%s", "E:\\Test\\ROI\\image", countROI, ".jpg");//保存的图片名
			cvSaveImage(imageName,ROI[countROI]);   //保存一帧图片
			countROI++;
			memset(imageName,0,sizeof(imageName));
		}
	}
	for(int i = 0;i<countROI;i++){
		char showImg[16];
		sprintf(showImg,"ROI%d",i);
		cvShowImage(showImg,ROI[i]);
	}
	cvShowImage("原始图片",img);
	cvShowImage("颜色分割处理",colTemp);
	cvShowImage("填充处理图片",dst);
	cvShowImage("掩膜",mask);
	cvWaitKey();
	//销毁窗口
	cvDestroyAllWindows();
	//释放图像
	for(int i = 0;i<countROI;i++){
		cvReleaseImage(&ROI[i]);
	}
	cvReleaseImage(&img);
	cvReleaseImage(&colTemp);
	cvReleaseImage(&gray);
	cvReleaseImage( &mask );
}

4. 附录

工程源码

#include<opencv/cv.h>
#include<opencv/highgui.h>
#include <math.h>
#define min(x,y) (x<y?x:y)

#define R_THRESHHOLD 125
#define S_THRESHHOLD 60
#define CONTOUR_MAX_AERA 200
#define PI 3.1416

//RGB+HSI颜色模型
void colorModel(IplImage *src,IplImage * dst){
	int step = NULL;
	int rows = src->height;
	int cols = src->width;
	for(int i = 0;i < rows;i++){
		//uchar* dataS = src.ptr<uchar>(i);
		//uchar* dataD = dst.ptr<uchar>(i);
		uchar *dataS = (uchar*)src->imageData;
		uchar *dataD= (uchar*)dst->imageData;
		for(int j = 0;j < cols; j++){
			step = i*src->widthStep+j*src->nChannels;;
			float S;
			float b = dataS[step]/255.0;
			float g = dataS[step+1]/255.0;
			float r = dataS[step+2]/255.0;
			float minRGB = min(min(r,g),b);
			float den = r+g+b;
			if(den == 0)	//分母不能为0
				S = 0;
			else
				S = (1 - 3*minRGB/den)*100;
			if(dataS[step+2] <= R_THRESHHOLD || dataS[step+2] < 165){
				dataD[step] = 0;
				dataD[step+1] = 0;
				dataD[step+2] = 0;
			}
			else if(dataS[step+2] <= dataS[step+1] || dataS[step+1] <= dataS[step] ){
				dataD[step] = 0;
				dataD[step+1] = 0;
				dataD[step+2] = 0;
			}
			else if(S <= (float)(S_THRESHHOLD*(255 - dataS [step+2]))/R_THRESHHOLD){
				dataD[step] = 0;
				dataD[step+1] = 0;
				dataD[step+2] = 0;
			}
			else{
				dataD[step] = dataS[step];
				dataD[step+1] = dataS[step+1];
				dataD[step+2] = dataS[step+2];
			}
		}
	}
}

//根据分割结果确定轮廓并填充
void fillSeg(IplImage *src,IplImage *tempdst)
{
	CvSeq * contour = NULL;
	CvMemStorage * storage = cvCreateMemStorage();
	//在二值图像中寻找轮廓,CV_CHAIN_APPROX_SIMPLE - 压缩水平、垂直和对角分割,即函数只保留末端的象素点
	cvFindContours(src,storage,&contour,sizeof(CvContour),CV_RETR_CCOMP ,CV_CHAIN_APPROX_SIMPLE);
	cvZero(tempdst);
	for( contour; contour != 0; contour = contour->h_next)
	{
		//轮廓的方向影响面积的符号。因此函数也许会返回负的结果。应用函数 fabs() 得到面积的绝对值。
		double area = cvContourArea( contour,CV_WHOLE_SEQ );
		//计算整个轮廓或部分轮廓的面积
		if(fabs(area) < 10)
		{
			continue;
		}
		//	CvScalar color = CV_RGB( 255, 255, 255 );
		CvPoint *point = new CvPoint[contour->total];
		CvPoint *Point;

		//printf("图像分割contour->total\t%d\n",contour->total);
		for (int i = 0;i<contour->total;i++)
		{
			Point = (CvPoint*)cvGetSeqElem(contour,i);
			point[i].x =Point->x;
			point[i].y = Point->y;
		}
		int pts[1] = {contour->total};
		cvFillPoly(tempdst,&point,pts,1,CV_RGB(255,255,255));//填充多边形内部
	}
	cvReleaseMemStorage(&storage);
}
int main(){
	IplImage *img = NULL;		//输入图像,8bit 3通道
	IplImage *colTemp = NULL;	//颜色分割后(有内部空洞)的火焰图片
	IplImage *gray = NULL;		//灰度图
	IplImage *mask = NULL;		//二值图,用于复制图像的掩膜
	IplImage *dst = NULL;		//输出火焰疑似图像,8bit、3通道
	IplImage *ROI[5] = {NULL};	//定义ROI图像头的数组
	char imageName[50];			//存储图像的名称
	CvMemStorage *stor;			//存储轮廓序列
	CvSeq *cont;				//可动态增长元素序列
	time_t the_time;			//时间
	stor=cvCreateMemStorage(0);	//创建一内存块并返回指向块首的指针,默认64K
	cont=cvCreateSeq(CV_SEQ_ELTYPE_POINT,sizeof(CvSeq),sizeof(CvPoint),stor);	//创建一序列 

	img = cvLoadImage("E:\\Test\\SegTest\\fire40.JPG");					//载入原始图片
	colTemp =  cvCreateImage(cvGetSize(img),img->depth,img->nChannels);	//经过颜色分割后(有内部空洞)的火焰图片
	gray = cvCreateImage(cvGetSize(img),img->depth,1);
	mask = cvCreateImage(cvGetSize(img),img->depth,1);
	dst = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);		//保存经过填补后的火焰图片
	cvZero(dst);
	colorModel(img,colTemp);											//使用颜色模型对火焰图像分割
	cvCvtColor(colTemp,gray,CV_BGR2GRAY);
	//使用cvFindContours函数与cvFillPoly填充连通区内部空洞
	fillSeg(gray,mask);
	cvCopy(img,dst,mask);
	//使用cvFindContours根据火焰疑似区域(已填充内部空洞)的掩膜(单通道)寻找轮廓;
	//随之使用cvRectangle找到轮廓的外接矩形,从而可以得到ROI。
	cvFindContours(mask,stor,&cont,sizeof(CvContour),CV_RETR_EXTERNAL ,CV_CHAIN_APPROX_SIMPLE,cvPoint(0,0));
	cvDrawContours(img,cont,cvScalar(255,255,255),cvScalar(0,0,0),1,1);
	int countROI = 0;		//ROI编号
	for(;cont;cont = cont->h_next){
		double area = fabs(cvContourArea(cont,CV_WHOLE_SEQ));
		double len = cvArcLength(cont, CV_WHOLE_SEQ,-1);
		double circularity = (4*PI*area)/(len*len);		//计算疑似火焰区域的圆形度
		CvRect r = ((CvContour*)cont)->rect;//子类转换为父类例子
		if(r.height * r.width > CONTOUR_MAX_AERA && circularity < 0.4 && circularity > 0.07 ) //面积小的外接矩形以及圆形度过大或过小的轮廓抛弃掉
		{
			printf("\n圆形度:%f\n",circularity);
			cvRectangle(img, cvPoint(r.x,r.y),cvPoint(r.x + r.width, r.y + r.height),CV_RGB(255,0,0), 1, CV_AA,0);
			cvSetImageROI(img,r);
			ROI[countROI] = cvCreateImage(cvGetSize(img),img->depth,img->nChannels);
			cvCopy(img,ROI[countROI],0);
			cvResetImageROI(img);

			(void) time(&the_time);				//获得从纪元(1970年1月1日)开始至今的秒数
			tm tm_ptr = *localtime(&the_time);  //从time_t格式的时间里返回本地时间
			//保存的图片名
			sprintf(imageName,"%s_%d-%04d年%02d月%02d日%02d时%02分%02d秒.jpg","E:\\Test\\ROI\\fire",countROI,
				tm_ptr.tm_year-100+2000,tm_ptr.tm_mon + 1, tm_ptr.tm_mday,
				tm_ptr.tm_hour,tm_ptr.tm_min, tm_ptr.tm_sec);
			//sprintf(imageName, "%s%d%s", "E:\\Test\\ROI\\image", countROI, ".jpg");//保存的图片名
			cvSaveImage(imageName,ROI[countROI]);   //保存一帧图片
			countROI++;
			memset(imageName,0,sizeof(imageName));
		}
	}
	for(int i = 0;i<countROI;i++){
		char showImg[16];
		sprintf(showImg,"ROI%d",i);
		cvShowImage(showImg,ROI[i]);
	}
	cvShowImage("原始图片",img);
	cvShowImage("颜色分割处理",colTemp);
	cvShowImage("填充处理图片",dst);
	cvShowImage("掩膜",mask);
	cvWaitKey();
	//销毁窗口
	cvDestroyAllWindows();
	//释放图像
	for(int i = 0;i<countROI;i++){
		cvReleaseImage(&ROI[i]);
	}
	cvReleaseImage(&img);
	cvReleaseImage(&colTemp);
	cvReleaseImage(&gray);
	cvReleaseImage( &mask );
}
时间: 2024-10-26 03:51:00

ROI的使用与批量存储的相关文章

django orm 批量存储数据

项目中 需要大量数据的保存操作,每条执行save操作效率太低,在官网上找到bull_create 的批量存储方式,效率提高很多 Insert in bulk When creating objects, where possible, use the bulk_create() method to reduce the number of SQL queries. For example: Entry.objects.bulk_create([ Entry(headline="Python 3.

iOS真机沙盒文件查看 图片批量存储/获取/删除

转载请标注来自:http://blog.csdn.net/u014202635/article/details/46863629 1. 选Devices 2. 3. 选中第二行 4. 保存在桌面 5. 右击选中显示包内容 6. 以上环境为x-code6.3.1 //---------------------------------------------------------------------------------------------------------------------

[Java]批量存储信息

批量存储信息 import java.util.LinkedList; import java.util.List; import java.util.Properties; import org.apache.log4j.Logger; public class MonitorStoreService4 extends Thread{ static final Logger logger = Logger.getLogger(MonitorStoreService4.class); Linke

一般数据存储和批量数据存储比较--10万条数据

一.在数据库中建立Student表 二.创建10万条数据 创建数据 三.创建一般数据存储方法 public static void SaveGeneral(DataTable dt) { string strConn = @"Data Source=.;Initial Catalog=Test;Integrated Security=True"; SqlConnection conn = new SqlConnection(strConn); conn.Open(); SqlComma

Mysql 存储过程 处理批量插入具有一定特点的数据

本博文属于原创,转载请注明出处~! 首先,根据业务需求,需要往现有的所有第二级别的知识点中,添加数据 所有第二级别的数据如下: 上图中,cateCode就是代表级别代表,parentID实现链表树状级别 所有的第三第四级别的数据都是一样,其中sort是和当前id一致的,而parentID需要根据逻辑公式求出,下边是第三第四级别的插入sql数据 INSERT INTO `lc_knowledge_type` ( `createTime`, `modifyTime`, `child`, `image

Android数据库高手秘籍(五)——LitePal的存储操作

经过前面几篇文章的学习,我们已经把LitePal的表管理模块的功能都很好地掌握了,相信大家都已经体会到了使用LitePal来创建表.升级 表.以及建立表关联所带来的便利.那么从本篇文章开始,我们将进入到一个新模块的学习旅程当中,使用LitePal来进行表的CRUD操作.还没有看过前 一篇文章的朋友建议先去参考 Android数据库高手秘籍(四)——使用LitePal建立表关联 . LitePal提供的CRUD操作的API还是颇为丰富的,一篇文章肯定是介绍不全的,因此这里我们仍然是分几篇文章进行讲

Redis 存储机制

Redis存储机制分成两种Snapshot和AOF.无论是那种机制,Redis都是将数据存储在内存中. Snapshot工作原理: 是将数据先存储在内存,然后当数据累计达到某些设定的伐值的时候,就会触发一次DUMP操作,将变化的数据一次性写入数据文件(RDB文件). AOF 工作原理: 是将数据也是先存在内存,但是在存储的时候会使用调用fsync来完成对本次写操作的日志记录,这个日志揭露文件其实是一个基于Redis网络交互协议的文本文件.AOF调用fsync也不是说全部都是无阻塞的,在某些系统上

Storm介绍及核心组件和编程模型

离线计算 离线计算:批量获取数据.批量传输数据.周期性批量计算数据.数据展示 代表技术:Sqoop批量导入数据.HDFS批量存储数据.MapReduce批量计算数据.Hive批量计算数据.azkaban/oozie任务调度 流式计算 流式计算:数据实时产生.数据实时传输.数据实时计算.实时展示 代表技术:Flume实时获取数据.Kafka/metaq实时数据存储.Storm/JStorm实时数据计算.Redis实时结果缓存.持久化存储(mysql). 一句话总结:将源源不断产生的数据实时收集并实

LSM和B+树区别

B树存储引擎是B树(关于B树的由来,数据结构以及应用场景可以看之前一篇博文)的持久化实现,不仅支持单条记录的增.删.读.改操作,还支持顺序扫描(B+树的叶子节点之间的指针),对应的存储系统就是关系数据库(Mysql等). LSM树(Log-Structured Merge Tree)存储引擎和B树存储引擎一样,同样支持增.删.读.改.顺序扫描操作.而且通过批量存储技术规避磁盘随机写入问题.当然凡事有利有弊,LSM树和B+树相比,LSM树牺牲了部分读性能,用来大幅提高写性能. 通过以上的分析,应该