diff --git a/Question_41_50/README.md b/Question_41_50/README.md
index d3c559fd..bdff1b99 100644
--- a/Question_41_50/README.md
+++ b/Question_41_50/README.md
@@ -189,6 +189,8 @@ x = - sin(t) / cos(t) * y + r / cos(t)
## Q.47. モルフォロジー処理(膨張)
+Morphology Dilate
+
*imori.jpg*を大津の二値化したものに、モルフォロジー処理による膨張を2回行え。
モルフォロジー処理とは二値化画像の白(255)マス部分を4近傍(上下左右1マス)に膨張、または1マスだけ収縮させる処理をいう。
@@ -198,10 +200,11 @@ x = - sin(t) / cos(t) * y + r / cos(t)
モルフォロジー処理の膨張(Dilation)アルゴリズムは、
注目画素I(x, y)=0で、I(x, y-1), I(x-1, y), I(x+1, y), I(x, y+1)のどれか一つが255なら、I(x, y) = 255 とする。
+
つまり、上の処理を2回行えば2マス分膨張できることになる。
-例えば、[[0,1,0], [1,0,1], [0,1,0]] のフィルタを掛けた和が255以上なら膨張である、と考える。
+モルフォロジー処理の実装は例えば、[[0,1,0], [1,0,1], [0,1,0]] のフィルタを掛けた和が255以上なら膨張である、と考えることもできる。
|入力 (imori.jpg) |大津の二値化(answers_image/answer_4.jpg)|出力 (answers_image/answer_47.jpg)|
|:---:|:---:|:---:|
@@ -218,7 +221,9 @@ x = - sin(t) / cos(t) * y + r / cos(t)
モルフォロジー処理の収縮(Erosion)アルゴリズムは、
注目画素I(x, y)=255で、I(x, y-1), I(x-1, y), I(x+1, y), I(x, y+1)のどれか一つでも0なら、I(x, y) = 0 とする。
-例えば、[[0,1,0], [1,0,1], [0,1,0]] のフィルタを掛けた和が255*4未満なら収縮である、と考える。
+
+
+収縮処理は例えば、[[0,1,0], [1,0,1], [0,1,0]] のフィルタを掛けた和が255*4未満なら収縮である、と考えることもできる。
|入力 (imori.jpg) |大津の二値化(answers_image/answer_4.jpg)|出力 (answers_image/answer_48.jpg)|
|:---:|:---:|:---:|
@@ -234,6 +239,8 @@ x = - sin(t) / cos(t) * y + r / cos(t)
オープニング処理とは、モルフォロジー処理の収縮をN回行った後に膨張をN回行う処理である。
+
+
オープニング処理により、一つだけ余分に存在する画素などを削除できる。
|入力 (imori.jpg) |大津の二値化(answers_image/answer_4.jpg)|出力 (answers_image/answer_49.jpg)|
@@ -248,6 +255,8 @@ x = - sin(t) / cos(t) * y + r / cos(t)
クロージング処理とは、モルフォロジー処理の膨張をN回行った後に収縮をN回行う処理である。
+
+
クロージング処理により、途中で途切れた画素を結合することができる。
|入力 (imori.jpg) |Canny(answers_image/answer_43.jpg)|出力 (answers_image/answer_50.jpg)|
diff --git a/Question_41_50/answers_cpp/answer_43.cpp b/Question_41_50/answers_cpp/answer_43.cpp
index 8f57001b..9440f240 100644
--- a/Question_41_50/answers_cpp/answer_43.cpp
+++ b/Question_41_50/answers_cpp/answer_43.cpp
@@ -344,7 +344,7 @@ int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
- // Canny step 2
+ // Canny
cv::Mat edge = Canny(img);
//cv::imwrite("out.jpg", out);
diff --git a/Question_41_50/answers_cpp/answer_49.cpp b/Question_41_50/answers_cpp/answer_49.cpp
new file mode 100644
index 00000000..7e2eed0b
--- /dev/null
+++ b/Question_41_50/answers_cpp/answer_49.cpp
@@ -0,0 +1,217 @@
+#include
+#include
+#include
+#include
+
+// BGR -> Gray
+cv::Mat BGR2GRAY(cv::Mat img){
+ // get height and width
+ int width = img.cols;
+ int height = img.rows;
+
+ // prepare output
+ cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
+
+ // each y, x
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // BGR -> Gray
+ out.at(y, x) = 0.2126 * (float)img.at(y, x)[2] \
+ + 0.7152 * (float)img.at(y, x)[1] \
+ + 0.0722 * (float)img.at(y, x)[0];
+ }
+ }
+
+ return out;
+}
+
+// Gray -> Binary
+cv::Mat Binarize_Otsu(cv::Mat gray){
+ int width = gray.cols;
+ int height = gray.rows;
+
+ // determine threshold
+ double w0 = 0, w1 = 0;
+ double m0 = 0, m1 = 0;
+ double max_sb = 0, sb = 0;
+ int th = 0;
+ int val;
+
+ // Get threshold
+ for (int t = 0; t < 255; t++){
+ w0 = 0;
+ w1 = 0;
+ m0 = 0;
+ m1 = 0;
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ val = (int)(gray.at(y, x));
+
+ if (val < t){
+ w0++;
+ m0 += val;
+ } else {
+ w1++;
+ m1 += val;
+ }
+ }
+ }
+
+ m0 /= w0;
+ m1 /= w1;
+ w0 /= (height * width);
+ w1 /= (height * width);
+ sb = w0 * w1 * pow((m0 - m1), 2);
+
+ if(sb > max_sb){
+ max_sb = sb;
+ th = t;
+ }
+ }
+
+ std::cout << "threshold:" << th << std::endl;
+
+ // prepare output
+ cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
+
+ // each y, x
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // Binarize
+ if (gray.at(y, x) > th){
+ out.at(y, x) = 255;
+ } else {
+ out.at(y, x) = 0;
+ }
+
+ }
+ }
+
+ return out;
+}
+
+// Morphology Erode
+cv::Mat Morphology_Erode(cv::Mat img, int Erode_time){
+ int height = img.cols;
+ int width = img.rows;
+
+ // output image
+ cv::Mat tmp_img;
+ cv::Mat out = img.clone();
+
+ // for erode time
+ for (int i = 0; i < Erode_time; i++){
+ tmp_img = out.clone();
+
+ // each pixel
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // check left pixel
+ if ((x > 0) && (tmp_img.at(y, x - 1) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+
+ // check up pixel
+ if ((y > 0) && (tmp_img.at(y - 1, x) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+
+ // check right pixel
+ if ((x < width - 1) && (tmp_img.at(y, x + 1) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+
+ // check left pixel
+ if ((y < height - 1) && (tmp_img.at(y + 1, x) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+ }
+ }
+ }
+
+ return out;
+}
+
+// Morphology Dilate
+cv::Mat Morphology_Dilate(cv::Mat img, int Dilate_time){
+ int height = img.cols;
+ int width = img.rows;
+
+ // output image
+ cv::Mat tmp_img;
+ cv::Mat out = img.clone();
+
+ // for erode time
+ for (int i = 0; i < Dilate_time; i++){
+ tmp_img = out.clone();
+
+ // each pixel
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // check left pixel
+ if ((x > 0) && (tmp_img.at(y, x - 1) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+
+ // check up pixel
+ if ((y > 0) && (tmp_img.at(y - 1, x) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+
+ // check right pixel
+ if ((x < width - 1) && (tmp_img.at(y, x + 1) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+
+ // check left pixel
+ if ((y < height - 1) && (tmp_img.at(y + 1, x) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+ }
+ }
+ }
+
+ return out;
+}
+
+// Morphology opening
+cv::Mat Morphology_Opening(cv::Mat img, int open_time){
+
+ // Morphology dilate
+ img = Morphology_Dilate(img, open_time);
+
+ // Morphology erode
+ img = Morphology_Erode(img, open_time);
+
+ return img;
+}
+
+
+int main(int argc, const char* argv[]){
+ // read image
+ cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
+
+ // BGR -> Gray
+ cv::Mat gray = BGR2GRAY(img);
+
+ // Gray -> Binary
+ cv::Mat bin = Binarize_Otsu(gray);
+
+ // Morphology Opening
+ cv::Mat out = Morphology_Opening(bin, 1);
+
+ //cv::imwrite("out.jpg", out);
+ cv::imshow("sample", out);
+ cv::waitKey(0);
+ cv::destroyAllWindows();
+
+ return 0;
+}
diff --git a/Question_41_50/answers_cpp/answer_50.cpp b/Question_41_50/answers_cpp/answer_50.cpp
new file mode 100644
index 00000000..fe491652
--- /dev/null
+++ b/Question_41_50/answers_cpp/answer_50.cpp
@@ -0,0 +1,464 @@
+#include
+#include
+#include
+#include
+
+// BGR -> Gray
+cv::Mat BGR2GRAY(cv::Mat img){
+ // get height and width
+ int width = img.cols;
+ int height = img.rows;
+
+ // prepare output
+ cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
+
+ // each y, x
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // BGR -> Gray
+ out.at(y, x) = 0.2126 * (float)img.at(y, x)[2] \
+ + 0.7152 * (float)img.at(y, x)[1] \
+ + 0.0722 * (float)img.at(y, x)[0];
+ }
+ }
+
+ return out;
+}
+
+float clip(float value, float min, float max){
+ return fmin(fmax(value, 0), 255);
+}
+
+// gaussian filter
+cv::Mat gaussian_filter(cv::Mat img, double sigma, int kernel_size){
+ int height = img.rows;
+ int width = img.cols;
+ int channel = img.channels();
+
+ // prepare output
+ cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);
+ if (channel == 1) {
+ out = cv::Mat::zeros(height, width, CV_8UC1);
+ }
+
+ // prepare kernel
+ int pad = floor(kernel_size / 2);
+ int _x = 0, _y = 0;
+ double kernel_sum = 0;
+
+ // get gaussian kernel
+ float kernel[kernel_size][kernel_size];
+
+ for (int y = 0; y < kernel_size; y++){
+ for (int x = 0; x < kernel_size; x++){
+ _y = y - pad;
+ _x = x - pad;
+ kernel[y][x] = 1 / (2 * M_PI * sigma * sigma) * exp( - (_x * _x + _y * _y) / (2 * sigma * sigma));
+ kernel_sum += kernel[y][x];
+ }
+ }
+
+ for (int y = 0; y < kernel_size; y++){
+ for (int x = 0; x < kernel_size; x++){
+ kernel[y][x] /= kernel_sum;
+ }
+ }
+
+ // filtering
+ double v = 0;
+
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // for BGR
+ if (channel == 3){
+ for (int c = 0; c < channel; c++){
+ v = 0;
+ for (int dy = -pad; dy < pad + 1; dy++){
+ for (int dx = -pad; dx < pad + 1; dx++){
+ if (((x + dx) >= 0) && ((y + dy) >= 0) && ((x + dx) < width) && ((y + dy) < height)){
+ v += (double)img.at(y + dy, x + dx)[c] * kernel[dy + pad][dx + pad];
+ }
+ }
+ }
+ out.at(y, x)[c] = (uchar)clip(v, 0, 255);
+ }
+ } else {
+ // for Gray
+ v = 0;
+ for (int dy = -pad; dy < pad + 1; dy++){
+ for (int dx = -pad; dx < pad + 1; dx++){
+ if (((x + dx) >= 0) && ((y + dy) >= 0) && ((x + dx) < width) && ((y + dy) < height)){
+ v += (double)img.at(y + dy, x + dx) * kernel[dy + pad][dx + pad];
+ }
+ }
+ }
+ out.at(y, x) = (uchar)clip(v, 0, 255);
+ }
+ }
+ }
+ return out;
+}
+
+// Sobel filter
+cv::Mat sobel_filter(cv::Mat img, int kernel_size, bool horizontal){
+ int height = img.rows;
+ int width = img.cols;
+ int channel = img.channels();
+
+ // prepare output
+ cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
+
+ // prepare kernel
+ double kernel[kernel_size][kernel_size] = {{1, 2, 1}, {0, 0, 0}, {-1, -2, -1}};
+
+ if (horizontal){
+ kernel[0][1] = 0;
+ kernel[0][2] = -1;
+ kernel[1][0] = 2;
+ kernel[1][2] = -2;
+ kernel[2][0] = 1;
+ kernel[2][1] = 0;
+ }
+
+ int pad = floor(kernel_size / 2);
+
+ double v = 0;
+
+ // filtering
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ v = 0;
+ for (int dy = -pad; dy < pad + 1; dy++){
+ for (int dx = -pad; dx < pad + 1; dx++){
+ if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
+ v += (double)img.at(y + dy, x + dx) * kernel[dy + pad][dx + pad];
+ }
+ }
+ }
+ out.at(y, x) = (uchar)clip(v, 0, 255);
+ }
+ }
+ return out;
+}
+
+// get edge
+cv::Mat get_edge(cv::Mat fx, cv::Mat fy){
+ // get height and width
+ int height = fx.rows;
+ int width = fx.cols;
+
+ // prepare output
+ cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
+
+ double _fx, _fy;
+
+ for(int y = 0; y < height; y++){
+ for(int x = 0; x < width; x++){
+ _fx = (double)fx.at(y, x);
+ _fy = (double)fy.at(y, x);
+
+ out.at(y, x) = (uchar)clip(sqrt(_fx * _fx + _fy * _fy), 0, 255);
+ }
+ }
+
+ return out;
+}
+
+// get angle
+cv::Mat get_angle(cv::Mat fx, cv::Mat fy){
+ // get height and width
+ int height = fx.rows;
+ int width = fx.cols;
+
+ // prepare output
+ cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
+
+ double _fx, _fy;
+ double angle;
+
+ for(int y = 0; y < height; y++){
+ for(int x = 0; x < width; x++){
+ _fx = fmax((double)fx.at(y, x), 0.000001);
+ _fy = (double)fy.at(y, x);
+
+ angle = atan2(_fy, _fx);
+ angle = angle / M_PI * 180;
+
+ if(angle < -22.5){
+ angle = 180 + angle;
+ } else if (angle >= 157.5) {
+ angle = angle - 180;
+ }
+
+ //std::cout << angle << " " ;
+
+ // quantization
+ if (angle <= 22.5){
+ out.at(y, x) = 0;
+ } else if (angle <= 67.5){
+ out.at(y, x) = 45;
+ } else if (angle <= 112.5){
+ out.at(y, x) = 90;
+ } else {
+ out.at(y, x) = 135;
+ }
+ }
+ }
+
+ return out;
+}
+
+
+// non maximum suppression
+cv::Mat non_maximum_suppression(cv::Mat angle, cv::Mat edge){
+ int height = angle.rows;
+ int width = angle.cols;
+ int channel = angle.channels();
+
+ int dx1, dx2, dy1, dy2;
+ int now_angle;
+
+ // prepare output
+ cv::Mat _edge = cv::Mat::zeros(height, width, CV_8UC1);
+
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ now_angle = angle.at(y, x);
+ // angle condition
+ if (now_angle == 0){
+ dx1 = -1;
+ dy1 = 0;
+ dx2 = 1;
+ dy2 = 0;
+ } else if(now_angle == 45) {
+ dx1 = -1;
+ dy1 = 1;
+ dx2 = 1;
+ dy2 = -1;
+ } else if(now_angle == 90){
+ dx1 = 0;
+ dy1 = -1;
+ dx2 = 0;
+ dy2 = 1;
+ } else {
+ dx1 = -1;
+ dy1 = -1;
+ dx2 = 1;
+ dy2 = 1;
+ }
+
+ if (x == 0){
+ dx1 = fmax(dx1, 0);
+ dx2 = fmax(dx2, 0);
+ }
+ if (x == (width - 1)){
+ dx1 = fmin(dx1, 0);
+ dx2 = fmin(dx2, 0);
+ }
+ if (y == 0){
+ dy1 = fmax(dy1, 0);
+ dy2 = fmax(dy2, 0);
+ }
+ if (y == (height - 1)){
+ dy1 = fmin(dy1, 0);
+ dy2 = fmin(dy2, 0);
+ }
+
+ // if pixel is max among adjuscent pixels, pixel is kept
+ if (fmax(fmax(edge.at(y, x), edge.at(y + dy1, x + dx1)), edge.at(y + dy2, x + dx2)) == edge.at(y, x)) {
+ _edge.at(y, x) = edge.at(y, x);
+ }
+ }
+ }
+
+ return _edge;
+}
+
+// histerisis
+cv::Mat histerisis(cv::Mat edge, int HT, int LT){
+ int height = edge.rows;
+ int width = edge.cols;
+ int channle = edge.channels();
+
+ // prepare output
+ cv::Mat _edge = cv::Mat::zeros(height, width, CV_8UC1);
+
+ int now_pixel;
+
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // get pixel
+ now_pixel = edge.at(y, x);
+
+ // if pixel >= HT
+ if (now_pixel >= HT){
+ _edge.at(y, x) = 255;
+ }
+ // if LT < pixel < HT
+ else if (now_pixel > LT) {
+ for (int dy = -1; dy < 2; dy++){
+ for (int dx = -1; dx < 2; dx++){
+ // if 8 nearest neighbor pixel >= HT
+ if (edge.at(fmin(fmax(y + dy, 0), 255), fmin(fmax(x + dx, 0), 255)) >= HT){
+ _edge.at(y, x) = 255;
+ }
+ }
+ }
+ }
+ }
+ }
+ return _edge;
+}
+
+
+// Canny
+cv::Mat Canny(cv::Mat img){
+ // BGR -> Gray
+ cv::Mat gray = BGR2GRAY(img);
+
+ // gaussian filter
+ cv::Mat gaussian = gaussian_filter(gray, 1.4, 5);
+
+ // sobel filter (vertical)
+ cv::Mat fy = sobel_filter(gaussian, 3, false);
+
+ // sobel filter (horizontal)
+ cv::Mat fx = sobel_filter(gaussian, 3, true);
+
+ // get edge
+ cv::Mat edge = get_edge(fx, fy);
+
+ // get angle
+ cv::Mat angle = get_angle(fx, fy);
+
+ // edge non-maximum suppression
+ edge = non_maximum_suppression(angle, edge);
+
+ // histerisis
+ edge = histerisis(edge, 50, 20);
+
+ return edge;
+}
+
+// Morphology Erode
+cv::Mat Morphology_Erode(cv::Mat img, int Erode_time){
+ int height = img.cols;
+ int width = img.rows;
+
+ // output image
+ cv::Mat tmp_img;
+ cv::Mat out = img.clone();
+
+ // for erode time
+ for (int i = 0; i < Erode_time; i++){
+ tmp_img = out.clone();
+
+ // each pixel
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // check left pixel
+ if ((x > 0) && (tmp_img.at(y, x - 1) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+
+ // check up pixel
+ if ((y > 0) && (tmp_img.at(y - 1, x) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+
+ // check right pixel
+ if ((x < width - 1) && (tmp_img.at(y, x + 1) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+
+ // check left pixel
+ if ((y < height - 1) && (tmp_img.at(y + 1, x) == 255)){
+ out.at(y, x) = 255;
+ continue;
+ }
+ }
+ }
+ }
+
+ return out;
+}
+
+// Morphology Dilate
+cv::Mat Morphology_Dilate(cv::Mat img, int Dilate_time){
+ int height = img.cols;
+ int width = img.rows;
+
+ // output image
+ cv::Mat tmp_img;
+ cv::Mat out = img.clone();
+
+ // for erode time
+ for (int i = 0; i < Dilate_time; i++){
+ tmp_img = out.clone();
+
+ // each pixel
+ for (int y = 0; y < height; y++){
+ for (int x = 0; x < width; x++){
+ // check left pixel
+ if ((x > 0) && (tmp_img.at(y, x - 1) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+
+ // check up pixel
+ if ((y > 0) && (tmp_img.at(y - 1, x) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+
+ // check right pixel
+ if ((x < width - 1) && (tmp_img.at(y, x + 1) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+
+ // check left pixel
+ if ((y < height - 1) && (tmp_img.at(y + 1, x) == 0)){
+ out.at(y, x) = 0;
+ continue;
+ }
+ }
+ }
+ }
+
+ return out;
+}
+
+// Morphology closing
+cv::Mat Morphology_Closing(cv::Mat img, int open_time){
+
+ // Morphology erode
+ img = Morphology_Erode(img, open_time);
+
+ // Morphology dilate
+ img = Morphology_Dilate(img, open_time);
+
+ return img;
+}
+
+
+int main(int argc, const char* argv[]){
+ // read image
+ cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
+
+ // Canny
+ cv::Mat edge = Canny(img);
+
+ // Morphology Opening
+ cv::Mat out = Morphology_Closing(edge, 1);
+
+ //cv::imwrite("out.jpg", out);
+ cv::imshow("sample", out);
+ cv::waitKey(0);
+ cv::destroyAllWindows();
+
+ return 0;
+}
diff --git a/Question_41_50/answers_image/answer_50.jpg b/Question_41_50/answers_image/answer_50.jpg
index 22cf9ebc..7f81380b 100644
Binary files a/Question_41_50/answers_image/answer_50.jpg and b/Question_41_50/answers_image/answer_50.jpg differ
diff --git a/Question_41_50/answers_py/answer_47.py b/Question_41_50/answers_py/answer_47.py
index 9009f066..6b0d0599 100644
--- a/Question_41_50/answers_py/answer_47.py
+++ b/Question_41_50/answers_py/answer_47.py
@@ -45,8 +45,8 @@ def otsu_binarization(img, th=128):
return out
-# Dilation
-def Dilate(img, Dil_time=1):
+# Morphology Erode
+def Morphology_Erode(img, Dil_time=1):
H, W = img.shape
# kernel
@@ -76,7 +76,7 @@ def Dilate(img, Dil_time=1):
otsu = otsu_binarization(gray)
# Morphology - dilate
-out = Dilate(otsu, Dil_time=2)
+out = Morphology_Erode(otsu, Dil_time=2)
# Save result
cv2.imwrite("out.jpg", out)
diff --git a/Question_41_50/answers_py/answer_48.py b/Question_41_50/answers_py/answer_48.py
index d0d29470..bd459e2b 100644
--- a/Question_41_50/answers_py/answer_48.py
+++ b/Question_41_50/answers_py/answer_48.py
@@ -44,8 +44,8 @@ def otsu_binarization(img, th=128):
return out
-# Erosion
-def Erode(img, Erode_time=1):
+# Morphology Dilate
+def Morphology_Dilate(img, Erode_time=1):
H, W = img.shape
out = img.copy()
@@ -77,7 +77,7 @@ def Erode(img, Erode_time=1):
otsu = otsu_binarization(gray)
# Morphology - dilate
-out = Erode(otsu, Erode_time=2)
+out = Morphology_Dilate(otsu, Erode_time=2)
# Save result
diff --git a/Question_41_50/answers_py/answer_49.py b/Question_41_50/answers_py/answer_49.py
index 6785ff22..410eb5fd 100644
--- a/Question_41_50/answers_py/answer_49.py
+++ b/Question_41_50/answers_py/answer_49.py
@@ -44,8 +44,8 @@ def otsu_binarization(img, th=128):
return out
-# Erosion
-def Erode(img, Erode_time=1):
+# Morphology Dilate
+def Morphology_Dilate(img, Erode_time=1):
H, W = img.shape
out = img.copy()
@@ -66,8 +66,8 @@ def Erode(img, Erode_time=1):
return out
-# Dilation
-def Dilate(img, Dil_time=1):
+# Morphology Erode
+def Morphology_Erode(img, Dil_time=1):
H, W = img.shape
# kernel
@@ -89,8 +89,8 @@ def Dilate(img, Dil_time=1):
# Opening morphology
def Morphology_Opening(img, time=1):
- out = Erode(img, Erode_time=time)
- out = Dilate(out, Dil_time=time)
+ out = Morphology_Dilate(img, Erode_time=time)
+ out = Morphology_Erode(out, Dil_time=time)
return out
diff --git a/Question_41_50/answers_py/answer_50.py b/Question_41_50/answers_py/answer_50.py
index df5198a6..1353898a 100644
--- a/Question_41_50/answers_py/answer_50.py
+++ b/Question_41_50/answers_py/answer_50.py
@@ -24,21 +24,24 @@ def gaussian_filter(img, K_size=3, sigma=1.3):
if len(img.shape) == 3:
H, W, C = img.shape
+ gray = False
else:
img = np.expand_dims(img, axis=-1)
H, W, C = img.shape
+ gray = True
## Zero padding
pad = K_size // 2
out = np.zeros([H + pad * 2, W + pad * 2, C], dtype=np.float)
- out[pad: pad + H, pad: pad + W] = img.copy().astype(np.float)
+ out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)
## prepare Kernel
K = np.zeros((K_size, K_size), dtype=np.float)
for x in range(-pad, -pad + K_size):
for y in range(-pad, -pad + K_size):
- K[y+pad, x+pad] = np.exp( -(x ** 2 + y ** 2) / (2 * (sigma ** 2)))
- K /= (sigma * np.sqrt(2 * np.pi))
+ K[y + pad, x + pad] = np.exp( - (x ** 2 + y ** 2) / (2 * sigma * sigma))
+ #K /= (sigma * np.sqrt(2 * np.pi))
+ K /= (2 * np.pi * sigma * sigma)
K /= K.sum()
tmp = out.copy()
@@ -47,22 +50,29 @@ def gaussian_filter(img, K_size=3, sigma=1.3):
for y in range(H):
for x in range(W):
for c in range(C):
- out[pad + y, pad + x, c] = np.sum(K * tmp[y: y + K_size, x: x + K_size, c])
+ out[pad + y, pad + x, c] = np.sum(K * tmp[y : y + K_size, x : x + K_size, c])
- out = out[pad: pad + H, pad: pad + W].astype(np.uint8)
- out = out[..., 0]
+ out = np.clip(out, 0, 255)
+ out = out[pad : pad + H, pad : pad + W]
+ out = out.astype(np.uint8)
+
+ if gray:
+ out = out[..., 0]
return out
# sobel filter
def sobel_filter(img, K_size=3):
- H, W = img.shape
+ if len(img.shape) == 3:
+ H, W, C = img.shape
+ else:
+ H, W = img.shape
# Zero padding
pad = K_size // 2
out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float)
- out[pad: pad + H, pad: pad + W] = gray.copy().astype(np.float)
+ out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)
tmp = out.copy()
out_v = out.copy()
@@ -76,44 +86,49 @@ def sobel_filter(img, K_size=3):
# filtering
for y in range(H):
for x in range(W):
- out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y: y + K_size, x: x + K_size]))
- out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y: y + K_size, x: x + K_size]))
+ out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y : y + K_size, x : x + K_size]))
+ out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y : y + K_size, x : x + K_size]))
out_v = np.clip(out_v, 0, 255)
out_h = np.clip(out_h, 0, 255)
- out_v = out_v[pad: pad + H, pad: pad + W].astype(np.uint8)
- out_h = out_h[pad: pad + H, pad: pad + W].astype(np.uint8)
+ out_v = out_v[pad : pad + H, pad : pad + W]
+ out_v = out_v.astype(np.uint8)
+ out_h = out_h[pad : pad + H, pad : pad + W]
+ out_h = out_h.astype(np.uint8)
return out_v, out_h
- def get_edge_tan(fx, fy):
+ def get_edge_angle(fx, fy):
# get edge strength
edge = np.sqrt(np.power(fx.astype(np.float32), 2) + np.power(fy.astype(np.float32), 2))
edge = np.clip(edge, 0, 255)
- fx = np.maximum(fx, 1e-5)
+ fx = np.maximum(fx, 1e-10)
#fx[np.abs(fx) <= 1e-5] = 1e-5
# get edge angle
- tan = np.arctan(fy / fx)
+ angle = np.arctan(fy / fx)
- return edge, tan
+ return edge, angle
- def angle_quantization(tan):
- angle = np.zeros_like(tan, dtype=np.uint8)
- angle[np.where((tan > -0.4142) & (tan <= 0.4142))] = 0
- angle[np.where((tan > 0.4142) & (tan < 2.4142))] = 45
- angle[np.where((tan >= 2.4142) | (tan <= -2.4142))] = 95
- angle[np.where((tan > -2.4142) & (tan <= -0.4142))] = 135
+ def angle_quantization(angle):
+ angle = angle / np.pi * 180
+ angle[angle < -22.5] = 180 + angle[angle < -22.5]
+ _angle = np.zeros_like(angle, dtype=np.uint8)
+ _angle[np.where(angle <= 22.5)] = 0
+ _angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45
+ _angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90
+ _angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135
- return angle
+ return _angle
def non_maximum_suppression(angle, edge):
H, W = angle.shape
+ _edge = edge.copy()
for y in range(H):
for x in range(W):
@@ -137,10 +152,10 @@ def non_maximum_suppression(angle, edge):
if y == H-1:
dy1 = min(dy1, 0)
dy2 = min(dy2, 0)
- if max(max(edge[y, x], edge[y+dy1, x+dx1]), edge[y+dy2, x+dx2]) != edge[y, x]:
- edge[y, x] = 0
+ if max(max(edge[y, x], edge[y + dy1, x + dx1]), edge[y + dy2, x + dx2]) != edge[y, x]:
+ _edge[y, x] = 0
- return edge
+ return _edge
def hysterisis(edge, HT=100, LT=30):
H, W = edge.shape
@@ -149,8 +164,8 @@ def hysterisis(edge, HT=100, LT=30):
edge[edge >= HT] = 255
edge[edge <= LT] = 0
- _edge = np.zeros((H+2, W+2), dtype=np.float32)
- _edge[1:H+1, 1:W+1] = edge
+ _edge = np.zeros((H + 2, W + 2), dtype=np.float32)
+ _edge[1 : H + 1, 1 : W + 1] = edge
## 8 - Nearest neighbor
nn = np.array(((1., 1., 1.), (1., 0., 1.), (1., 1., 1.)), dtype=np.float32)
@@ -178,22 +193,22 @@ def hysterisis(edge, HT=100, LT=30):
fy, fx = sobel_filter(gaussian, K_size=3)
# get edge strength, angle
- edge, tan = get_edge_tan(fx, fy)
+ edge, angle = get_edge_angle(fx, fy)
# angle quantization
- angle = angle_quantization(tan)
+ angle = angle_quantization(angle)
# non maximum suppression
edge = non_maximum_suppression(angle, edge)
# hysterisis threshold
- out = hysterisis(edge)
+ out = hysterisis(edge, 50, 20)
return out
-# Erosion
-def Erode(img, Erode_time=1):
+# Morphology Dilate
+def Morphology_Dilate(img, Erode_time=1):
H, W = img.shape
out = img.copy()
@@ -214,8 +229,8 @@ def Erode(img, Erode_time=1):
return out
-# Dilation
-def Dilate(img, Dil_time=1):
+# Morphology Erode
+def Morphology_Erode(img, Dil_time=1):
H, W = img.shape
# kernel
@@ -234,9 +249,10 @@ def Dilate(img, Dil_time=1):
return out
+# Morphology Closing
def Morphology_Closing(img, time=1):
- out = Dilate(img, Dil_time=time)
- out = Erode(out, Erode_time=time)
+ out = Morphology_Erode(img, Dil_time=time)
+ out = Morphology_Dilate(out, Erode_time=time)
return out
diff --git a/Question_41_50/assets/canny_erode2.jpg b/Question_41_50/assets/canny_erode2.jpg
new file mode 100644
index 00000000..3dd6a98a
Binary files /dev/null and b/Question_41_50/assets/canny_erode2.jpg differ
diff --git a/Question_41_50/assets/morphology_closing.png b/Question_41_50/assets/morphology_closing.png
new file mode 100644
index 00000000..639688f7
Binary files /dev/null and b/Question_41_50/assets/morphology_closing.png differ
diff --git a/Question_41_50/assets/morphology_dilate.png b/Question_41_50/assets/morphology_dilate.png
new file mode 100644
index 00000000..8d16f422
Binary files /dev/null and b/Question_41_50/assets/morphology_dilate.png differ
diff --git a/Question_41_50/assets/morphology_erode.png b/Question_41_50/assets/morphology_erode.png
new file mode 100644
index 00000000..e61c7d7e
Binary files /dev/null and b/Question_41_50/assets/morphology_erode.png differ
diff --git a/Question_41_50/assets/morphology_opening.png b/Question_41_50/assets/morphology_opening.png
new file mode 100644
index 00000000..7cfbb764
Binary files /dev/null and b/Question_41_50/assets/morphology_opening.png differ
diff --git a/README.md b/README.md
index a925637e..39bb9878 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,7 @@ Twitterで更新を発信してますぅ
https://twitter.com/curry_frog
+- 2019.11.22 [C++] Q.49~50 モルフォロジー処理(オープンイング、クロージング)を追加
- 2019.11.21 [C++] Q.48 モルフォロジー処理(収縮)を追加
- 2019.11.20 [C++] Q.47 モルフォロジー処理(膨張)を追加
- 2019.10.27 [C++] Q.44~46 Hough直線検出を追加、[Python]の解答を修正
@@ -147,8 +148,8 @@ $ git clone https://github.com/yoyoyo-yo/Gasyori100knock.git
| 41 | [Cannyエッジ検出 (Step.1) エッジ強度](Question_41_50#q41-canny%E3%82%A8%E3%83%83%E3%82%B8%E6%A4%9C%E5%87%BA-step1-%E3%82%A8%E3%83%83%E3%82%B8%E5%BC%B7%E5%BA%A6) | [✓](Question_41_50/answers_py/answer_41.py) | [✓](Question_41_50/answers_cpp/answer_41.cpp) | | 46 | [Hough変換・直線検出 (Step.3) Hough逆変換](Question_41_50#q46-hough%E5%A4%89%E6%8F%9B%E7%9B%B4%E7%B7%9A%E6%A4%9C%E5%87%BA-step3-hough%E9%80%86%E5%A4%89%E6%8F%9B) | [✓](Question_41_50/answers_py/answer_46.py) | [✓](Question_41_50/answers_cpp/answer_46.cpp) |
| 42 | [Cannyエッジ検出 (Step.2) 細線化](Question_41_50#q42-canny%E3%82%A8%E3%83%83%E3%82%B8%E6%A4%9C%E5%87%BA-step2-%E7%B4%B0%E7%B7%9A%E5%8C%96) | [✓](Question_41_50/answers_py/answer_42.py) | [✓](Question_41_50/answers_cpp/answer_42.cpp) | | 47 | [モルフォロジー処理(膨張)](Question_41_50#q47-%E3%83%A2%E3%83%AB%E3%83%95%E3%82%A9%E3%83%AD%E3%82%B8%E3%83%BC%E5%87%A6%E7%90%86%E8%86%A8%E5%BC%B5) | [✓](Question_41_50/answers_py/answer_47.py) | [✓](Question_41_50/answers_cpp/answer_47.cpp) |
| 43 | [Cannyエッジ検出 (Step.3) ヒステリシス閾処理](Question_41_50#q43-canny%E3%82%A8%E3%83%83%E3%82%B8%E6%A4%9C%E5%87%BA-step3-%E3%83%92%E3%82%B9%E3%83%86%E3%83%AA%E3%82%B7%E3%82%B9%E9%96%BE%E5%87%A6%E7%90%86) | [✓](Question_41_50/answers_py/answer_43.py) | [✓](Question_41_50/answers_cpp/answer_43.cpp) | | 48 | [モルフォロジー処理(収縮)](Question_41_50#q48-%E3%83%A2%E3%83%AB%E3%83%95%E3%82%A9%E3%83%AD%E3%82%B8%E3%83%BC%E5%87%A6%E7%90%86%E5%8F%8E%E7%B8%AE) | [✓](Question_41_50/answers_py/answer_48.py) | [✓](Question_41_50/answers_cpp/answer_48.cpp)|
-| 44| [Hough変換・直線検出 (Step.1) Hough変換](Question_41_50#q44-hough%E5%A4%89%E6%8F%9B%E7%9B%B4%E7%B7%9A%E6%A4%9C%E5%87%BA-step1-hough%E5%A4%89%E6%8F%9B) | [✓](Question_41_50/answers_py/answer_44.py) | [✓](Question_41_50/answers_cpp/answer_44.cpp) | | 49 | [オープニング処理](Question_41_50#q49-%E3%82%AA%E3%83%BC%E3%83%97%E3%83%8B%E3%83%B3%E3%82%B0%E5%87%A6%E7%90%86) | [✓](Question_41_50/answers_py/answer_49.py) | |
-| 45| [Hough変換・直線検出 (Step.2) NMS](Question_41_50#q45-hough%E5%A4%89%E6%8F%9B%E7%9B%B4%E7%B7%9A%E6%A4%9C%E5%87%BA-step2-nms) | [✓](Question_41_50/answers_py/answer_45.py) | [✓](Question_41_50/answers_cpp/answer_45.cpp) | | 50 | [クロージング処理](Question_41_50#q50-%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0%E5%87%A6%E7%90%86) | [✓](Question_41_50/answers_py/answer_50.py) | |
+| 44| [Hough変換・直線検出 (Step.1) Hough変換](Question_41_50#q44-hough%E5%A4%89%E6%8F%9B%E7%9B%B4%E7%B7%9A%E6%A4%9C%E5%87%BA-step1-hough%E5%A4%89%E6%8F%9B) | [✓](Question_41_50/answers_py/answer_44.py) | [✓](Question_41_50/answers_cpp/answer_44.cpp) | | 49 | [オープニング処理](Question_41_50#q49-%E3%82%AA%E3%83%BC%E3%83%97%E3%83%8B%E3%83%B3%E3%82%B0%E5%87%A6%E7%90%86) | [✓](Question_41_50/answers_py/answer_49.py) |[✓](Question_41_50/answers_cpp/answer_49.cpp) |
+| 45| [Hough変換・直線検出 (Step.2) NMS](Question_41_50#q45-hough%E5%A4%89%E6%8F%9B%E7%9B%B4%E7%B7%9A%E6%A4%9C%E5%87%BA-step2-nms) | [✓](Question_41_50/answers_py/answer_45.py) | [✓](Question_41_50/answers_cpp/answer_45.cpp) | | 50 | [クロージング処理](Question_41_50#q50-%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%B3%E3%82%B0%E5%87%A6%E7%90%86) | [✓](Question_41_50/answers_py/answer_50.py) | [✓](Question_41_50/answers_cpp/answer_50.cpp)|
### [問題51 - 60](Question_51_60)