오늘 WinRT로 프로젝트를 제작하면서 OpenCV를 사용해야해서 OpenCV 코드를 작성하고 컴파일을 했는데 컴파일은 분명 잘되는데 프로그램을 실행하면서 처음에는 잘 되다가 다시 실행하면 아래 사진 처럼 오류가 난다.. 도무지 모르겠는데.. 그러다가 12시간 동안 뭔 문제인지 찾다가 알아냈다. (참고로 진짜 12시간 걸렸다.)
Windows 스토어 응용 프로그램 'APP_ID'을(를) 활성화할 수 없습니다. 활성화 요청이 실패했습니다(오류: '앱이 시작되지 않았습니다').
문제 해결에 필요한 정보는 도움말을 참조하십시오.
일단 이 원인.. 잘은 모르겠지만 프로그램을 실행하는 도중에 Console 창에서 오류가 나는걸 못 띄우니 여기에서라도 실행할 때 오류를 띄우는 것 같다. 그렇다 보니 원인도 모르겠고..
디버깅을 할려고 해도 처음부터 저게 떠버려서 디버깅도 할 수가 없었다..
아무튼 방법은 찾았는데 그냥 OpenCV dll 파일이 없어서였다.. (왜 이것 때문에.. 12시간을 고생한거지;;)
OpenCV VCPKG WinRT용 설치
일단 한가지 알아낸 것이 있는데 WinRT에서 OpenCV를 돌릴려면 WinRT 용으로 바꿔야하는 것 같다.
WinRT는 UWP로 뭐 돌아가는건데 VCPKG에서 UWP로 설치하는 버전이 있다.
설치는 아래처럼 하면 된다.
vcpkg install opencv4:x64-uwp
이런식으로 설치하면 uwp 버전으로 설치가 된다.
오류 해결
앞에서 말했 듯이 그냥 이건 dll 파일이 없어서 생긴 오류이다. 실행에 필요한 dll 파일을 아래 경로에 붙여넣기 하면 오류가 해결된다.
<ProjectName>\x64\Release\<ProjectName>\AppX
그런데 한가지 문제가 있다.. 빌드 할 때마다 dll 파일이 사라져서 자동으로 복붙이 되게 해야하는데
이럴 때는 방법이 있다.
한가지는 그냥 Visual Studio 프로젝트 파일에서 새 필터로 dll 파일 추가해서 하는 방법이 있는데 이건 비추하고..
다른 방법으로는 자동으로 복붙되게 하는 것이다.
일단 프로젝트 파일을 오른쪽 클릭하고 속성을 열어서 나오는 창에서
빌드 이벤트의 빌드 후 이벤트로 들어간다.
들어가게 되면 명령줄이 있는데 여기에 코드만 넣으면 된다.
일단 빌드 후 이벤트는 말그대로 빌드가 된 후에 자동으로 명령줄에 있는걸 실행하게 되는 것이다.
코드는 이걸 넣으면 된다!
xcopy /y "$(OutDir)\*.dll" "$(OutDir)\AppX"
일단 내가 대충 만들어서 해봤을 때 되는 코드이니.. 이게 잘 되는 코드인지는 모르겠다만.. 아무튼 난 저걸 명령줄에 넣어서 했더니 자동으로 복붙이 되었고 해결이 되었다.
코드는 대충 AppX에는 dll이 없지만 다행이도 출력 폴더에는 dll 파일이 있다. 그래서 모든 dll 파일을 가지고 AppX 폴더로 붙여 넣는 코드이다.
음 얼굴을 찾아서 뭔가 하는 프로젝트를 하는데 문제가 생겼다. 얼굴 이미지를 벡터로 변환하는 과정에서 원본 이미지와 얼굴 이미지를 보여줄려 하는데 여기에서 원본 이미지를 보여줄 때 보여주는 곳이 정사각형이다..
근데 OpenCV에서 이미지를 바로 넣을려니깐 이미지를 리사이즈 해서 바로 넣는 것은 좀 불편하고.. 이미지를 정사각형에 맞춰 그냥 리사이즈 하면 이미지가 뭉개지고..
그래서 생각한건 흰 배경 중심에 이미지를 넣는 것인데
이걸 해결하기 위해 1시간 반이 걸렸다..
대충 생각을 해본건
원본 이미지 보여주는 곳과 같은 크기의 배경을 만들어 중앙에 이미지를 배치
원본 이미지의 가장 긴 변의 길이 대로 정사각형 배경을 만들어 중앙에 이미지를 배치
이런거고 중앙에다 배치할땐
비율에 맞춰 원본 이미지를 축소해서 정사각형 배경의 반에 원본 이미지의 짧은 변의 반을 빼서 위치 구하기
정사각형 배경의 반에 원본 이미지의 짧은 변의 반을 빼서 위치 구하기
등을 생각했는데.. 난 여러가지 방법을 생각해보고 여러가지 방법을 해보다가 헷갈리기도 하고 수정하기도 하며 결국엔
원본 이미지의 가장 긴 변의 길이 대로 정사각형 배경을 만들어 중앙에 이미지를 배치할 때 정사각형 배경의 반에 원본 이미지의 짧은 변의 반을 빼서 위치를 구해서 바로 넣는 방법을 하기로 결정했다..
그래서 이걸 대충 이미지로 표시하면!
이런식으로
일단 정사각형이면 그냥 패스
가로가 더 길 경우 가로 길이에 대한 정사각형을 만들고 그게 검은색 사각형!
거기에 빨간색이 원본 이미지꺼..
그래서 파란색 동그라미 부분의 위치를 구하는건데
검은색 사각형에서 반을 나누고 거기에서 빨간색 사각형의 반을 빼면 저 위치가 나올 것 같다 라는 생각이 들어서 한 것!
파란색은 반대로 생각하면 되는거!
대충 식은
(Square / 2) - (OriginalImg / 2)
뭐 어쨌든 이런 방법으로 코드를 구현한 결과!
if (I_celeb_img.nc() == I_celeb_img.nr())
GUICon::putWebcamView(I_celeb_img, preSetImage);
else {
int squareLen = (I_celeb_img.nc() < I_celeb_img.nr() ? I_celeb_img.nr() : I_celeb_img.nc());
cv::Mat originalViewImage(squareLen, squareLen, CV_8UC3);
originalViewImage = cv::Scalar(0xFF, 0xFF, 0xFF);
//배경 이미지 중앙에 사진 넣기
//사진의 세로가 가로보다 짧을 경우 squareLen의 길이에서 반을 나누고 이미지 세로의 반 만큼 빼서 좌표 구하기
if (I_celeb_img.nr() < I_celeb_img.nc()) { //가로가 더 클 경우 같을 경우 비교 안하는건 위에서 이미 해서
int ypos = (squareLen / 2) - (I_celeb_img.nr() / 2); //세로가 짧을 경우 정사각형의 한 변의 길에서 이미지 세로 반을 빼서 위치 구하는 것
CPputImage(I_celeb_img, originalViewImage, cv::Rect(0, ypos, I_celeb_img.nc(), I_celeb_img.nr()));
}
else {
int xpos = (squareLen / 2) - (I_celeb_img.nc() / 2); //위와 반대
CPputImage(I_celeb_img, originalViewImage, cv::Rect(xpos, 0, I_celeb_img.nc(), I_celeb_img.nr()));
}
cv::cvtColor(originalViewImage, originalViewImage, cv::COLOR_RGB2BGR);
GUICon::putWebcamView(originalViewImage, preSetImage);
}
코드는 이렇다!
여기에서 CPputImage는 그냥 OpenCV 이미지의 특정 위치에 이미지를 넣는 함수고
GUICon::putWebCamView는 원본 이미지 표시하는 쪽에 이미지 넣는 함수다.. 암튼 그럼!
이런 코드가 있다. 그리고 위쪽에 보면 back_color로 0x99가 정의 되어 있는데.. 이걸 보면 대충 알 수 있다. 일단 MFC에서 텍스트 이미지를 생성하면 텍스트 밖은 기본적으로 흰색(255, 255, 255), 텍스트는 검은색(0, 0, 0)이다.이다. 또한 0x99는 153으로 RGB 153 153 153 뭐 이래보면 회색이다. 최대가 255이니.. 그래서 최종적으로 저 코드는 텍스트외의 배경 (153, 153, 153)과 (255, 255, 255) 부분을 제외한 검은색 부분(텍스트 부분)을 가져와 사전에 정의한 RGB색대로 다시 설정하는 방식이다.
이 때문에 이 코드에선 아무리 안티에일리어싱을 한들.. 안티 에일리어싱이 픽셀 사이사이에 텍스트보다 연한 색을 찍는건데 이 것들이 다 제외되면서 결국엔 안티 에일리어싱이 안찍힌다..
그래서 내가 생각한 방식은 안티 에일리어싱의 경우 연한 색도 처리해야하기 때문에 배경색이 바뀔때마다 당연히 글자 색도 변경될테니 배경색을 직접 지정하고 텍스트 색도 직접 지정해서 나중에 배경색만 빼는 것이다. 그러면 결국엔 적용되는건 텍스트의 진한 픽셀부터 연한 픽셀까지이다. 그래서 이 방법으로 코딩한건 뭐 아래에 넣을꺼고
또한 내가 지정한 것 중에 다른 것들도 있지만 ori가 있다. 내가 당시 했을때 오리엔테이션인가 뭔가 하는걸로 줄여서 적은 것 같은데.. 잘 못 지정한 것 같다.
나중에 자기 글에다 쓰고 싶다면... 적어도 링크라도.. 부탁.. (이거 그럼 만든게 2022년 11월 쯤에 만들었으니.. 고등학교 2학년 때 만든거군.. 아 여기 학교 C/C++과목 전혀 없음)
뭐 어쨌든 끄읕!
얼마나 사용해줄진 모르겠지만.. C++은 겁나 복잡해!!
(파이썬은 짧고 쉽던데.. 방식은 이거와 비슷)
2023-03-01 추가
코드에서 RGBScale에 대한 구조체 내용이 없어서 다시 정리하고 구조체에 대한 것도 새로 적습니다.
struct RGBScale {
int r = 0;
int g = 0;
int b = 0;
int rgb = 0;
RGBScale(int r, int g, int b) {
this->r = r;
this->g = g;
this->b = b;
this->rgb = RGB(r, g, b);
}
};
RGBScale(0xFF, 0xFF, 0xFF)로 적으면 되는데 0xFF는 그냥 16진수 이기 때문에 0부터 255 숫자중 원하는 색깔에 맞춰 적으시면 됩니다! 그리고.. 기본적으로 cv::Mat 생성할 때 RGB가 아닌 BGR로 생성하기 때문에 아마 RGB 이미지를 넣어서 적용시키면 색깔이 다르게 나올꺼기 때문에.. 인풋엔 BGR로 넣으시면 됩니다. 수정할려면 아래 적혀있는 코드에서 비트맵을 OpenCV로 바꿔주는 곳에서 b, g, r 적혀있는 것을 r, g, b로 변경하시면 될겁니다. RGBScale를 따로 만든 이유는 그냥 r, g, b도 추출하고 rgb도 출력하고 하기 위함!
아래는 다시 정리해본 코드!
void CPputText(cv::Mat& O_image, cv::String text, cv::Point org, int ori, const char* fontName, int fontWeight, double fontScale, RGBScale textColor, RGBScale bkColor) {
int fontSize = (int)(10 * fontScale);
int width = O_image.cols;
int height = fontSize * 3 / 2;
HDC hdc = CreateCompatibleDC(NULL); //텍스트 이미지를 만들어두는 곳 같은거
HBRUSH hBrush = CreateSolidBrush(bkColor.rgb); //채우는 방식인데 bkColor로 단색으로 채우는거
//텍스트 이미지 크기 정하는거
RECT rect;
rect.left = rect.top = 0;
rect.right = width;
rect.bottom = height;
//비트맵의 구조를 사전에 정의하는 것 크기나 색
BITMAPINFOHEADER header;
ZeroMemory(&header, sizeof(BITMAPINFOHEADER));
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = width;
header.biHeight = height;
header.biPlanes = 1;
header.biBitCount = 24;
BITMAPINFO bitmapInfo;
bitmapInfo.bmiHeader = header;
HBITMAP hbmp = CreateDIBSection(NULL, (LPBITMAPINFO)&bitmapInfo, DIB_RGB_COLORS, NULL, NULL, 0);
SelectObject(hdc, hbmp); //hdc에 적용? 하는 거
FillRect(hdc, &rect, hBrush); //지정한 크기만큼 완전하게 채우는거 (다 채움)
BITMAP bitmap;
GetObject(hbmp, sizeof(BITMAP), &bitmap);
//텍스트 이미지 만들 때 사용할 수 있는 폰트를 생성? 하는 그런거
HFONT hFont = CreateFontA(
fontSize,
0,
0,
0,
fontWeight,
FALSE,
FALSE,
FALSE,
DEFAULT_CHARSET, //한국어나 일본어나 해주게 하는거 (아마)
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY, //안티 에일리어싱을 켜주는거
VARIABLE_PITCH | FF_ROMAN,
fontName);
SelectObject(hdc, hFont);
SetTextColor(hdc, textColor.rgb);
SetBkColor(hdc, bkColor.rgb);
//계산을 위해 미리 텍스트의 사이즈 구하는거
SIZE size;
GetTextExtentPoint32A(hdc, text.c_str(), (int)text.length(), &size);
TextOutA(hdc, 0, height / 3 * 1, text.c_str(), (int)text.length()); //이미지에 텍스트 적는거
int posX = (ori == 0 ? org.x : (ori == 1 ? org.x - (size.cx / 2) : org.x - size.cx)); //기준 정하는거 0은 텍스트의 왼쪽 1은 텍스트의 중간 2는 텍스트의 오른쪽
int posY = org.y - (size.cy / 2 + 5);
//비트맵 사진을 OpenCV이미지에 삽입해주는거
unsigned char* _tmp;
unsigned char* _img;
for (int y = 0; y < bitmap.bmHeight; y++) {
if (posY + y >= 0 && posY + y < O_image.rows) {
_img = O_image.data + (int)(3 * posX + (posY + y) * (((bitmap.bmBitsPixel / 8) * O_image.cols) & ~3));
_tmp = (unsigned char*)(bitmap.bmBits) + (int)((bitmap.bmHeight - y - 1) * (((bitmap.bmBitsPixel / 8) * bitmap.bmWidth) & ~3));
for (int x = 0; x < bitmap.bmWidth; x++) {
if (x + posX >= O_image.cols)
break;
if (_tmp[0] != bkcolor.b || _tmp[1] != bkcolor.g || _tmp[2] != bkcolor.r) { //텍스트 이미지의 배경 컬러는 없애기 위한 것, bgr 순서로 하는 이유는 Mat 이미지를 처음에 만들 때 BGR 순이여서
_img[0] = (unsigned char)_tmp[0]; //B
_img[1] = (unsigned char)_tmp[1]; //G
_img[2] = (unsigned char)_tmp[2]; //R
}
_img += 3;
_tmp += 3;
}
}
}
//메모리에서 삭제해주는거 이거 안하면 메모리 계속 사용함
DeleteObject(hBrush);
DeleteObject(hFont);
DeleteObject(hbmp);
DeleteObject(hdc);
}
만약 vcpkg가 없는 명령어로 뜨면 환경 변수 설정이 안되거나 환경 변수 경로를 잘 못 지정했을 겁니다!
x64-windows는 vcpkg 패키지 리스트에서 보고 자기꺼에 맞는 걸로 선택하시면 됩니다!
이렇게 하면 모든 것이 설치 완료!!
이제 Visual Studio에서 include에서 사용하시면 됩니다!
// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt
/*
This is an example illustrating the use of the perspective_window tool
in the dlib C++ Library. It is a simple tool for displaying 3D point
clouds on the screen.
*/
#include <dlib/gui_widgets.h>
#include <dlib/image_transforms.h>
#include <cmath>
using namespace dlib;
using namespace std;
// ----------------------------------------------------------------------------------------
int main()
{
// Let's make a point cloud that looks like a 3D spiral.
std::vector<perspective_window::overlay_dot> points;
dlib::rand rnd;
for (double i = 0; i < 20; i+=0.001)
{
// Get a point on a spiral
dlib::vector<double> val(sin(i),cos(i),i/4);
// Now add some random noise to it
dlib::vector<double> temp(rnd.get_random_gaussian(),
rnd.get_random_gaussian(),
rnd.get_random_gaussian());
val += temp/20;
// Pick a color based on how far we are along the spiral
rgb_pixel color = colormap_jet(i,0,20);
// And add the point to the list of points we will display
points.push_back(perspective_window::overlay_dot(val, color));
}
// Now finally display the point cloud.
perspective_window win;
win.set_title("perspective_window 3D point cloud");
win.add_overlay(points);
win.wait_until_closed();
}
// ----------------------------------------------------------------------------
위 소스는 dlib.net의 기본 예제입니다.
위 소스를 Visual Studio에서 빈 프로젝트로 하나 만들고 소스 파일에서 test.cpp 하나 만들고 위 코드를 입력해서 실행해보시면 아주 잘 되는 것을 알 수 있습니다!
이런식으로 창이 나오면 잘 작동한다는 것이죠!
마우스로 회전도 되더라구요.. (그래프로 만들면 재밌겠다)
뭐 어쨌든 이 방식으로 설치했다면 빌드 폴더에 dll 파일도 나오는 경우가 있는데 이걸 같이 이동 시켜줘야지 다른 컴퓨터에서도 정상적으로 작동할 겁니다! (아무것도 설치 안되어있어도 잘 작동하더라구요.. 뭐 다른 한개는 설치해줘야 하긴 하는데..)
찾아보니 x64-windows-static 버전은 exe에다가 dll 파일 연결 안하고 모조리 다 때려 넣는 것 같군요! 하지만 설정은 더 힘드니 저 혼자만 하도록 하죠!