使用 OpenCV 做一個簡單方便的摳圖工具

在計算機圖像領域,我們經常需要做一些摳圖的工作,將圖像中的目標感興趣區域提取出來,剔除其他冗餘的背景元素,以實現計算機視覺的各項功能(如車輛檢測、人臉檢測等)。如果純粹使用美圖秀秀等工具類軟件的話,由於工具類軟件將圖像處理中各種可能用到的功能都集成在了一起,所以純粹做摳圖的話效率很低。現在我們就用 OpenCV 來實現一段簡易的摳圖程序,只需要在畫面上選定目標的感興趣區域,該目標就會被自動按序號保存。

 

代碼如下,同時包含有通俗易懂的註釋:

#include <io.h>
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <opencv.hpp>

// 摳圖是單目標還是多目標,若爲單目標請將下面這行文字取消註釋,反之請註釋這段文字。
// #define SINGLE_OBJECT

#define TRUE 1                                  // 邏輯真
#define FALSE 0                                 // 邏輯假

#define CODE_ESC 27                             // ESC 鍵的編碼
#define CODE_SPC 32                             // 空格鍵的編碼

#define STATUS_WAIT 0                           // 摳圖等待狀態
#define STATUS_PROC 1                           // 摳圖進行狀態
#define STATUS_DONE 2                           // 摳圖完成狀態

#define VIDEO_FILENAME "capture-1.mp4"          // 視頻流文件名

static int m_x1     = 0;                        // 鼠標指針座標(起點 x)
static int m_x2     = 0;                        // 鼠標指針座標(終點 x)
static int m_y1     = 0;                        // 鼠標指針座標(起點 y)
static int m_y2     = 0;                        // 鼠標指針座標(終點 y)
static int m_status = STATUS_WAIT;              // 當前摳圖狀態指示

static void on_mouse(int, int, int, int, void*);// 鼠標回調

