#include <opencv2/core/core.hpp> // coreモジュールのヘッダーをインクルード
#include <opencv2/highgui/highgui.hpp> // highguiモジュールのヘッダーをインクルード
#include <opencv2/imgproc/imgproc_c.h> // imgprocモジュールのC版ヘッダーをインクルード
#include <windows.h>
#include <string.h>
#include <iostream>
#include <tchar.h>
#include <shlwapi.h>
#include <limits.h>
#include <time.h>
//#include <wingdi.h>
//#include <shlobj.h>
#pragma comment(lib,"shlwapi.lib")
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "hid.lib")
#pragma comment(lib, "winmm.lib")
#define S_BITMAP 1
#define S_PXM 2
#define S_PNG 3
#define S_JPG 4
#define EXTENCTION_LEN 6
#define FPS_LIMITS 60.0
#define GRAY_SCALED 1
#define THRESHOLDING 2
#define FILE_NAME_LEN 50//毎秒60枚取るとして7桁あれば40時間以上撮影可能。ファイル名重複対策に2桁、バックスラッシュに1桁、予備4桁で25桁
#define FILE_NAME_SUB_LEN 18//毎秒60枚取るとして7桁あれば40時間以上撮影可能。ファイル名重複対策に2桁、バックスラッシュに1桁、予備4桁で17桁
typedef struct _PARAM{
int jpeg_quality[2];//-q
int png_compression[2];//-c
int pxm_binary[2];//-b
}PARAM;
typedef struct _PROCESS_CONF{
PARAM param;
bool use_param;//-enable-ad-conf
double threshold;//二値化のしきい値。0なら大津のアルゴリズムを使用(default = 0)
short ppm_magic;//pxm_binary依存
short pic_type;//-p
short process_mode;//-edit
unsigned int frame_num;//-f
double frame_rate;//-fps
unsigned int core_filename_num;//-n
}PROCESS_CONF;
void print_help(void) {
fprintf(stdout, ""
"Scatto_continuo.exe [options...] [-enable-ad-conf [aditional option(s)]] \n"
"All parameter must be specified as an Intenger type without \"-fps\" and \"-th\""
" option(s)...\n"
" -h print help\n"
"\n"
" -p [type] set picture format\n"
" [type] : bmp, ppm, pgm, pbn, png, jpg\n"
" -f [num] set how many times you want to capture.\n"
" -fps [0-60] set fps<double>.\n"
" When you set \"23.97\", \"29.97\" or \"59.94\", we will use more accurate value.\n"
" -edit [0-2] set picture process mode\n"
" [num] :0:do anything.1:to grayscaled. 2:to thresholded. \n"
" If you use \"-p pgm\" or \"-p pbm\", \"-edit\" will be disable.\n"
"\n"
" -enable-ad-conf enable a group of options listed below"
"\n"
" aditional option(s)\n"
" -b [mode] set PPM save_mode(binary : 1(default) / ascii : 0)\n"
" -c [level] set PNG complession level.\n"
" -q [quality] set JPG quality\n"
"\n"
" -n \"core_name\" set core_name.\n"
" Output-filename will be \"[core_name][num]_[num].[type]\"\n"
" -th [1-254] You specify a threshold<double>,\n"
" and disable the Otsu's algorithm\n"
"\n"
"We will caputure and set core_name caputure and save as bitmap image only once when there is no command line arguments");
exit(0);
}
void print_command_line_error(int place = 0){
printf("invalid option(s).\n"
"place:%d", place);
print_help();
}
int getnum(void){
char s[100];
long t;
char *endptr;
fgets(s, 100, stdin);
errno = 0;
t = strtol(s, &endptr, 10);
if (errno != 0 || *endptr != '\n' || (t < INT_MIN || INT_MAX < t))
return -1;
return t;
}
int sgetnum(const char *s, const long max = INT_MAX, const long min = INT_MIN){
long t;
char *endptr;
errno = 0;
t = strtol(s, &endptr, 10);
if (errno != 0 || t < min || max < t || t < INT_MIN || INT_MAX < t)
return -1;
return (int)t;
}
double sgetnum_d(const char *s, const long double max = DBL_MAX, const long double min = DBL_MIN){
long double t;
char *endptr;
errno = 0;
t = strtod(s, &endptr);
if (errno != 0 || t < min || max < t || t < DBL_MIN || DBL_MAX < t)
return -1;
return (double)t;
}
//==============================================================
// [概要]
// wchar_t* から char* への変換を行う
// [引数]
// pSrc const wchar_t* 変換するwchar_t型の文字列。
// pDest char* 変換結果のchar型文字列。
// sDestSize const size_t pDestのサイズ
// [戻り値]
// int 0:正常,-1:異常
//==============================================================
int wchartToChar(const wchar_t* pSrc, char* pDest, const size_t sDestSize)
{
size_t iReturnValue;
errno_t ret = wcstombs_s(&iReturnValue, pDest, sDestSize, pSrc, _TRUNCATE);
if (ret != 0) return -1;
return 0;
}
//==============================================================
// [概要]
// char* から wchar_t* への変換を行う
// [引数]
// pSrc const char* 変換するchar型の文字列。
// pDest wchar_t* 変換結果のwchar_t型文字列。
// sDestSize const size_t pDestのサイズ
// [戻り値]
// int 0:正常,その他:異常
//==============================================================
int charToWchart(const char* pSrc, wchar_t* pDest, const size_t sDestSize)
{
size_t iReturnValue;
errno_t ret = mbstowcs_s(&iReturnValue, pDest, sDestSize, pSrc, _TRUNCATE);
if (ret != 0) return ret;
return 0;
}
int Determine_Filename(const int roop_turn, TCHAR *tfilename, TCHAR * tcore_filename, const short pic_type, const short ppm_magic = 6){
TCHAR crDir[MAX_PATH + FILE_NAME_LEN + 1];//毎秒60枚取るとして7桁あれば40時間以上撮影可能。ファイル名重複対策に2桁、バックスラッシュに1桁、予備4桁
int i = 0;
TCHAR pic_extension[EXTENCTION_LEN];
switch (pic_type)
{
case S_BITMAP:
_stprintf_s(pic_extension, EXTENCTION_LEN, __T("bmp"));
break;
case S_PXM:
switch (ppm_magic)
{
case 1:
case 4:
_stprintf_s(pic_extension, EXTENCTION_LEN, __T("pbm"));
break;
case 2:
case 5:
_stprintf_s(pic_extension, EXTENCTION_LEN, __T("pgm"));
break;
case 3:
case 6:
_stprintf_s(pic_extension, EXTENCTION_LEN, __T("ppm"));
break;
default:
exit(-1);
break;
}
break;
case S_PNG:
_stprintf_s(pic_extension, EXTENCTION_LEN, __T("png"));
break;
case S_JPG:
_stprintf_s(pic_extension, EXTENCTION_LEN, __T("jpg"));
break;
default:
exit(-1);
break;
}
while (1){//ファイル名重複対策
i++;
GetCurrentDirectory(MAX_PATH + 1, crDir);
PathAddBackslash(crDir);
TCHAR temp[FILE_NAME_SUB_LEN + 1];
_stprintf_s(tfilename, _tcslen(tfilename) + 1, __T("%s"), tcore_filename);
_stprintf_s(temp, FILE_NAME_SUB_LEN + 1, __T("%d_%02d.%s"), roop_turn, i, pic_extension);
_tcscat_s(tfilename, _tcslen(tfilename) + FILE_NAME_SUB_LEN + 1, temp);
_tcscat_s(crDir, MAX_PATH + _tcslen(tfilename) + FILE_NAME_SUB_LEN + 1, tfilename);
if (PathFileExists(crDir) == 0) break;
}
return 0;
}
void DrawCursor(HDC hdc)
{
int x, y;
CURSORINFO cursorInfo;
ICONINFO iconInfo;
cursorInfo.cbSize = sizeof(CURSORINFO);
GetCursorInfo(&cursorInfo);
GetIconInfo(cursorInfo.hCursor, &iconInfo);
x = cursorInfo.ptScreenPos.x - iconInfo.xHotspot;
y = cursorInfo.ptScreenPos.y - iconInfo.yHotspot;
DrawIcon(hdc, x, y, cursorInfo.hCursor);
}
bool WriteBitmap(LPTSTR lpszFileName, LPVOID lpBits, BITMAPINFOHEADER *bmiHeader)
{
HANDLE hFile;
DWORD dwResult;
BITMAPFILEHEADER bmfHeader;
hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//カレントディレクトリにファイル作成
if (hFile == INVALID_HANDLE_VALUE)
return false;
ZeroMemory(&bmfHeader, sizeof(BITMAPFILEHEADER));
bmfHeader.bfType = *(LPWORD)"BM";
bmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bmiHeader->biSizeImage;
bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
WriteFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER), &dwResult, NULL);
WriteFile(hFile, bmiHeader, sizeof(BITMAPINFOHEADER), &dwResult, NULL);
WriteFile(hFile, lpBits, bmiHeader->biSizeImage, &dwResult, NULL);
CloseHandle(hFile);
return true;
}
int Command_line_Analyzer(const int argc, char *argv[], PROCESS_CONF *pre_config){
if (argc == 1){
pre_config->pic_type = S_JPG;
pre_config->frame_num = 1;
pre_config->use_param = false;
pre_config->process_mode = 0;
return 0;
}
int return_num = 0;
if (strcmp(argv[1], "-h") == 0) print_help();
if (argc < 4 * 2 + 1) print_command_line_error(1);
int i;
for (i = 1; i < argc; i++){
if (0 == strcmp(argv[i], "-f")){
const unsigned int temp = sgetnum(argv[i + 1], UINT_MAX, 0);
if (temp == -1)
pre_config->frame_num = 1;// print_command_line_error(2);
else
pre_config->frame_num = temp;
i++;
}
if (0 == strcmp(argv[i], "-fps")){
const double temp = sgetnum_d(argv[i + 1], FPS_LIMITS, 0);
if (temp == -1) print_command_line_error(3);
if (temp == 23.97)
pre_config->frame_rate = (double)24000 / 1001;
else if (temp == 29.97)
pre_config->frame_rate = (double)30000 / 1001;
else if (temp == 59.94)
pre_config->frame_rate = (double)60000 / 1001;
else
pre_config->frame_rate = temp;
i++;
}
if (0 == strcmp(argv[i], "-p")){//bmp, ppm, pgm, pbn, png, jpg
if (0 == strcmp(argv[i + 1], "bmp"))
pre_config->pic_type = S_BITMAP;
else if (0 == strcmp(argv[i + 1], "png"))
pre_config->pic_type = S_PNG;
else if (0 == strcmp(argv[i + 1], "jpg"))
pre_config->pic_type = S_JPG;
else if (argv[i + 1][0] == 'p' && argv[i + 1][2] == 'm' && (argv[i + 1][1] == 'p' || argv[i + 1][1] == 'g' || argv[i + 1][1] == 'b')){
pre_config->pic_type = S_PXM;
switch (argv[i + 1][1])
{
case 'g':
pre_config->ppm_magic = 5;//P5
pre_config->process_mode = GRAY_SCALED;
break;
case 'b':
pre_config->ppm_magic = 4;//P4
pre_config->process_mode = THRESHOLDING;
break;
default:
pre_config->ppm_magic = 6;//P6
break;
}
}else
print_command_line_error(4);
i++;
}
if (pre_config->process_mode == 0 && 0 == strcmp(argv[i], "-edit")){
const int temp = sgetnum(argv[i + 1], 2, 0);
if (temp < 1)
pre_config->process_mode = 0;
else
pre_config->process_mode = temp;
i++;
}
if (0 == strcmp(argv[i], "-enable-ad-conf")) break;
}
for (; i < argc; i++){
if (0 == strcmp(argv[i], "-b")){
const int temp = sgetnum(argv[i + 1], 1, 0);
if (temp == -1) print_command_line_error(5);
if (temp == 0) pre_config->ppm_magic -= 3;//P1~P3
pre_config->param.pxm_binary[1] = temp;
i++;
}
if (0 == strcmp(argv[i], "-c")){
const int temp = sgetnum(argv[i + 1], 9, 0);
if (temp == -1) print_command_line_error(6);
pre_config->param.png_compression[1] = temp;
i++;
}
if (0 == strcmp(argv[i], "-q")){
const int temp = sgetnum(argv[i + 1], 100, 0);
if (temp == -1) print_command_line_error(7);
pre_config->param.jpeg_quality[1] = temp;
i++;
}
if (0 == strcmp(argv[i], "-n")){
if (argv[i + 1][0] == '-') print_command_line_error(8);
int temp = strlen(argv[i + 1]);
for (int k = 0; k < temp;k++)
if (argv[i + 1][i] == '\\') print_command_line_error(9);
return_num = temp;
i++;
}
if (0 == strcmp(argv[i], "-th")){
const double temp = sgetnum_d(argv[i + 1], 255, 0);
if (temp == -1) print_command_line_error(10);
pre_config->threshold = temp;
i++;
}
}
return 0;
}
int main(int argc, char *argv[])
{
if (argc < 1) exit(1);
timeBeginPeriod(1);// タイマーの最小精度を1msecにする
clock_t first_time = clock();
PROCESS_CONF pre_config = { { { CV_IMWRITE_JPEG_QUALITY, 95 }, { CV_IMWRITE_PNG_COMPRESSION, 3 }, { CV_IMWRITE_PXM_BINARY, 1 } }, false, 0, 6, 0 };
const int core_filename_num = Command_line_Analyzer(argc, argv, &pre_config);//コマンドライン引数解析
const double frame_rate = pre_config.frame_rate;
const unsigned int frame_num = pre_config.frame_num;
/*HWND hwndDesk;
RECT rc;
HDC hdc, hMemDC;
BITMAPINFO bmi;
BITMAP bm;//この中にBITMAPINFOHEADERがある
HBITMAP hBitmap;
LPDWORD lpPixel;
int width, height;
hwndDesk = GetDesktopWindow(); //デスクトップウィンドウのハンドルを取得します
GetWindowRect(hwndDesk, &rc);//指定されたウィンドウの左上端と右下端の座標をスクリーン座標で取得
width = rc.right;
height = rc.bottom;
ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);//構造体が必要とするバイト数
bmi.bmiHeader.biWidth = rc.right;
bmi.bmiHeader.biHeight = rc.bottom;
bmi.bmiHeader.biPlanes = 1;//ターゲット デバイスに対する面の数を指定する。これは必ず 1
bmi.bmiHeader.biBitCount = 32;//1ピクセル当たりのビッット数
//bmi.bmiHeader.biBitCount = 24;
bmi.bmiHeader.biCompression = BI_RGB;//非圧縮 RGB
bmi.bmiHeader.biSizeImage = dwSizeImage;
bmi.bmiHeader.biSizeImage = 0;
bmi.bmiHeader.biXPelsPerMeter = 0;
bmi.bmiHeader.biYPelsPerMeter = 0;
bmi.bmiHeader.biClrUsed = 0;
bmi.bmiHeader.biClrImportant = 0;
hdc = GetDC(hwndDesk);//デスクトップウィンドウのデバイスコンテキストを取得
//デバイス独立のビットマップ(DIB)を作成
//デスクトップの解像度がこんだけだからメモリこんだけ用意するとか
//圧縮されてんのかとか 1ピクセルあたりのビト数とかBITMAPINFOにいれてそれを渡す
hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
hMemDC = CreateCompatibleDC(hdc);//メモリデバイスコンテキストを作成
SelectObject(hMemDC, hBitmap);//ブラシとかそういうのみたいに選択する
BitBlt(hMemDC, 0, 0, width, height, hdc, 0, 0, SRCCOPY);//ビットブロック転送
ReleaseDC(hwndDesk, hdc);//デスクトップウィンドウのデバイスコンテキスト解放
// ビットマップ情報の取得
GetObject(hBitmap, sizeof(BITMAP), &bm);*/
//出力ファイル名決定
char* default_core_filenameA = "caputure";
char* core_filenameA;
if (core_filename_num == 0)
core_filenameA = default_core_filenameA;
else
core_filenameA = argv[core_filename_num];
#ifdef UNICODE
const size_t cchBufW = strlen(core_filenameA) + 1;
//wchar_t* const core_filenameT = new wchar_t[cchBufW * sizeof *core_filenameT];
wchar_t* const core_filenameT = static_cast<wchar_t*>(_alloca(cchBufW * sizeof *core_filenameT));
charToWchart(core_filenameA, core_filenameT, cchBufW);
#else
#define core_filenameT core_filenameA
#endif
//for
#ifdef UNICODE
wchar_t* const tFilename = new wchar_t[(cchBufW + FILE_NAME_SUB_LEN + 1) * sizeof *tFilename];
//wchar_t* const tFilename = static_cast<wchar_t*>(_alloca((cchBufW + FILE_NAME_SUB_LEN) * sizeof *tFilename));
#else
char* const tFilename = new char[((strlen(argv[core_filename_num]) + FILE_NAME_SUB_LEN + 2) * sizeof *tFilename];
//char* const tFilename = static_cast<char*>(_alloca((strlen(argv[core_filename_num]) + 1 + FILE_NAME_SUB_LEN) * sizeof *tFilename));
#endif
Determine_Filename(1, tFilename, core_filenameT, pre_config.pic_type);
#ifdef UNICODE
const size_t cchBufA = wcslen(tFilename) + 1;
char *aFilename = new char[cchBufA * sizeof *aFilename];
//char *aFilename = static_cast<char*>(_alloca(cchBufA * sizeof *aFilename));
wchartToChar(tFilename, aFilename, cchBufA);
#else
#define aFilename tFilename
#endif
HDC hdc;
HWND hwndDesk;
RECT rc;
BITMAP bm;
LPVOID lpPixel;
BITMAPINFO bmi;
HBITMAP hbmp;
HBITMAP hbmpPrev;
BITMAPINFOHEADER bmiHeader;
hwndDesk = GetDesktopWindow();//DesktopのWindowHandle
GetWindowRect(hwndDesk, &rc);
DWORD dwSizeImage = rc.bottom * ((3 * rc.right + 3) / 4) * 4;
ZeroMemory(&bmiHeader, sizeof(BITMAPINFOHEADER));
bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmiHeader.biWidth = rc.right;
bmiHeader.biHeight = rc.bottom;
bmiHeader.biPlanes = 1;
if (pre_config.pic_type == S_BITMAP && pre_config.process_mode == 0)//OpenCVには32、WriteBitmapには24
bmiHeader.biBitCount = 24;
else
bmiHeader.biBitCount = 32;
bmiHeader.biCompression = BI_RGB;
bmiHeader.biSizeImage = dwSizeImage;
bmiHeader.biXPelsPerMeter = 0;
bmiHeader.biYPelsPerMeter = 0;
bmiHeader.biClrUsed = 0;
bmiHeader.biClrImportant = 0;
bmi.bmiHeader = bmiHeader;
hdc = CreateCompatibleDC(NULL);
hbmp = CreateDIBSection(NULL, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS, &lpPixel, NULL, 0);;
hbmpPrev = (HBITMAP)SelectObject(hdc, hbmp);
BitBlt(hdc, 0, 0, rc.right, rc.bottom, GetWindowDC(hwndDesk), 0, 0, SRCCOPY);//これがキャプチャー部分
//GetWindowDCの制約によりWindwos2000以前ではマルチモニター環境ではプライマリモニタ以外に対象があると失敗する
DrawCursor(hdc);
GetObject(hbmp, sizeof(BITMAP), &bm);
if (pre_config.pic_type != S_BITMAP || pre_config.process_mode != 0){//ただのBITMAPで編集しないならOpenCVは使わず、else文へ
IplImage *convertIplImg, *convertIplImg1, *convertIplImg2;
convertIplImg = cvCreateImage(cvSize(bmiHeader.biWidth, bmiHeader.biHeight), IPL_DEPTH_8U, 4);//メモリ確保
convertIplImg1 = cvCreateImage(cvSize(bmiHeader.biWidth, bmiHeader.biHeight), IPL_DEPTH_8U, 4);//メモリ確保
//ビットマップを構成する各ピクセルの色は、
//ビットイメージ(またはピクセルビット)と呼ばれる配列に格納されています。
//よって、この配列にアクセスすれば、
//ビットマップの任意の位置の色を取得することができます。
//ビットイメージは、BITMAP構造体のbmBits
memcpy(convertIplImg->imageData, bm.bmBits, bmiHeader.biWidth * bmiHeader.biHeight * 4);
cvFlip(convertIplImg, convertIplImg1, 0);//水平軸反転
if (pre_config.process_mode == THRESHOLDING){
convertIplImg2 = cvCreateImage(cvGetSize(convertIplImg1), IPL_DEPTH_8U, 1);
cvSmooth(convertIplImg1, convertIplImg1, CV_GAUSSIAN, 5);
if (pre_config.threshold != 0)
cvThreshold(convertIplImg1, convertIplImg2, pre_config.threshold, 255, CV_THRESH_BINARY);
else
cvThreshold(convertIplImg1, convertIplImg2, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);// (1)二値化(大津の手法を利用)
if (pre_config.use_param){
switch (pre_config.pic_type)
{
case S_PXM:
if (pre_config.param.pxm_binary[1] == 1)
cvSaveImage(aFilename, convertIplImg2, pre_config.param.pxm_binary);
else
cvSaveImage(aFilename, convertIplImg2, 0);
break;
case S_PNG:
if (pre_config.param.png_compression[1] != 3)
cvSaveImage(aFilename, convertIplImg2, pre_config.param.png_compression);
else
cvSaveImage(aFilename, convertIplImg2, 0);
break;
case S_JPG:
if (pre_config.param.jpeg_quality[1] != 95)
cvSaveImage(aFilename, convertIplImg2, pre_config.param.jpeg_quality);
else
cvSaveImage(aFilename, convertIplImg2, 0);
default:
cvSaveImage(aFilename, convertIplImg2, 0);
break;
}
}
else{
cvSaveImage(aFilename, convertIplImg2, 0);
}
cvReleaseImage(&convertIplImg2);
}
else if (pre_config.process_mode == GRAY_SCALED){
convertIplImg2 = cvCreateImage(cvGetSize(convertIplImg1), IPL_DEPTH_8U, 1);
cvCvtColor(convertIplImg1, convertIplImg2, CV_BGR2GRAY);
cvSaveImage(aFilename, convertIplImg2, 0);
cvReleaseImage(&convertIplImg2);
}
else{
cvSaveImage(aFilename, convertIplImg1, 0);
}
// 終了処理
cvReleaseImage(&convertIplImg);
cvReleaseImage(&convertIplImg1);
}
else{
if ((WriteBitmap(tFilename, bm.bmBits, &bmiHeader)) == false)
return -1;
}
// 終了処理
//SelectObject(hdc, hbmpPrev);//これ何なのかよくわからない
//cf.)http://e...content-available-to-author-only...s.jp/graphics/bitmap/bitmap16.html
DeleteObject(hbmp);
DeleteDC(hdc);
delete[] tFilename;
#ifdef UNICODE
delete[] aFilename;
#endif
clock_t finish_time = clock();
timeEndPeriod(1);// タイマーの最小精度を戻す
printf("Finish process.");
printf("\n%lf[sec.](%d)\n", (double)(finish_time - first_time) / CLOCKS_PER_SEC, finish_time - first_time);
return 0;
}