// 主程序
int main(void)
{
	int        end    = 0;                      // 指示是否結束程序
	int        next   = 0;                      // 指示是否切換到下一張圖片
	int        code   = 0;                      // 存儲按鍵編碼
	int        count  = 0;                      // 存儲目標計數
	int        frame  = 0;                      // 視頻幀號(用於間隔採樣)
	int        maxCol = 0;                      // 圖像最大列數(= 圖像寬度 - 1)
	int        maxRow = 0;                      // 圖像最大行數(= 圖像高度 - 1)
	CvCapture* pVideo = NULL;                   // 視頻流對象
	IplImage*  pFrame = NULL;                   // 視頻幀圖像(用於樣本存儲)
	IplImage*  pFrmCp = NULL;                   // 視頻幀圖像(用於屏幕顯示)
	CvPoint    pt1    = cvPoint(0, 0);          // 矩形框對角座標點 1
	CvPoint    pt2    = cvPoint(0, 0);          // 矩形框對角座標點 2
	CvRect     r      = cvRect(0, 0, 0, 0);     // 感興趣區域矩形框
	char       seq[]  = "-2147483648";          // 目標計數的字串形式
	char       fil[]  = "data\\-2147483648.jpg";// 文件名字串

	// 載入視頻流
	pVideo = cvCreateFileCapture(VIDEO_FILENAME);
	if (!pVideo)
	{
		return -1;
	} // if (!pVideo)

	// 創建數據存儲目錄
	if (_access("data", 0) != 0)
	{
		system("md data");
	} // if (_access())

	// 獲取首幀圖像,並創建拷貝,同時得到最大列數和行數,方便之後使用
	pFrame = cvQueryFrame(pVideo);
	if (pFrame)
	{
		pFrmCp = cvCreateImage(cvGetSize(pFrame), 8, pFrame->nChannels);
		maxCol = pFrmCp->width - 1;
		maxRow = pFrmCp->height - 1;
	} // if (pFrame)
	else
	{
		cvReleaseCapture(&pVideo);
		return -1;
	} // else

	// 設置顯示窗口,並設置鼠標回調
	cvNamedWindow("Monitor", CV_WINDOW_AUTOSIZE);
	cvSetMouseCallback("Monitor", on_mouse, NULL);

	// 其他初始化
	end = FALSE;
	count = 0;
	frame = 0;

	while (!end && pFrame)
	{
		next = FALSE;
		while (!next && !end)
		{
			// 將原始視頻圖像複製到拷貝區域中(清除已將圖像進行污染的線條、矩形框等)
			cvCopy(pFrame, pFrmCp, NULL);
			if (STATUS_WAIT == m_status)
			{
				// 等待摳圖狀態。畫出橫向和縱向的參考線
				cvLine(pFrmCp, cvPoint(m_x1, 0), cvPoint(m_x1, maxRow), CV_RGB(0, 255, 0));
				cvLine(pFrmCp, cvPoint(0, m_y1), cvPoint(maxCol, m_y1), CV_RGB(0, 255, 0));
			} // if (STATUS_WAIT)
			else if (STATUS_PROC == m_status)
			{
				// 摳圖過程中。畫出當前選定的感興趣區域
				pt1 = cvPoint(m_x1, m_y1);
				pt2 = cvPoint(m_x2, m_y2);
				cvRectangle(pFrmCp, pt1, pt2, CV_RGB(0, 255, 0));
			} // else if (STATUS_PROC)
			else if (STATUS_DONE == m_status)
			{
				// 摳圖完畢,獲得感興趣區域並按編號保存樣本
				r = cvRect(
					m_x1,
					m_y1,
					m_x2 - m_x1 + 1,
					m_y2 - m_y1 + 1
					); // 矩形感興趣區域
				if (r.width > 30 && r.height > 30)
				{
					// 區域達到了一定大小,摳圖有效,保存感興趣區域樣本
					++count;
					cvSetImageROI   (pFrame, r);
					sprintf_s       (seq, "%d", count);
					strcpy_s        (fil, "data\\");
					strcat_s        (fil, seq);
					strcat_s        (fil, ".jpg");
					cvSaveImage     (fil, pFrame, 0);
					cvResetImageROI (pFrame);

#ifdef SINGLE_OBJECT
					m_next = TRUE;
#endif
				} // if (r.width)
				
				// 恢復摳圖等待狀態
				m_status = STATUS_WAIT;
			} // else if (STATUS_DONE)

			cvShowImage("Monitor", pFrmCp);
			code = cvWaitKey(10);
			if (CODE_SPC == code)
			{
				next = TRUE;
			} // if (CODE_SPC)
			else if (CODE_ESC == code)
			{
				end = TRUE;
			} // else if (CODE_ESC)
		} // while (!next)

		if (next)
		{
			do
			{
				pFrame = cvQueryFrame(pVideo);
				++frame;
			} while (pFrame && frame % 60 != 0); // do...while
		} // if (next)
	} // while (!end)

	cvDestroyAllWindows();
	cvReleaseImage(&pFrmCp);
	cvReleaseCapture(&pVideo);

	return 0;
} // main()


// 鼠標事件回調
void on_mouse(int event, int x, int y, int flags, void* param)
{
	switch (flags)
	{
	case CV_EVENT_MOUSEMOVE:
		if (STATUS_WAIT == m_status)
		{
			// 等待狀態,確定感興趣區域起點
			m_x1 = x, m_y1 = y;
		} // if (STATUS_WAIT)
		else if (STATUS_PROC == m_status)
		{
			// 捕捉狀態,確定感興趣區域終點
			m_x2 = x, m_y2 = y;
		} // else if (STATUS_PROC)
		break;

	case CV_EVENT_LBUTTONDOWN:
		if (STATUS_WAIT == m_status)
		{
			// 等待狀態按下鼠標,進入捕捉狀態,固定起點
			m_x1 = x, m_y1 = y;
			m_status = STATUS_PROC;
		} // if (STATUS_WAIT)
		else if (STATUS_PROC == m_status)
		{
			// 捕捉狀態按下鼠標,捕捉完成,固定終點
			m_x2 = x, m_y2 = y;
			m_status = STATUS_DONE;
		} // else if (STATUS_PROC)
		break;
	} // switch
} // on_mouse()


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章